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

bttbmeg0  于 2023-02-18  发布在  ElasticSearch
关注(0)|答案(4)|浏览(103)

我有一个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 } }
}]}

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

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

在没有aws4.sign的情况下,这实际上是可以工作的,因为我已经完全打开了ES域,但是现在我应用了上面的IP地址策略。
现在,我不断地收到这样的错误响应:
我们计算的请求签名与您提供的签名不匹配。请检查AWS保密访问密钥和签名方法。有关详细信息,请参阅服务文档。
我还需要做些什么才能正确地签署请求?

mxg2im7a

mxg2im7a1#

这实际上与axiosaws4这两个库有关。aws4将基于正常的NodeJS http请求进行签名,而在具有主体的POST请求中,,需要主体 * 才能正确地对请求进行签名。
通过传入bodypath,可以非常简单地修复此问题

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",
}))
q5iwbnjs

q5iwbnjs2#

我们发现有一些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 send(process.env.APPSYNC_ENDPOINT, requestBody);
  } catch (error) {
    logger.error(`[ERROR] Error calling appsync: ${JSON.stringify(error, null, 2)}`);
    throw error;
  }

当然,您需要为您的lambda IAM角色提供适当的权限。(本博客文章gives good指针)

qoefvg9y

qoefvg9y3#

是的,我使用了同样的例子,它运行成功。但是每次它返回不同的签名。即使是相同的值。

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",
}))
juzqafwq

juzqafwq4#

上面的回答有助于将其全部引入到我的解决方案中,以便将WS消息从lambda发布到API网关中的WebSocket API。
这样做的原因是为了避免使用aws-sdk,它在第一次运行(预热)时至少增加了约2500 ms。
下面是我的代码,希望它能帮助别人:

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);

相关问题