【问题标题】:How do I sign API requests (AWS SigV4) to Lambda behind Proxy & API Gateway?How do I sign API requests (AWS SigV4) to Lambda behind Proxy & API Gateway?
【发布时间】:2022-12-02 00:42:42
【问题描述】:

I'm working on a project where we currently use Cognito User pools for auth., but after some research we found that if we want more fine-grained access-control we should use an Identity pool instead.

The theory is simple : first we create an Identity Pool that uses the Cognito user pool as Auth provider. Then in API Gateway we set up our Lambda to use Authorizer: AWS_IAM. To access it, User now has to :

  1. Sign in to User pool, which gives user a JWT Token.
  2. Exchange that JWT Token with the Identity pool for temporary AWS Credentials.
  3. Use those new credentials to sign API request to the protected Lambda.

    Steps 1 and 2 work fine, with a test user we manage to get the JWT Token and successfully exchange it for AWS credentials. They look like this (modified for security reasons):

    awsAccessKey: ASIAZFDXSW29NWI3QZ01
    
    awsSecretKey: B+DrYdPMFGbDd1VRLSPV387uHT715zs7IsvdNnDk
    
    awsSessionToken: IQoJb3JpZ2luX2VjEA8aCWV1LXdlc3QtMyJHMEUCIQC4kHasZrfnaMezJkcPtDD8YizZlKESas/a5N9juG/wIQIgShWaOIgIc4X9Xrtlc+wiGuSC1AQNncwoac2vFkpJ3gkqxAQIWBAAGgw2NTI5NTE0MDE0MDIiDDuTZ1aGOpVffl3+XCqhBDmjCS3+1vSsMqV1GxZ96WMoIoEC1DMffPrBhc+NnBf94eMOI4g03M5gAm3uKAVCBkKO713TsQMaf4GOqqNemFC8LcJpKNrEQb+c+kJqqf7VWeWxveuGuPdHl1dmD2/lIc8giY0+q4Wgtbgs6i0/gR5HzdPfantrElu+cRNrn/wIq4Akf+aARUm14XsIgq7/1fT9aKSHpTgrnTLHeXLKOyf/lZ947XdH71IHDZXBUdwdPikJP/Rikwill6RRTVw7kGNOoacagCmmK7CD6uh9h0OnoW3Qw5df+zX5Z8U7U55AyQfEyzeB7bW3KH65yJn6sopegxIIFfcG2CLIvtb5cZYImAz/4BdnppYpsrEgLPUTvRAXn6KUa5sXgc5Vd7tJeRo5qpYckrR2qfbebsU+0361BCYK2HxGJqsUyt1GVsEoAosxofpn/61mYJXqfeR0ifCAgL7OMOquvlaUVXhHmnhWnUSIOUQ+XtRc+DxUDjwn5RPD7QTwLHIat7d4BI4gZJPAcMT9gZrBVO/iN88lk5R0M5LBzFwd5jiUW46H/G755I4e5ZHaT1I37TY3tbcObIFGVVNz5iHDpK/NePTJevKTshe8cYxXczOQgos4J/RsNpqouO9qRgT9JDyXjU3Etyxqm9RzbLYgV3fl5WwZl5ofVmrBsy3adq+088qEz5b9cogPgDggA/nQaPv7nAZHT8u0ct/hw230pmXUDGCutjOML2G6ZYGOoUCy+BitAN0SZOYWlbZlYomIGKMNQuXjV4z+S9CEW8VunqW4Rgl7rTba6xbI0DdX9upYEczeln6pTl+2UPEDYf6usayFfMsGDvJXesqC5EOtWco1Z8tem/wDQIH7ZbioQHZ7UJDd5ntUAruFveY7sXmKsQbtah/RB5W5HLYy19hCmyGpYMnVXxR0FcNGImsweNcprtw9MmQqy2SUK9V6Rwn1yIE6svfAT3NVyzp9ILbP/qSQLGHNhm4CNd8+EJZZa9rcmCbQiQ+iBJ8FW+AmRSCC4LiB1dhuH1KsFo88DyNhYdVf3py8XV4CDR7l+UyuZMrIQsERwx9JzwVBjfv9COT948mvyGTY
    

    The issue is the signing. Our Lambda is behind a CloudFront proxy + API Gateway. Requests to e.g john.dev.project.io are forwarded to the 'real' API origin at api.dev.project.io.

    Using Postman and setting AWS Signature, the request doesn't work and gives following error :

    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.\n\nThe Canonical String for this request should have been\n'................................................................................................................................................................................................................................................................'\n\nThe String-to-Sign should have been\n'............................................................................'\n
    

    We found however, thatby overriding the Host header to the real origin of the API, request now works fine :

    So it seems that since the custom URL we use and the original API URL are different, signatures don't match. The problem is that by default browsers don't allow you to override Host header for security reasons, so our front-end signed requests always fail.

    Maybe the proxy is also modifying other headers before forwarding to origin, which would also invalidate the signature from my understanding...

    Any help appreciated in solving this issue!

