14

我想将 WAF 放在 API Gateway 前面,并且我发现只有通过在APIG前面手动放置启用 WAF 的额外 Cloudfront 分发才能实现。有点遗憾,特别是因为 APIG 现在原生支持自定义域,但它应该可以工作。

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

  • 我希望能够使用与 S3 类似的“原始访问身份”,但不知道该怎么做。
  • 如果我可以将 IAM 用户(或角色?)分配给 Cloudfront 发行版,我可以使用 APIG IAM 功能,但我不知道如何做到这一点。
  • 我可能需要 APIG 中的 API 密钥,并将其作为来自 Cloudfront 的 Origin Custom Header 传递。只要我们不想将 API 密钥用于其他目的,这可能会奏效,所以我对此并不完全满意。
  • 可以使用虚拟 (!) 自定义授权方,令牌验证表达式实际上检查作为来自 Cloudfront 的 Origin 自定义标头传递的秘密。应该可以,它更灵活,但有点脏……还是不行?

有更好的想法吗?或者也许存在“正确的方法”,但我忽略了它?

4

4 回答 4

5

我来自 API 网关。

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

我们已经意识到这个限制和不太好的解决方法。我们希望在未来提供更好的 WAF 集成,但我们没有 ETA。

于 2017-04-23T22:45:10.683 回答
3

正如其他人所提到的,“正确”的方法是使用 API Gateway 中的自定义授权者。

“便宜”的方式是子弹 3,一个 api 密钥。如果您试图抵御 ddos​​ 攻击,您可能只会配置 waf -> cloudfront -> api 网关。因此,如果有人发现了您的 api 网关 url 并决定使用 ddos​​ 而不是 cloudfront,则自定义授权者意味着您现在首当其冲地受到 lambda 的攻击。Api 网关每秒可以处理超过 10k 个请求,默认 lambda 限制为每秒 100 个。即使您让亚马逊提高您的限制,您是否愿意为持续攻击支付每秒 10k lambda 的费用?

AWS 代表会告诉您,“API 密钥用于识别,而不是用于身份验证。密钥不用于签署请求,也不应用作安全机制” https://aws.amazon.com/blogs/aws/亚马逊API网关的新使用计划/

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

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

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

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

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

于 2017-09-10T14:49:09.433 回答
1

可以通过对SigV4签名源请求使用Lambda@Edge 函数强制通过 CloudFront 进行访问,然后在您的 API 网关上启用 IAM 身份验证。此策略可以与 CloudFront 分配中的 API 密钥结合使用(CloudFront+API 密钥指南)。

假设您已经将 API Gateway 设置为 CloudFront 分配的源,您首先需要创建一个 Lambda@Edge 函数(Lambda@Edge 设置指南),然后确保其执行角色可以访问您想要的 API Gateway访问。为简单起见,您可以AmazonAPIGatewayInvokeFullAccess在您的 Lambda 执行角色中使用托管 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 自定义资源以调用 SDK,因为CloudFormation 尚不支持本机启用此功能

于 2019-01-22T16:29:28.720 回答
0

您可以使用自定义域名并将 DNS 指向带有 WAF 的分配。直接对原始 API Gateway 分发的请求将不起作用。

于 2017-04-14T21:18:53.330 回答