我想把WAF放在API网关前面,而(little) info我发现只有在APIG面前手动添加启用了WAF的额外Cloudfront发行版才能实现。这有点遗憾,特别是因为APIG现在本身支持自定义域,但它应该可以工作。
现在为了使解决方案安全而不仅仅是模糊,我想强制要求只能通过Cloudfront发行版访问API。 这样做的最佳选择是什么?
有更好的想法吗?或者也许"正确的方式"这样做存在但我忽略了它?
答案 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 function来SigV4签署原始请求,然后在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