1

I have an api post end point which would update a customer's information in dynamodb. It is set to authenticate using AWS_IAM. I am getting 403 from my lambda when calling this api. I have allowed execute-api:Invoke permission to the api for the role lambda uses. I see in this post that I need to create a canonical request. I was able to come up with the below code and I still get a 403. I can't figure out what is missing and wish if a different eye can spot the problem. Please help!

"use strict";
const https = require("https");
const crypto = require("crypto");

exports.handler = async (event, context, callback) => {
  try {
    var attributes = {
      customerId: 1,
      body: { firstName: "abc", lastName: "xyz" }
    };

    await updateUsingApi(attributes.customerId, attributes.body)
      .then((result) => {
        var jsonResult = JSON.parse(result);
        if (jsonResult.statusCode === 200) {
          callback(null, {
            statusCode: jsonResult.statusCode,
            statusMessage: "Attributes saved successfully!"
          });
        } else {
          callback(null, jsonResult);
        }
      })
      .catch((err) => {
        console.log("error: ", err);
        callback(null, err);
      });
  } catch (error) {
    console.error("error: ", error);
    callback(null, error);
  }
};

function sign(key, message) {
  return crypto.createHmac("sha256", key).update(message).digest();
}

function getSignatureKey(key, dateStamp, regionName, serviceName) {
  var kDate = sign("AWS4" + key, dateStamp);
  var kRegion = sign(kDate, regionName);
  var kService = sign(kRegion, serviceName);
  var kSigning = sign(kService, "aws4_request");
  return kSigning;
}

function updateUsingApi(customerId, newAttributes) {
  var request = {
    partitionKey: `MY_CUSTOM_PREFIX_${customerId}`,
    sortKey: customerId,
    payLoad: newAttributes
  };

  var data = JSON.stringify(request);

  var apiHost = new URL(process.env.REST_API_INVOKE_URL).hostname;
  var apiMethod = "POST";
  var path = `/stage/postEndPoint`;

  var { amzdate, authorization, contentType } = getHeaders(host, method, path);

  const options = {
    host: host,
    path: path,
    method: method,
    headers: {
      "X-Amz-Date": amzdate,
      Authorization: authorization,
      "Content-Type": contentType,
      "Content-Length": data.length
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      if (res && res.statusCode !== 200) {
        console.log("response from api", res);
      }

      var response = {
        statusCode: res.statusCode,
        statusMessage: res.statusMessage
      };

      resolve(JSON.stringify(response));
    });

    req.on("error", (e) => {
      console.log("error", e);
      reject(e.message);
    });

    req.write(data);

    req.end();
  });
}

function getHeaders(host, method, path) {
  var algorithm = "AWS4-HMAC-SHA256";
  var region = "us-east-1";
  var serviceName = "execute-api";
  var secretKey = process.env.AWS_SECRET_ACCESS_KEY;
  var accessKey = process.env.AWS_ACCESS_KEY_ID;
  var contentType = "application/x-amz-json-1.0";
  var now = new Date();
  var amzdate = now
    .toJSON()
    .replace(/[-:]/g, "")
    .replace(/\.[0-9]*/, "");
  var datestamp = now.toJSON().replace(/-/g, "").replace(/T.*/, "");
  var canonicalHeaders = `content-type:${contentType}\nhost:${host}\nx-amz-date:${amzdate}\n`;
  var signedHeaders = "content-type;host;x-amz-date";
  var payloadHash = crypto.createHash("sha256").update("").digest("hex");
  var canonicalRequest = [
    method,
    path,
    canonicalHeaders,
    signedHeaders,
    payloadHash
  ].join("/n");
  var credentialScope = [datestamp, region, serviceName, "aws4_request"].join(
    "/"
  );
  const sha56 = crypto
    .createHash("sha256")
    .update(canonicalRequest)
    .digest("hex");
  var stringToSign = [algorithm, amzdate, credentialScope, sha56].join("\n");
  var signingKey = getSignatureKey(secretKey, datestamp, region, serviceName);
  var signature = crypto
    .createHmac("sha256", signingKey)
    .update(stringToSign)
    .digest("hex");
  var authorization = `${algorithm} Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
  return { amzdate, authorization, contentType };
}

user007
  • 1,504
  • 2
  • 18
  • 51

0 Answers0