【问题标题】:Does API Gateway behind CloudFront not support AWS_IAM authentication?CloudFront 后面的 API Gateway 不支持 AWS_IAM 身份验证吗?
【发布时间】:2018-07-26 16:13:57
【问题描述】:

似乎无法通过 CloudFront 分配调用启用了 AWS_IAM 保护的 REST API。

这里是如何重现这个:

  • 使用 API Gateway 创建 REST API
  • 使用 AWS_IAM 身份验证保护 REST API 方法
  • 创建一个以 REST API 为目标的 CloudFront 分配
  • 在 Route 53 中创建以 CloudFront 分配为目标的 A 记录

现在使用经过身份验证的用户(我使用 Cognito UserPool 用户和 aws-amplify)来调用

  1. 受保护的 REST API 方法及其 API 网关 URL = SUCCESS
  2. 通过 CloudFront 分配 URL 的受保护 REST API 方法 = FAILURE
  3. 通过 Route 53 域 URL 的受保护 REST API 方法 = FAILURE

我得到的错误是:

{"message":"我们计算的请求签名与您提供的签名不匹配。请检查您的 AWS Secret Access Key 和签名方法。详情请参阅服务文档。"}

我简直不敢相信 AWS 不支持自定义域后面的 AWS_IAM 保护端点,因为这一定是一个非常常见的用例。

因此,您能否提供一份详细的清单,说明如何实现这一目标?

谢谢

【问题讨论】:

  • 你有什么运气吗?

标签: aws-sdk amazon-cloudfront aws-api-gateway amazon-cognito aws-amplify


【解决方案1】:

我怀疑这是不可能的,有两个原因。

IAM 身份验证——特别是 Signature V4——有一个隐含的假设,即客户端访问的主机名也是访问服务的主机名。

API Gateway 端点期望使用其自己的主机名作为签名过程中使用的主机标头对请求进行签名。这可以通过对 API Gateway 端点的请求进行签名,然后将 URL 更改为指向 CloudFront 端点来解决。

但是,如果您这样做,我希望 CloudFront 添加到请求中的 x-amz-cf-id 标头也会使通过有效签名变得不可能,因为需要对 x-amz-* 标头进行签名 - 这是不可能的,因为您不知道该标头的值。

我不确定是否有解决方法,这里...但是如果您使用 IAM 身份验证,使用 CloudFront 的唯一优势是将服务保持在与站点其他部分相同的域名下 -- CloudFront 将无法缓存任何经过身份验证的请求的响应,因为每个请求的缓存键都不同。

