CloudFront背后的API网关是否不支持AWS_IAM身份验证?

Chr*_*ine 13 amazon-cloudfront amazon-cognito aws-sdk aws-api-gateway aws-amplify

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

以下是如何重现这个:

  • 使用API​​网关创建REST API
  • 使用AWS_IAM身份验证保护REST API方法
  • 创建面向REST API的CloudFront分配
  • 在Route 53中创建一个以CloudFront Distribution为目标的A Record

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

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

我得到的错误是:

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

我无法相信AWS不支持自定义域后面的AWS_IAM受保护端点,因为这必须是一个非常常见的用例.

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

谢谢

Ree*_*mes 7

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;
Run Code Online (Sandbox Code Playgroud)


Mic*_*bot 3

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

IAM 身份验证(具体而言,Signature V4)有一个隐含的假设,即客户端正在访问的主机名也是访问服务所使用的主机名。

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

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

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