通过 http 对 aws 资源的请求进行正确签名

Exp*_*lls 4 amazon-web-services node.js elasticsearch aws-sdk aws-lambda

我有一个 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 } }
}]}
Run Code Online (Sandbox Code Playgroud)

我找到了用于签署 http 请求的aws4。我这样使用它:

axios(aws4.sign({
    host: process.env.ES_ENDPOINT,
    method: "post",
    url: `https://${process.env.ES_ENDPOINT}/foobot/foobot`,
    data,
}))
Run Code Online (Sandbox Code Playgroud)

这实际上在没有该块的情况下工作aws4.sign,因为我完全开放了 ES 域,但现在我应用了上面的 IP 地址策略。

现在,我不断收到这样的错误响应:

我们计算的请求签名与您提供的签名不匹配。检查您的 AWS 秘密访问密钥和签名方法。有关详细信息,请参阅服务文档。

我还需要做些什么才能正确签署请求吗?

Exp*_*lls 7

这实际上与两个库有关,axios并且aws4. aws4将基于正常的 NodeJShttp请求进行签名,并且在带有正文的 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",
}))
Run Code Online (Sandbox Code Playgroud)


Rap*_*aph 5

我们发现有些 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'));
}
Run Code Online (Sandbox Code Playgroud)

用法:

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 send(process.env.APPSYNC_ENDPOINT, requestBody);
  } catch (error) {
    logger.error(`[ERROR] Error calling appsync: ${JSON.stringify(error, null, 2)}`);
    throw error;
  }
Run Code Online (Sandbox Code Playgroud)

当然,您需要为您的 lambda IAM 角色授予适当的权限。(这篇博文给出了很好的指导)