AWS Cloudfront (with WAF) + API Gateway: How to force access through Cloudfront?

I want to put WAF in front of the Gateway API and with (small) information . I believe that this is only possible manually by adding an additional Cloudfront distribution with WAF enabled, before APIG. This is a little embarrassing, especially since APIG now supports user areas natively, but it should work.

Now, to make the solution safe, and not just obscure, I want to ensure that the APIs can only be accessed through the Cloudfront distribution. What is the best way to do this?

  • I was hoping to use "Origin Access Identities" similar to S3, but I don’t see how to do it.
  • If I could assign the IAM user (or role?) To the Cloudfront distribution, I could use the APIG IAM function, but I do not see how this can be done.
  • I may need an API key in APIG and pass it as an Origin custom header from Cloudfront. This may work if we do not want to use the API keys for some other purpose, so I am not completely satisfied with this.
  • You can use a custom authorizer (!) Using a token validation expression that actually checks for a secret that is passed as an Origin custom header from Cloudfront. It should work, it's more flexible, but a little dirty ... or not?

Any better ideas? Or perhaps the “right way” for this exists, but I did not notice it?

+12
5

API Gateway.

, , , CloudFront ( 4 ).

. WAF , ETA.

+5

"" Gateway API, .

"" 3, api. , , waf → cloudfront → api gateway, ddos. , - URL- api ddos, , . Api gateway 10 . , 100 . amazon, , 10 000 ?

AWS : " API , . " https://aws.amazon.com/blogs/aws/new-usage-plans-for-amazon-api-gateway/

, - , - , - . ( 128 )

, api 6 ?

api , api, api- . , ddos ​​ 10k aph aph, , , 10k api. Cloudfront waf 100K , .

, api, @ api gateway . ( , lambda @ , , .)

WAF API GATEWAY!!:)

+3

DNS WAF. API Gateway .

0

, ( ).

, API- CloudFront, Lambda @Edge, SigV4, API- CloudFront.

HTTP- CloudFront , .

Lambda @Edge (), , API-, . IAM AmazonAPIGatewayInvokeFullAccess Lambda, API .

, aws4 , - :

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 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;
0

CloudFront Lambda @Edge SigV4 IAM API-. API- CloudFront ().

, API- CloudFront, () Lambda @Edge, , API-, . IAM AmazonAPIGatewayInvokeFullAccess Lambda, API .

Then, if you use aws4 as a signing client, your lambda code will look like this:

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 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;

Please note that if you include the body in your request, you will have to manually configure the Lambda @Edge function to include the body through the console or SDK, or configure the CloudFormation user resource to call the SDK, since CloudFormation does not support it initially

0
source

Source: https://habr.com/ru/post/1016552/


All Articles