17

似乎不可能调用通过 CloudFront Distribution 启用了 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 Gateway URL = SUCCESS
  2. 通过 CloudFront 分配 URL 的受保护 REST API 方法 = FAILURE
  3. 通过 Route 53 域 URL = FAILURE 的受保护 REST API 方法

我得到的错误是:

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

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

因此,您能否向我提供如何实现这一目标的详细列表?

谢谢

4

7 回答 7

4

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

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

正常 HTTP 请求和CloudFront 事件格式之间有相当多的转换,但都是可控的。

首先,创建一个 Lambda@Edge 函数 ( guide ),然后确保其执行角色可以访问您想要访问的 API 网关。为简单起见,您可以AmazonAPIGatewayInvokeFullAccess在您的 Lambda 执行角色中使用托管 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;
于 2019-01-21T23:03:55.960 回答
3

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

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

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

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

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

于 2018-02-16T01:19:25.050 回答
3

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

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

于 2020-10-23T03:12:02.437 回答
1

在您尝试运行一些由网关授权方保护的 API 之前,作为 CF 中的源的 API 网关通常很好。

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

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

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

于 2021-07-14T10:10:24.337 回答
1
  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 正确响应。

它对我有用

于 2019-12-20T06:46:04.410 回答
1

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

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

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

于 2018-07-23T08:36:56.090 回答
0

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

  • 选择你的 API
  • 前往授权人
  • 然后点击 Create New Authorizer 选择 Cognito 然后选择你的用户池 Set token source to Authorization
  • 单击创建
  • 现在转到资源并选择您要配置的 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 的步骤是相同的​​。

希望有帮助,

于 2019-02-06T08:35:30.363 回答