aws-sdk - CloudFront后面的API网关不支持AWS_IAM身份验证吗?

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

似乎无法调用通过 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. protected REST API 方法及其 API 网关 URL = SUCCESS
  2. 通过 CloudFront 分发 URL = FAILURE 的 protected REST API 方法
  3. 通过 Route 53 域 URL = FAILURE 的 protected REST API 方法

我收到的错误是:

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

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

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

谢谢

最佳答案

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

假设您已将 API Gateway 设置为 CloudFront 分配的源,则需要设置 Lambda@Edge function拦截原始请求,然后使用 SigV4 对其进行签名这样您就可以限制您的 API Gateway 只能通过 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;

关于aws-sdk - CloudFront后面的API网关不支持AWS_IAM身份验证吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48815143/

相关文章:

ssl - s3 静态站点 + 云端 + SSL 不适用于非 www

amazon-web-services - 在 API 网关部署上更新阶段变量时如何避免停机?

node.js - 有什么方法可以为用户生成认知身份验证 token 而无需在 aws-sdk 或 aws-amplify 中提供密码?

php - Laravel 和 AWS Cloudfront

amazon-web-services - 事件类型 : viewer-request results in 503 for lambda edge

amazon-web-services - AWS API 网关中的 OpenID 身份验证

python - AWS API Gateway 和 Python Lambda 返回 HTML

node.js - 通过 http 对 aws 资源的请求进行正确签名

amazon-web-services - 从 S3 IA 中删除 20 亿个对象的最便宜方法

php - 我应该使用什么作为 Cognito 的 UserContextData => EncodedData?