【讨论】:

  • 感谢您的回复。对于我们来说,在我们的域名下拥有 API 非常重要。 CloudFront 也可以缓存我想的 OPTIONS 方法?真的没有解决方法来实现这一点吗?
  • 我遇到了同样的问题。我在 apigateway 控制台中找到了一种使用自定义域的方法,但我正在尝试使用 CF 来处理静态网页以及 apigateway。我可以使用子域,但这也带来了一系列问题...... :( 。这里还有其他选择吗?
【解决方案2】:

如果为 API 设置了自定义域,API Gateway 现在会使用自定义域作为主机生成签名。

https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html

使用 API Gateway 作为源手动创建 CloudFront 分配不起作用。

【讨论】:

    【解决方案3】:

    CloudFront 不支持对访问分配的调用进行 IAM 身份验证。正如其他人所强调的那样,SigV4 依赖于主机标头,并且无法在访问您的域时计算签名(无需做一些骇人听闻的事情,例如在客户端硬编码 API 网关域,然后使用该标头对 SigV4 进行硬编码)。但是,您可以使用 Lambda@Edge 函数将 IAM 从您的分配中添加到您的 API。

    假设您已经将 API 网关设置为 CloudFront 分配的源,您需要设置一个 Lambda@Edge function 来拦截源请求,然后使用 SigV4 对其进行签名,这样您就可以限制您的 API 网关只能访问通过 CloudFront。

    在普通 HTTP 请求和CloudFront event format 之间有相当多的转换,但都是可控的。

    首先,创建一个 Lambda@Edge 函数 (guide),然后确保其执行角色可以访问您想要访问的 API 网关。为简单起见,您可以在您的 Lambda 执行角色中使用 AmazonAPIGatewayInvokeFullAccess 托管 IAM 策略,使其能够访问您账户中的任何 API 网关。

    然后,如果您使用 aws4 作为您的签名客户端,您的 lambda 代码将如下所示:

    const aws4 = require("aws4");
    
    const signCloudFrontOriginRequest = (request) => {
      const searchString = request.querystring === "" ? "" : `?${request.querystring}`;
    
      // Utilize a dummy request because the structure of the CloudFront origin request
      // is different than the signing client expects
      const dummyRequest = {
        host: request.origin.custom.domainName,
        method: request.method,
        path: `${request.origin.custom.path}${request.uri}${searchString}`,
      };
    
      if (Object.hasOwnProperty.call(request, 'body')) {
        const { data, encoding } = request.body;
        const buffer = Buffer.from(data, encoding);
        const decodedBody = buffer.toString('utf8');
    
        if (decodedBody !== '') {
          dummyRequest.body = decodedBody;
          dummyRequest.headers = { 'content-type': request.headers['content-type'][0].value };
        }
      }
    
      // Use the Lambda's execution role credentials
      const credentials = {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
        sessionToken: process.env.AWS_SESSION_TOKEN
      };
    
      aws4.sign(dummyRequest, credentials); // Signs the dummyRequest object
    
      // Sign a clone of the CloudFront origin request with appropriate headers from the signed dummyRequest
      const signedRequest = JSON.parse(JSON.stringify(request));
      signedRequest.headers.authorization = [ { key: "Authorization", value: dummyRequest.headers.Authorization } ];
      signedRequest.headers["x-amz-date"] = [ { key: "X-Amz-Date", value: dummyRequest.headers["X-Amz-Date"] } ];
      signedRequest.headers["x-amz-security-token"] = [ { key: "X-Amz-Security-Token", value: dummyRequest.headers["X-Amz-Security-Token"] } ];
    
      return signedRequest;
    };
    
    const handler = (event, context, callback) => {
      const request = event.Records[0].cf.request;
      const signedRequest = signCloudFrontOriginRequest(request);
    
      callback(null, signedRequest);
    };
    
    module.exports.handler = handler;
    

    【讨论】:

      【解决方案4】:

      尝试转到您的 api 网关控制台并执行以下操作:

      • 选择您的 api
      • 转到授权人
      • 然后点击 Create New Authorizer 选择 Cognito 然后选择 您的用户池将令牌来源设置为授权
      • 点击创建
      • 现在转到资源并选择要使用的 HTTP 方法 配置(例如 ANY)
      • 点击方法请求
      • 在“授权”下拉列表中选择您之前创建的那个,然后按对勾。
      • 最后选择Actions并点击Deploy API(选择你要部署的阶段)

      然后你需要从当前用户那里获取jwtToken。下面的代码显示了它是如何使用 ReactJS 完成的,并为您放大了哪些配置 CloudFront。

         Amplify.configure({
            Auth: {
                  identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab',        
                  region: 'XX-XXXX-X',         
                  userPoolId: 'XX-XXXX-X_abcd1234',         
                  userPoolWebClientId: 'a1b2c3d4e5f6g7h8i9j0k1l2m3',
            },
            API: {
              endpoints: [
                {
                  name: 'myapi',
                  endpoint: 'https://XXX',
                   region: 'XX-XXXX-X',   
                  custom_header: async () => ({ Authorization: (await Auth.currentSession()).idToken.jwtToken})
                }
              ]
      });
      

      但我认为将 Auth 添加到 API 的步骤是相同的​​。

      希望对你有帮助,

      【讨论】:

        【解决方案5】:
        1. 在 APIGW 中创建一个像 www.example.com 这样的自定义域并将该域映射到特定的 API,但不要将 www.example.com 解析为 APIGW 的域

        2. 将 www.example.com 解析为 CloudFront 的分发域。将基于选定请求头的缓存设置为白名单,将主机、授权和其他必要的头添加到白名单中。 origin url 配置为 APIGW 的默认 url

        3. 客户端使用签名访问CF时,生成的签名域为www.example.com,然后CF访问同样签名的APIGW,主机也是www.example.com。当 APIGW 收到签名时,它与它关联的域计算签名,仍然是 www.example.com。然后签名匹配,APIGW正确响应。

        它对我有用

        【讨论】:

          【解决方案6】:

          它确实支持它,您只需要将 HOST 设置为您的 API GW 或位于它前面的 API GW 自定义域。

          这是一个难以调试的问题,我在这里写了一篇博客,详细介绍了解决方案,希望对其他人有所帮助。 https://www.rehanvdm.com/serverless/cloudfront-reverse-proxy-api-gateway-to-prevent-cors/index.html

          【讨论】:

            【解决方案7】:

            在 CF 中作为来源的 API 网关通常没问题,除非您尝试运行一些由网关授权器保护的 API。

            正如 Ray Liang 所说,如果您在 API Gateway 设置中设置自定义域,它就可以工作。这是一个不错的功能,允许您进行顶级路径映射,以将多个不同的网关放置在单个域下。

            API网关自定义域的配置会生成一个新的代理域名(一般以“d-”开头)。如果您希望用户直接通过该域访问 api 网关,您可以将其命名或别名为您的真实域。在这种情况下,您不想这样做,因为您希望用户通过 CloudFront 访问 APi 网关。因此,必须将 Cloudfront 分发设置为映射到真实域。并使用此代理域(来自 APi 网关的自定义域设置)作为来源。

            然后使用该来源设置行为,并确保让所有标题通过。这将通过默认网关授权方,因为在 API 网关看来,请求确实是使用正确的域名(API 网关自定义域)签名的。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-03-24
              • 2017-12-15
              • 2018-09-21
              • 2021-05-02
              • 2012-08-03
              • 2016-08-20
              • 2019-12-14
              • 2014-03-25
              相关资源
              最近更新 更多