Подписание AWS Lambda V4. Подпись запроса, которую мы рассчитали, не совпадает с предоставленной вами подписью. Проверьте свой секретный ключ доступа AWS

#amazon-web-services #aws-lambda

Вопрос:

У меня есть AWS-лямбда в узле, которому необходимо выполнить запрос PUT в индекс кластера эластичного поиска. Я использую подпись AWS V4 для запроса. При запросе PUT я сталкиваюсь с этой ошибкой:

сообщение: «Подпись запроса, которую мы рассчитали, не соответствует подписи, которую вы предоставили. Проверьте свой секретный ключ доступа AWS и способ подписи. Для получения подробной информации обратитесь к сервисной документации.’

ПОСТАВИТЬ запрос (неудачно, ненавидит подпись):

     const esRequest = new AWS.HttpRequest(esUrl, region);
    const esDomain = new urlParse(esUrl);
    esRequest.method = "PUT";
    esRequest.path = `/index/_doc/id`;
    esRequest.headers.host = endpoint.hostname.toString();
    esRequest.headers["Content-Type"] = "application/json";
    esRequest.body = JSON.stringify(data);
    esRequest.region = "us-west-2"

    var credentials = new AWS.EnvironmentCredentials('AWS');
    const signer = new AWS.Signers.V4(esRequest, "es");
    signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
    
    const esResponse = await new Promise((resolve, reject) => {
        const httpRequest = https.request({ ...esRequest, host: esDomain.hostname.toString() }, (result) => {
            let data = "";

            result.on("data", (chunk) => {
                data  = chunk;
            });

            result.on("end", () => {
                resolve(JSON.parse(data.toString()));
            });
        });

        httpRequest.write(req.body);
        httpRequest.end();
    });

 

Что интересно, так это то, что если я отправлю запрос GET, я получу успешный ответ

ПОЛУЧИТЬ запрос (успешно с ожидаемым телом ответа на поиск):

     const esRequest = new AWS.HttpRequest(esUrl, region);
    const esDomain = new urlParse(esUrl);
    esRequest.method = "GET";
    esRequest.path = `/index/search?_pretty`;
    esRequest.headers.host = endpoint.hostname.toString();
    esRequest.headers["Content-Type"] = "application/json";
    esRequest.region = "us-west-2"

    var credentials = new AWS.EnvironmentCredentials('AWS');
    const signer = new AWS.Signers.V4(esRequest, "es");
    signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
    
    const esResponse = await new Promise((resolve, reject) => {
        const httpRequest = https.request({ ...esRequest, host: esDomain.hostname.toString() }, (result) => {
            let data = "";

            result.on("data", (chunk) => {
                data  = chunk;
            });

            result.on("end", () => {
                // success
                resolve(JSON.parse(data.toString()));
            });
        });

        httpRequest.write(req.body);
        httpRequest.end();
    });
 

If I do not sign the GET request, I get the following error

Message: ‘User: anonymous is not authorized to perform: es:ESHttpGet’

Which suggests that it wants/needs/likes the signature in this context.

I had some issues with the V4 signing client for node, specifically the client throwing an error until I dug through the SDK code and found that the region is expected on the request, which is not mentioned in the documentation: https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-request-signing.html#es-request-signing-node

in v4.js

   signature: function signature(credentials, datetime) {
    var signingKey = v4Credentials.getSigningKey(
      credentials,
      datetime.substr(0, 8),
      this.request.region, // <--
      this.serviceName,
      this.signatureCache
    );
    return AWS.util.crypto.hmac(signingKey, this.stringToSign(datetime), 'hex');
  },

 

if region isn’t provided on the request, the lambda dumps out this error:

 {
  "errorType": "TypeError",
  "errorMessage": "The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received undefined",
  "code": "ERR_INVALID_ARG_TYPE",
  "stack": [
    "TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received undefined",
    "    at Hmac.update (internal/crypto/hash.js:84:11)",
    "    at Object.hmac (/var/runtime/node_modules/aws-sdk/lib/util.js:428:50)",
    "    at Object.getSigningKey (/var/runtime/node_modules/aws-sdk/lib/signers/v4_credentials.js:74:35)",
    "    at V4.signature (/var/runtime/node_modules/aws-sdk/lib/signers/v4.js:98:36)",
    "    at V4.authorization (/var/runtime/node_modules/aws-sdk/lib/signers/v4.js:93:36)",
    "    at V4.addAuthorization (/var/runtime/node_modules/aws-sdk/lib/signers/v4.js:35:12)",
    "    at Runtime.exports.handler (/var/task/index.js:230:12)",
    "    at processTicksAndRejections (internal/process/task_queues.js:95:5)"
  ]
}
 

I’m at a bit of a loss to know where to look — any suggestions would be greatly appreciated