似乎无法调用通过CloudFront分配启用AWS_IAM保护的REST API。
以下是重现此内容的方法:
现在使用经过身份验证的用户(我使用Cognito UserPool用户和aws-amplify)来调用
我得到的错误是:
{" message":"我们计算的请求签名与您提供的签名不符。检查您的AWS Secret Access Key和签名方法。有关详细信息,请参阅服务文档。"}
我无法相信AWS不支持自定义域后面的AWS_IAM保护端点,因为这必须是一个非常常见的用例。
因此,您能否向我提供一份如何实现这一目标的详细清单?
谢谢
答案 0 :(得分:2)
我怀疑这是不可能的,原因有两个。
IAM身份验证 - 特别是Signature V4 - 隐含地假设客户端正在访问的主机名也是通过其访问服务的主机名。
API网关端点期望使用自己的主机名对请求进行签名,作为签名过程中使用的主机头。这可以通过签署API网关端点的请求,然后将URL更改为指向CloudFront端点来解决。
但是,如果您这样做,我希望CloudFront添加到请求的x-amz-cf-id
标头也会使得无法通过有效签名,因为x-amz-*
标头需要签名 - 这是不可能的,因为你不知道标题的值。
我不确定是否有解决方法,此处......但如果您使用的是IAM身份验证,则使用CloudFront的唯一优势是将服务保持在与网站其余部分相同的域名下 - CloudFront无法缓存经过身份验证的请求的任何响应,因为每个请求的缓存密钥都不同。
答案 1 :(得分:1)
如果为API设置了自定义域,API网关现在将使用该自定义域作为主机来生成签名。
https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html
以API网关作为原始资源手动创建CloudFront分配无效。
答案 2 :(得分:1)
它确实支持它,您只需将其主机的API GW或位于其前面的API GW自定义域设置为主机。
这是一个很难调试的人,我在这里写了一个博客,详细介绍了该解决方案,希望对其他人有帮助。 https://www.rehanvdm.com/serverless/cloudfront-reverse-proxy-api-gateway-to-prevent-cors/index.html
答案 3 :(得分:0)
CloudFront不支持对到达分配的呼叫进行IAM身份验证。正如其他人所强调的那样,SigV4依赖于主机标头,并且无法在访问您的域时计算签名(没有做一些棘手的事情,例如在客户端对API网关域进行硬编码,然后使用该标头对SigV4进行硬编码)。但是,您可以使用Lambda @ Edge函数将IAM从分发版本添加到API。
假设您已经将API网关设置为CloudFront分发的来源,则需要设置Lambda@Edge function来拦截来源请求,然后使用SigV4对其进行签名,以便可以限制API只能通过CloudFront访问的网关。
正常的HTTP请求和CloudFront event format之间有相当多的转换,但都可以管理。
首先,创建Lambda @ Edge函数(guide),然后确保其执行角色有权访问您要访问的API网关。为简单起见,您可以在Lambda的执行角色中使用AmazonAPIGatewayInvokeFullAccess
托管的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;
答案 4 :(得分:0)
尝试转到您的api网关控制台并执行以下操作:
然后,您需要从当前用户那里获取jwtToken
。下面的代码显示了如何使用ReactJS进行操作,并为您放大了哪些CloudFront配置。
Amplify.configure({
Auth: {
identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab',
region: 'XX-XXXX-X',
userPoolId: 'XX-XXXX-X_abcd1234',
userPoolWebClientId: 'a1b2c3d4e5f6g7h8i9j0k1l2m3',
},
API: {
endpoints: [
{
name: 'myapi',
endpoint: 'https://XXX',
region: 'XX-XXXX-X',
custom_header: async () => ({ Authorization: (await Auth.currentSession()).idToken.jwtToken})
}
]
});
但是我认为将Auth添加到API的步骤是相同的。
希望有帮助,
答案 5 :(得分:0)
在APIGW中创建一个自定义域,如www.example.com,并将该域映射到特定的API,但不要将www.example.com解析为APIGW的域
将www.example.com解析为CloudFront的分发域。将“基于选定的请求标头的缓存”设置为白名单,向白名单添加主机,授权和其他必要的标头。原始网址已配置为APIGW的默认网址
当客户端使用签名访问CF时,将使用域www.example.com生成签名,然后CF访问具有相同签名的APIGW,而主机也是www.example.com。当APIGW收到签名时,它将使用与其关联的域(仍为www.example.com)来计算签名。然后匹配签名,APIGW正确响应。
对我有用
答案 6 :(得分:0)
在 CF 中作为源的 Api 网关通常没问题,直到您尝试运行某些受网关授权方保护的 API。
正如 Ray Liang 所说,如果您在 API Gateway 设置中设置自定义域,它就会起作用。这是一个不错的功能,允许您进行顶级路径映射,以将多个不同的网关放置在单个域下。
API网关自定义域名的配置会生成一个新的代理域名(通常以“d-”开头)。如果您希望用户直接通过该域访问 api 网关,您可以将其 CName 或别名为您的真实域。在这种情况下,您不想这样做,因为您希望用户通过 CloudFront 访问 APi 网关。因此,必须将 Cloudfront 分布设置为映射到真实域。并使用此代理域(来自 APi 网关的自定义域设置)作为源。
然后使用该原点设置行为并确保让所有标题通过。这将通过默认网关授权方,因为在 API Gateway 看来,请求确实是使用正确的域名(API Gateway 自定义域)签名的。