We found that this actually is possible (as I also answered [here][1]).
Assuming that you have already setup API Gateway as an origin for your CloudFront distribution, you need to setup a [Lambda@Edge function][2] that intercepts origin requests and then signs it using [SigV4][3] so that you can restrict your API Gateway to access only via CloudFront.
There is a fair amount of conversion between normal HTTP requests and the [CloudFront event format][4] but it is all manageable.
First, create a Lambda@Edge function ([guide][5]) and then ensure its execution role has access to the API Gateway that you would like to access. For simplicity, you can use the `AmazonAPIGatewayInvokeFullAccess` managed IAM policy in your Lambda's execution role which gives it access to invoke any API Gateway within your account.
Then, if you go with using [aws4][6] as your signing client, this is what your lambda code would look like:
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;
[1]: https://stackoverflow.com/questions/48815143/does-api-gateway-behind-cloudfront-not-support-aws-iam-authentication
[2]: https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html
[3]: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
[4]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html
[5]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html
[6]: https://www.npmjs.com/package/aws4
We found that this actually is possible (as I also answered [here][1]).
Assuming that you have already setup API Gateway as an origin for your CloudFront distribution, you need to setup a [Lambda@Edge function][2] that intercepts origin requests and then signs it using [SigV4][3] so that you can restrict your API Gateway to access only via CloudFront.
There is a fair amount of conversion between normal HTTP requests and the [CloudFront event format][4] but it is all manageable.
First, create a Lambda@Edge function ([guide][5]) and then ensure its execution role has access to the API Gateway that you would like to access. For simplicity, you can use the `AmazonAPIGatewayInvokeFullAccess` managed IAM policy in your Lambda's execution role which gives it access to invoke any API Gateway within your account.
Then, if you go with using [aws4][6] as your signing client, this is what your lambda code would look like:
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;
[1]: https://stackoverflow.com/questions/48815143/does-api-gateway-behind-cloudfront-not-support-aws-iam-authentication
[2]: https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html
[3]: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
[4]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html
[5]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html
[6]: https://www.npmjs.com/package/aws4