AWS Cloudfront(带 WAF)+ API 网关:如何强制通过 Cloudfront 访问?

AWS Cloudfront (with WAF) + API Gateway: how to force access through Cloudfront?

我想将 WAF 放在 API 网关前面,并且使用 我发现这只能通过手动将启用了 WAF 的额外 Cloudfront 分发放在 APIG。有点遗憾,尤其是 APIG 现在原生支持自定义域,但它应该可以工作。

现在为了使解决方案安全而不是晦涩难懂,我想强制执行 APIs 只能通过 Cloudfront 发行版访问。 执行此操作的最佳选择是什么?

有什么更好的主意吗?或者也许 "the right way" 存在但我忽略了它?

您可以使用自定义域名并将 DNS 指向与 WAF 的分配。原来直接向API网关分配请求是行不通的。

我来自 API Gateway。

不幸的是,我们目前拥有的最佳解决方案是,在 CloudFront 中注入原始自定义 header 并在自定义授权方中对其进行验证(您问题中的选项 4)。

我们已经意识到此限制和 not-so-great 解决方法。我们期待在未来提供更好的 WAF 集成,但我们没有 ETA。

"right" 方法是使用其他人提到的 API 网关中的自定义授权器。

"cheap" 方法是项目符号 3,一个 api 键。如果您试图抵御 ddos​​ 攻击,您可能只会提供 waf -> cloudfront -> api 网关。因此,如果有人发现了您的 api 网关 url 并决定对其进行 ddos​​ 而不是 cloudfront,则自定义授权者意味着您现在首当其冲地受到了对 lambda 的攻击。 Api 网关每秒可以处理超过 10k 个请求,默认的 lambda 限制是每秒 100 个。即使你让亚马逊提高你的限制,你愿意为持续攻击支付每秒 10k lambda 的费用吗?

AWS 代表会告诉您,"API Keys are for identification, not for authentication. The keys are not used to sign requests, and should not be used as a security mechanism" https://aws.amazon.com/blogs/aws/new-usage-plans-for-amazon-api-gateway/

但老实说,如果您不打算在 lambda 中做比验证一些巨大的混乱字符串更好的事情,为什么不把这个负担和成本留给其他人。 (最大密钥长度为 128 个字符)

也许你可以有一个预定的 lambda 函数来发布一个新的 api 密钥并每 6 小时更新一次云端的 header?

如果您想将 api 密钥用于其他用途,则只需要一个 api 网关源进行身份验证,另一个源和 api 网关用于其他一切。通过这种方式,在 ddos​​ 攻击中,您每秒可以处理 10k 请求到您的身份验证 api,而所有其他已经登录的客户每秒总共有 10k 请求来使用您的 api。 Cloudfront 和 waf 每秒可以处理 100K,因此在这种情况下它们不会阻碍您。

如果您在 api 网关后面使用 lambda,请注意另一件事,您可以使用 lambda@edge 并一起跳过 api 网关。 (这不适合大多数情况,因为 lambda@edge 受到严重限制,但我想我会把它扔在那里。)

但最终我们需要 WAF 与 API 网关集成!! :)

可以通过使用 Lambda@Edge function for SigV4 signing origin requests and then enabling IAM auth on your API Gateway. This strategy is able to be used in conjunction with API Keys in your CloudFront distribution (guide for CloudFront+API Key).

强制通过 CloudFront 进行访问

假设您已经将 API 网关设置为 CloudFront 分配的来源,您首先需要创建一个 Lambda@Edge 函数 (guide for Lambda@Edge setup),然后确保其执行角色具有访问权限到您要访问的 API 网关。为简单起见,您可以在 Lambda 的执行角色中使用 AmazonAPIGatewayInvokeFullAccess 托管 IAM 策略,使其有权调用您账户中的任何 API 网关。

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

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}`,
  };

  // Include the body in the signature if present
  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;

请注意,如果您在请求中包含正文,则必须手动配置 Lambda@Edge 函数以通过控制台或 SDK 包含正文或设置 CloudFormation custom resource to call the SDK since CloudFormation does not support enabling this natively yet