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

时间:2017-04-14 13:49:42

标签: amazon-web-services amazon-cloudfront aws-api-gateway amazon-waf

我想把WAF放在API网关前面,而(little) info我发现只有在APIG面前手动添加启用了WAF的额外Cloudfront发行版才能实现。这有点遗憾,特别是因为APIG现在本身支持自定义域,但它应该可以工作。

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

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

有更好的想法吗?或者也许"正确的方式"这样做存在但我忽略了它?

4 个答案:

答案 0 :(得分:5)

我来自API Gateway。

不幸的是,我们现在拥有的最佳解决方案是在CloudFront中注入原始自定义标头并在自定义授权程序中验证(问题中的选项4)。

我们已经意识到这种局限性并没有那么好的解决方法。我们希望将来提供更好的WAF集成,但我们没有ETA。

答案 1 :(得分:3)

“正确”的方式是在其他人提到的API网关中使用自定义授权者。

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

AWS代表将告诉您,“API密钥用于标识,而不是用于身份验证。密钥不用于签署请求,不应用作安全机制”https://aws.amazon.com/blogs/aws/new-usage-plans-for-amazon-api-gateway/

但老实说,如果你不打算在你的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受到严重限制,但我想我会把它扔出去。)

但最终我们需要与API GATEWAY进行WAF集成! :)

答案 2 :(得分:0)

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

答案 3 :(得分:0)

可以通过使用Lambda@Edge functionSigV4签署原始请求,然后在API网关上启用IAM身份验证来强制通过CloudFront进行访问。可以将此策略与CloudFront发行版(guide for CloudFront+API Key)中的API密钥结合使用。

假设您已经将API Gateway设置为CloudFront发行版的来源,则首先需要创建Lambda @ Edge函数(guide for Lambda@Edge setup),然后确保其执行角色可以访问您所使用的API Gateway想要访问。为简单起见,您可以在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来调用SDK,因为CloudFormation does not support enabling this natively yet