通过 http 正确签署对 aws 资源的请求
Proper signing of requests to aws resources through http
我有一个 lambda 函数,它正在将一些数据写入我也通过 AWS 设置的 Elasticsearch 域。目前我的域上的访问策略只是允许我自己的 IP 地址与域一起工作
{"Version": "2012-10-17", "Statement": [{
"Effect": "Allow", "Principal": {"AWS": "*"},
"Action": "es:*",
"Resource": "arn:aws:es:us-east-1:$ACCOUNT:domain/DOMAIN/*",
"Condition": { "IpAddress": { "aws:SourceIp": $MYIP } }
}]}
我找到了 aws4
library 用于签署 http 请求。我这样使用它:
axios(aws4.sign({
host: process.env.ES_ENDPOINT,
method: "post",
url: `https://${process.env.ES_ENDPOINT}/foobot/foobot`,
data,
}))
这实际上在没有 aws4.sign
的情况下工作,因为我完全打开了 ES 域,但现在我已经应用了上面的 IP 地址策略。
现在,我不断收到这样的错误响应:
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
我还需要做些什么才能正确签署请求吗?
这实际上与axios
和aws4
这两个库有关。 aws4
将基于正常的 NodeJS http
请求进行签名,并且在带有正文的 POST 请求中 需要正文 才能正确签署请求。
这很简单地通过传入 body
和 path
来解决
axios(aws4.sign({
host: process.env.ES_ENDPOINT,
method: "POST",
url: `https://${process.env.ES_ENDPOINT}/foobot/foobot`,
data,
body: JSON.stringify(data),
path: "/foobot/foobot",
}))
是的,我使用了同一个示例并且 运行 成功了。但每次它的 return 不同的签名。即使是相同的值。
axios(aws4.sign({
host: process.env.ES_ENDPOINT,
method: "POST",
url: `https://${process.env.ES_ENDPOINT}/foobot/foobot`,
data,
body: JSON.stringify(data),
path: "/foobot/foobot",
}))
以上答案有助于将其全部纳入我的解决方案,用于将 WS 消息从 lambda 发布到 API 网关中的 Websocket API。
这样做的原因是为了避免使用 aws-sdk,它在第一次 运行(预热)时至少增加了 ~2500ms。
这是我的代码,希望对其他人有帮助:
const data = {'success': true};
const request = {
host: process.env.AWS_API_GATEWAY_ENDPOINT,
method: 'POST',
url: `https://${process.env.AWS_API_GATEWAY_ENDPOINT}/wss/@connections/${connectionId}`, // this is for axios
path: `/wss/@connections/${connectionId}`,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data),
data, // this is needed for axios
}
const signedRequest = aws4.sign(request,
{
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
sessionToken: process.env.AWS_SESSION_TOKEN // needed when sending from a lambda
});
delete signedRequest.headers['Host']; // delete Host to not potentially mess with axios
const response = await axios(signedRequest);
我们发现有些 AWS 库可以顺利处理事情,而无需通过环境变量与 lambda 共享您的凭据。
这是一个允许 lambda 调用 appysync 端点的完整示例。
使其适应任何其他服务应该不难。
希望对大家有所帮助。
const { defaultProvider } = require('@aws-sdk/credential-provider-node');
const { SignatureV4 } = require('@aws-sdk/signature-v4');
const { Sha256 } = require('@aws-crypto/sha256-js');
const { HttpRequest } = require('@aws-sdk/protocol-http');
const axios = require('axios');
const signer = new SignatureV4({
credentials: defaultProvider(),
region: 'eu-west-1',
service: 'appsync',
sha256: Sha256,
});
/**
* Send a signed graphQl request via http to appsync
*
* @param {string} appsyncUrl URL to reach GraphQL
* @param {object} requestBody JSON stringified request object.
* @returns {Promise} Request that has been sent
*/
async function send(appsyncUrl, requestBody) {
const parsedUrl = new UrlParse(appsyncUrl);
const endpoint = parsedUrl.hostname.toString();
const path = parsedUrl.pathname.toString();
const req = new HttpRequest({
hostname: endpoint,
path,
method: 'POST',
body: requestBody,
headers: {
host: endpoint,
'Content-Type': 'application/json',
},
});
const signed = await signer.sign(req, { signingDate: new Date() });
return axios
.post(appsyncUrl, signed.body, { headers: signed.headers })
.then((response) => {
if (response.data && response.data.errors) {
console.error({ error: response.data.errors }, 'Updating data failed');
} else if (response.data) {
return response.data;
}
})
.catch((error) => console.error({ error, endpoint }, 'Failed to connect to graphQL server'));
}
用法:
const myGraphQlMutation = /* GraphQL */ `
mutation MyMutation($id: ID!, $status: String!) {
myMutation(result: { id: $id, status: $status }) {
id
status
}
}
`;
const sendToAppSync = async (id, status) => {
const requestBody = JSON.stringify({
query: myGraphQlMutation,
variables: {
id: id,
status: status,
},
});
try {
const response = await appsync.send(process.env.APPSYNC_ENDPOINT, requestBody);
} catch (error) {
logger.error(`[ERROR] Error calling appsync: ${JSON.stringify(error, null, 2)}`);
throw error;
}
当然,您需要为您的 lambda IAM 角色授予适当的权限。 (本博postgives good指点)
我有一个 lambda 函数,它正在将一些数据写入我也通过 AWS 设置的 Elasticsearch 域。目前我的域上的访问策略只是允许我自己的 IP 地址与域一起工作
{"Version": "2012-10-17", "Statement": [{
"Effect": "Allow", "Principal": {"AWS": "*"},
"Action": "es:*",
"Resource": "arn:aws:es:us-east-1:$ACCOUNT:domain/DOMAIN/*",
"Condition": { "IpAddress": { "aws:SourceIp": $MYIP } }
}]}
我找到了 aws4
library 用于签署 http 请求。我这样使用它:
axios(aws4.sign({
host: process.env.ES_ENDPOINT,
method: "post",
url: `https://${process.env.ES_ENDPOINT}/foobot/foobot`,
data,
}))
这实际上在没有 aws4.sign
的情况下工作,因为我完全打开了 ES 域,但现在我已经应用了上面的 IP 地址策略。
现在,我不断收到这样的错误响应:
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
我还需要做些什么才能正确签署请求吗?
这实际上与axios
和aws4
这两个库有关。 aws4
将基于正常的 NodeJS http
请求进行签名,并且在带有正文的 POST 请求中 需要正文 才能正确签署请求。
这很简单地通过传入 body
和 path
axios(aws4.sign({
host: process.env.ES_ENDPOINT,
method: "POST",
url: `https://${process.env.ES_ENDPOINT}/foobot/foobot`,
data,
body: JSON.stringify(data),
path: "/foobot/foobot",
}))
是的,我使用了同一个示例并且 运行 成功了。但每次它的 return 不同的签名。即使是相同的值。
axios(aws4.sign({
host: process.env.ES_ENDPOINT,
method: "POST",
url: `https://${process.env.ES_ENDPOINT}/foobot/foobot`,
data,
body: JSON.stringify(data),
path: "/foobot/foobot",
}))
以上答案有助于将其全部纳入我的解决方案,用于将 WS 消息从 lambda 发布到 API 网关中的 Websocket API。
这样做的原因是为了避免使用 aws-sdk,它在第一次 运行(预热)时至少增加了 ~2500ms。
这是我的代码,希望对其他人有帮助:
const data = {'success': true};
const request = {
host: process.env.AWS_API_GATEWAY_ENDPOINT,
method: 'POST',
url: `https://${process.env.AWS_API_GATEWAY_ENDPOINT}/wss/@connections/${connectionId}`, // this is for axios
path: `/wss/@connections/${connectionId}`,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data),
data, // this is needed for axios
}
const signedRequest = aws4.sign(request,
{
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
sessionToken: process.env.AWS_SESSION_TOKEN // needed when sending from a lambda
});
delete signedRequest.headers['Host']; // delete Host to not potentially mess with axios
const response = await axios(signedRequest);
我们发现有些 AWS 库可以顺利处理事情,而无需通过环境变量与 lambda 共享您的凭据。
这是一个允许 lambda 调用 appysync 端点的完整示例。
使其适应任何其他服务应该不难。
希望对大家有所帮助。
const { defaultProvider } = require('@aws-sdk/credential-provider-node');
const { SignatureV4 } = require('@aws-sdk/signature-v4');
const { Sha256 } = require('@aws-crypto/sha256-js');
const { HttpRequest } = require('@aws-sdk/protocol-http');
const axios = require('axios');
const signer = new SignatureV4({
credentials: defaultProvider(),
region: 'eu-west-1',
service: 'appsync',
sha256: Sha256,
});
/**
* Send a signed graphQl request via http to appsync
*
* @param {string} appsyncUrl URL to reach GraphQL
* @param {object} requestBody JSON stringified request object.
* @returns {Promise} Request that has been sent
*/
async function send(appsyncUrl, requestBody) {
const parsedUrl = new UrlParse(appsyncUrl);
const endpoint = parsedUrl.hostname.toString();
const path = parsedUrl.pathname.toString();
const req = new HttpRequest({
hostname: endpoint,
path,
method: 'POST',
body: requestBody,
headers: {
host: endpoint,
'Content-Type': 'application/json',
},
});
const signed = await signer.sign(req, { signingDate: new Date() });
return axios
.post(appsyncUrl, signed.body, { headers: signed.headers })
.then((response) => {
if (response.data && response.data.errors) {
console.error({ error: response.data.errors }, 'Updating data failed');
} else if (response.data) {
return response.data;
}
})
.catch((error) => console.error({ error, endpoint }, 'Failed to connect to graphQL server'));
}
用法:
const myGraphQlMutation = /* GraphQL */ `
mutation MyMutation($id: ID!, $status: String!) {
myMutation(result: { id: $id, status: $status }) {
id
status
}
}
`;
const sendToAppSync = async (id, status) => {
const requestBody = JSON.stringify({
query: myGraphQlMutation,
variables: {
id: id,
status: status,
},
});
try {
const response = await appsync.send(process.env.APPSYNC_ENDPOINT, requestBody);
} catch (error) {
logger.error(`[ERROR] Error calling appsync: ${JSON.stringify(error, null, 2)}`);
throw error;
}
当然,您需要为您的 lambda IAM 角色授予适当的权限。 (本博postgives good指点)