-
Notifications
You must be signed in to change notification settings - Fork 937
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2442 from mamrane/mamrane-feature-protect-httpapi…
…-waf-cloudfront New serverless pattern - apigw-http-api-waf-cloudfront-terraform
- Loading branch information
Showing
20 changed files
with
1,645 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# Protect Amazon API Gateway with Amazon CloudFront and AWS WAF | ||
|
||
This pattern demonstrates how to increase the security posture of Amazon API Gateway against common attack patterns such as SQL injection, cross-site scripting (XSS) or DDOS attacks | ||
|
||
Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/apigw-http-api-waf-cloudfront-terraform | ||
|
||
Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. | ||
|
||
## Requirements | ||
|
||
* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. | ||
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured | ||
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) | ||
* [Python 3.12](https://www.python.org/downloads/release/python-3120/) installed | ||
* [Terraform](https://developer.hashicorp.com/terraform/install) (Terraform) installed | ||
|
||
## Deployment Instructions | ||
|
||
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: | ||
``` | ||
git clone https://github.com/aws-samples/serverless-patterns | ||
``` | ||
2. Change directory to the pattern directory: | ||
``` | ||
cd apigw-http-api-waf-cloudfront-terraform | ||
``` | ||
3. Install the Terraform dependencies | ||
```bash | ||
terraform init -input=false | ||
``` | ||
4. Preview the resources that will be deployed with Terraform in your AWS account | ||
```bash | ||
terraform plan \ | ||
-var "region=#your-region#" \ #region where resources will be deployed (default is us-east-1) | ||
-var "cloudfront_domain_name=#your-cloudfront-domain-name#" \ #domain name for your CloudFront distribution (optional) | ||
-var "cloudfront_certificate_arn=arn:aws:acm:#region#:#accountid#:certificate/#certificateid#" \ #cloudfront certificate arn (required only if cloudfront_domain_name is set) | ||
-var "domain_name=#your-api-domain-name#" \ #domain name for your API Gateway (optional) | ||
-var "certificate_arn=arn:aws:acm:#region#:#accountid#:certificate/#certificateid#" \ #certificate arn (required only if domain_name is set) | ||
-var "zone_id=#zoneid#" #hosted zone id (required only if domain_name is set) | ||
``` | ||
5. Provision resources with Terraform in your AWS account | ||
```bash | ||
terraform apply -auto-approve \ | ||
-var "region=#your-region#" \ #region where resources will be deployed (default is us-east-1) | ||
-var "cloudfront_domain_name=#your-cloudfront-domain-name#" \ #domain name for your CloudFront distribution (optional) | ||
-var "cloudfront_certificate_arn=arn:aws:acm:#region#:#accountid#:certificate/#certificateid#" \ #cloudfront certificate arn (required only if cloudfront_domain_name is set) | ||
-var "domain_name=#your-api-domain-name#" \ #domain name for your API Gateway (optional) | ||
-var "certificate_arn=arn:aws:acm:#region#:#accountid#:certificate/#certificateid#" \ #certificate arn (required only if domain_name is set) | ||
-var "zone_id=#zoneid#" #hosted zone id (required only if domain_name is set) | ||
``` | ||
## How it works | ||
Requests sent to a HTTP API Gateway are protected by an Amazon CloudFront distribution and AWS WAF's web ACL. | ||
The below diagram illustrates the solution. | ||
![Solution diagram](/apigw-http-api-waf-cloudfront-terraform/img/solution_overview.png) | ||
When a request is sent to an Amazon CloudFront distribution (Step 1), it is evaluated by a AWS WAF's web ACL (Step 2). | ||
Then a unique key is retrieved from Secrets Manager by an Amazon CloudFront's Lambda@Edge function (Step 3) and is added as a header (x-origin-verify) in the request (Step 4) before being passed to the API Gateway. | ||
Once the HTTP API Gateway receives the request (Step 5), a Lambda authorizer checks the header (Step 6 & Step 7) and confirm its validity (checks if this is the same value as the one in Secrets Manager). | ||
Finally, the request is sent to a backend Lambda function (Step 8) and a response is returned back to the requester. | ||
If the request is sent directly to the HTTP API Gateway, the request fails as the request is not coming from the Amazon CloudFront distribution (the request doesn't include the header x-origin-verify with the unique key from Secrets Manager). | ||
Note: The unique key in Secrets Manager is rotated periodically as shown on the above diagram (Step A, B and C) | ||
## Testing | ||
After deployment, enter the URL of the CloudFront distribution in your navigator: this should return a successful response ("Hello from Lambda!"). | ||
If you enter the URL of the API Gateway: this should return a failure response. | ||
## Cleanup | ||
In order to delete the solution, the follow the steps below. | ||
1. Destroy the resources via Terraform | ||
```bash | ||
terraform destroy -auto-approve \ | ||
-var "region=#your-region#" \ #region where resources will be deployed (default is us-east-1) | ||
-var "cloudfront_domain_name=#your-cloudfront-domain-name#" \ #domain name for your CloudFront distribution (optional) | ||
-var "cloudfront_certificate_arn=arn:aws:acm:#region#:#accountid#:certificate/#certificateid#" \ #cloudfront certificate arn (required only if cloudfront_domain_name is set) | ||
-var "domain_name=#your-api-domain-name#" \ #domain name for your API Gateway (optional) | ||
-var "certificate_arn=arn:aws:acm:#region#:#accountid#:certificate/#certificateid#" \ #certificate arn (required only if domain_name is set) | ||
-var "zone_id=#zoneid#" #hosted zone id (required only if domain_name is set) | ||
``` | ||
|
||
2. Re-run terraform destroy if you the Lambda@Edge function failed to be deleted | ||
|
||
(Note: When the Lambda@Edge function fails to be deleted, you may see the following error message: "Lambda was unable to delete arn:aws:lambda:#region#:#account-id#:function:http-edge-lambda:#version# because it is a replicated function. Please see our documentation for Deleting Lambda@Edge Functions and Replicas") | ||
|
||
```bash | ||
terraform destroy -auto-approve \ | ||
-var "region=#your-region#" \ #region where resources will be deployed (default is us-east-1) | ||
-var "cloudfront_domain_name=#your-cloudfront-domain-name#" \ #domain name for your CloudFront distribution (optional) | ||
-var "cloudfront_certificate_arn=arn:aws:acm:#region#:#accountid#:certificate/#certificateid#" \ #cloudfront certificate arn (required only if cloudfront_domain_name is set) | ||
-var "domain_name=#your-api-domain-name#" \ #domain name for your API Gateway (optional) | ||
-var "certificate_arn=arn:aws:acm:#region#:#accountid#:certificate/#certificateid#" \ #certificate arn (required only if domain_name is set) | ||
-var "zone_id=#zoneid#" #hosted zone id (required only if domain_name is set) | ||
``` | ||
|
||
3. Delete the secret | ||
|
||
(Note: The secret will not be immediately deleted after Step 1. It will only be scheduled for deletion. To fully delete the secret, you will need to use the below command) | ||
|
||
```bash | ||
aws secretsmanager delete-secret --secret-id waf-http-api --force-delete-without-recovery | ||
``` | ||
|
||
---- | ||
Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
||
SPDX-License-Identifier: MIT-0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# create http api gateway | ||
resource "aws_apigatewayv2_api" "http_api" { | ||
description = var.http_api_description | ||
name = var.http_api_name | ||
protocol_type = "HTTP" | ||
tags = { | ||
Name = var.http_api_name | ||
} | ||
cors_configuration { | ||
allow_origins = ["*"] | ||
allow_methods = ["*"] | ||
allow_headers = ["*"] | ||
} | ||
disable_execute_api_endpoint = local.domain_enabled | ||
} | ||
|
||
# create http api gateway route | ||
resource "aws_apigatewayv2_route" "http_api_route" { | ||
api_id = aws_apigatewayv2_api.http_api.id | ||
authorization_type = "CUSTOM" | ||
authorizer_id = aws_apigatewayv2_authorizer.http_api_auth_authorizer.id | ||
route_key = "ANY /" | ||
target = "integrations/${aws_apigatewayv2_integration.http_api_integration.id}" | ||
} | ||
|
||
# create http api gateway integration | ||
resource "aws_apigatewayv2_integration" "http_api_integration" { | ||
api_id = aws_apigatewayv2_api.http_api.id | ||
integration_type = "AWS_PROXY" | ||
description = "Mock Integration for ${var.http_api_name}" | ||
integration_method = "POST" | ||
integration_uri = aws_lambda_function.http_api_lambda.invoke_arn | ||
passthrough_behavior = "WHEN_NO_MATCH" | ||
payload_format_version = "2.0" | ||
} | ||
|
||
# create http api gateway stage | ||
resource "aws_apigatewayv2_stage" "http_api_stage" { | ||
api_id = aws_apigatewayv2_api.http_api.id | ||
name = var.http_api_stage | ||
auto_deploy = true | ||
default_route_settings { | ||
throttling_burst_limit = var.throttling_burst_limit | ||
throttling_rate_limit = var.throttling_rate_limit | ||
} | ||
tags = { | ||
Name = var.http_api_name | ||
} | ||
} |
111 changes: 111 additions & 0 deletions
111
apigw-http-api-waf-cloudfront-terraform/apigw-http-api-waf-cloudfront-terraform.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
{ | ||
"title": "Protect Amazon API Gateway with Amazon CloudFront and AWS WAF", | ||
"description": "Provision an Amazon API Gateway (with Amazon CloudFront and AWS WAF) using Terraform", | ||
"language": "Python", | ||
"level": "300", | ||
"framework": "Terraform", | ||
"introBox": { | ||
"headline": "How it works", | ||
"text": [ | ||
"This pattern demonstrates how to increase the security posture of HTTP API Gateways against common attack patterns such as SQL injection, cross-site scripting (XSS) or DDOS attacks" | ||
] | ||
}, | ||
"gitHub": { | ||
"template": { | ||
"repoURL": "https://serverlessland.com/patterns/apigw-http-api-waf-cloudfront-terraform", | ||
"templateURL": "serverless-patterns/apigw-http-api-waf-cloudfront-terraform", | ||
"projectFolder": "apigw-http-api-waf-cloudfront-terraform", | ||
"templateFile": "main.tf" | ||
} | ||
}, | ||
"resources": { | ||
"bullets": [ | ||
{ | ||
"text": "HTTP API Gateway", | ||
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html" | ||
}, | ||
{ | ||
"text": "How AWS WAF works with Amazon CloudFront features", | ||
"link": "https://docs.aws.amazon.com/waf/latest/developerguide/cloudfront-features.html" | ||
} | ||
] | ||
}, | ||
"deploy": { | ||
"text": ["See the GitHub repo for detailed deployment instructions."] | ||
}, | ||
"testing": { | ||
"text": ["See the GitHub repo for detailed testing instructions."] | ||
}, | ||
"cleanup": { | ||
"text": ["See the GitHub repo for detailed cleanup instructions."] | ||
}, | ||
"authors": [ | ||
{ | ||
"name": "Mehdi Amrane", | ||
"bio": "Senior Solutions Architect at AWS" | ||
} | ||
], | ||
"patternArch": { | ||
"icon4": { | ||
"x": 10, | ||
"y": 70, | ||
"service": "waf", | ||
"label": "AWS WAF" | ||
}, | ||
"icon1": { | ||
"x": 37, | ||
"y": 70, | ||
"service": "cloudfront", | ||
"label": "Amazon CloudFront" | ||
}, | ||
"icon2": { | ||
"x": 64, | ||
"y": 70, | ||
"service": "apigw", | ||
"label": "Amazon API Gateway" | ||
}, | ||
"icon3": { | ||
"x": 90, | ||
"y": 70, | ||
"service": "lambda", | ||
"label": "AWS Lambda" | ||
}, | ||
"icon5": { | ||
"x": 64, | ||
"y": 20, | ||
"service": "lambda", | ||
"label": "Lambda@Edge" | ||
}, | ||
"icon6": { | ||
"x": 90, | ||
"y": 20, | ||
"service": "lambda", | ||
"label": "Authorizer" | ||
}, | ||
"line1": { | ||
"from": "icon4", | ||
"to": "icon1", | ||
"label": "" | ||
}, | ||
"line2": { | ||
"from": "icon1", | ||
"to": "icon2", | ||
"label": "" | ||
}, | ||
"line3": { | ||
"from": "icon2", | ||
"to": "icon3", | ||
"label": "Get unique key" | ||
}, | ||
"line4": { | ||
"from": "icon1", | ||
"to": "icon5", | ||
"label": "Retrieve unique key for header" | ||
}, | ||
"line5": { | ||
"from": "icon2", | ||
"to": "icon6", | ||
"label": "Validate key" | ||
} | ||
} | ||
} |
Oops, something went wrong.