【问题讨论】:

    标签: aws-lambda aws-api-gateway amazon-cloudfront aws-signature aws-identitypools


    【解决方案1】:

    I was facing a similar issue when trying to make a signed request to an API Gateway endpoint behind an Akamai proxy.

    The trick to solve itwas indeed to generate a request as if you were sending it directly to the API Gateway URL, sign that request using sigv4 and then send that signed request to the proxy endpoint instead.

    I've put together a simple NodeJS code to exemplify how to do this:

    const AWS = require("aws-sdk");
    
    const { HttpRequest } = require("@aws-sdk/protocol-http");
    const { SignatureV4 } = require("@aws-sdk/signature-v4");
    const { NodeHttpHandler } = require("@aws-sdk/node-http-handler");
    const { Sha256 } = require("@aws-crypto/sha256-browser");
    
    const REGION = "ca-central-1";
    
    const PROXY_DOMAIN = "proxy.domain.com";
    const PROXY_PATH = "/proxypath";
    
    const API_GATEWAY_DOMAIN = "API-ID.execute-api.ca-central-1.amazonaws.com";
    const API_GATEWAY_PATH = "/apigateway/path";
    
    const IDENTITY_ID = "{{identity-pool-region}}:{{identity-pool-id}}";
    const POOL_REGION = "{{identity-pool-region}}";
    
    const REQUEST_BODY = { test: "test" };
    const METHOD = "POST";
    
    const udpatedSignedRequestExample = async () => {
      try {
        const BODY = JSON.stringify(REQUEST_BODY);
    
        const request = new HttpRequest({
          body: BODY,
          headers: {
            "Content-Type": "application/json",
            host: API_GATEWAY_DOMAIN,
          },
          hostname: API_GATEWAY_DOMAIN,
          port: 443,
          method: METHOD,
          path: API_GATEWAY_PATH,
        });
    
        console.log("request", request);
    
        const credentials = await getCredentials();
        console.log(credentials);
    
        const signedRequest = await signRequest(request, credentials);
        console.log("signedRequest", signedRequest);
    
        const updatedSignedRequest = updateRequest(signedRequest);
        console.log("updatedSignedRequest", updatedSignedRequest);
    
        const response = await makeSignedRequest(updatedSignedRequest);
        console.log(response.statusCode + " " + response.body.statusMessage);
      } catch (error) {
        console.log(error);
      }
    };
    
    const getCredentials = async () => {
      var cognitoidentity = new AWS.CognitoIdentity({ region: POOL_REGION });
      var params = {
        IdentityId: IDENTITY_ID,
      };
      const response = await cognitoidentity
        .getCredentialsForIdentity(params)
        .promise();
    
      return {
        accessKeyId: response.Credentials.AccessKeyId,
        secretAccessKey: response.Credentials.SecretKey,
        sessionToken: response.Credentials.SessionToken,
        expiration: response.Credentials.Expiration,
      };
    };
    
    const signRequest = async (request, credentials) => {
      const signer = new SignatureV4({
        credentials: credentials,
        region: REGION,
        service: "execute-api",
        sha256: Sha256,
      });
    
      const signedRequest = await signer.sign(request);
      return signedRequest;
    };
    
    const updateRequest = (httpRequest) => {
      httpRequest.hostname = PROXY_DOMAIN;
      httpRequest.path = PROXY_PATH;
      httpRequest.headers.host = PROXY_DOMAIN;
    
      return httpRequest;
    };
    
    const makeSignedRequest = async (httpRequest) => {
      const client = new NodeHttpHandler();
      const { response } = await client.handle(httpRequest);
      return response;
    };
    
    udpatedSignedRequestExample();

    Hope that helps.

    【讨论】:

      猜你喜欢
      • 2022-11-09
      • 2022-12-01
      • 2017-08-24
      • 2013-07-10
      • 2022-11-20
      • 2022-11-20
      • 2022-12-27
      • 1970-01-01
      • 2017-09-19
      相关资源
      最近更新 更多