AWS CloudFront Origin Security

Cloud Journey
7 min readNov 4, 2021

Overview

This blog post is sequel to AWS CloudFront Security Part 1

We will understand more about AWS WAF and CloudFront custom request header feature, compare multiple options and outline pros and cons.

Why We Need Custom Request Header?

  • You can use custom request headers to control access to content.
  • You prevent users from bypassing CloudFront and accessing your origin content directly.
  • Helps to enforce web traffic being processed at CloudFront edge locations according to your AWS WAF rules prior to being forwarded to your origin.
  • Ensure origin only receive traffic from specific CloudFront

Custom Request Header Insertion

There are two product features which support adding custom header to viewer’s HTTP request.

  • Add custom request header by CloudFront
  • Add WAF to CloudFront, use WAF to insert custom request header

Pros and Cons

When using CloudFront to insert “Origin Custom Header” it will be added to the viewer request whenever there is a cache miss and CloudFront forwards the request to the Origin to fetch the content. There will be no additional prefix added to header name specified in the configuration.

When using WAF to insert custom headers, WAF prefixes all request headers that it inserts with “x-amzn-waf-”. For example, if you specify the header name “sample”, WAF inserts the header name as “x-amzn-waf-sample”. WAF will only insert the custom header only when Rule Action is set to “ALLOW” or “COUNT”. As WAF inspects the requests before CloudFront evaluates a cache-hit or cache-miss, the custom request header will be inserted in every request which WAF evaluates. Custom request header can be added at each rule level or at whole WAF WebACL default action level.

From AWS console, WebACL Rules page, edit default web ACL action.

Custom Request Header Validation in Origin

  • Use ALB listener rule to validate the custom request header value
  • Add WAF to origin ALB, use WAF to validate custom request header value

Pros and Cons

WAF rule might charge more than ALB, (https://aws.amazon.com/waf/pricing/ ) Web ACL $5.00/Month (https://aws.amazon.com/elasticloadbalancing/pricing/ ) $0.008 per LCU hour or 1000 rule evaluations, and we just add one rule.

Lab

Download artifact from aws-samples/amazon-cloudfront-waf-secretsmanager: Enhance Amazon CloudFront Origin Security with AWS WAF and AWS Secrets Manager (github.com)

Locate two zip files in artifacts directory, and upload to your S3 bucket.

Locate cf-origin-verify.yaml in templates directory, and create CloudFromation stack.

Secret is rotated first time during initial deployment:

C:\Users\rquan>aws secretsmanager get-secret-value — secret-id arn:aws:secretsmanager:us-east-1:<xyzaccount>:secret:OriginVerifySecret-KXuaqbeP0xTQ-SNchAS — profile user1
{
“ARN”: “arn:aws:secretsmanager:us-east-1:<xyzaccount>:secret:OriginVerifySecret-KXuaqbeP0xTQ-SNchAS”,
“Name”: “OriginVerifySecret-KXuaqbeP0xTQ”,
“VersionId”: “21ed4”,
“SecretString”: “{\”HEADERVALUE\”:\”<some value stage AWSCURRENT>\”}”,
“VersionStages”: [
“AWSCURRENT”,
“AWSPENDING”
],
“CreatedDate”: “2021–11–04T22:24:30.126000–04:00”
}
C:\Users\rquan>aws secretsmanager get-secret-value — secret-id arn:aws:secretsmanager:us-east-1:<xyzaccount>:secret:OriginVerifySecret-KXuaqbeP0xTQ-SNchAS — version-stage AWSCURRENT — profile user1
{
“ARN”: “arn:aws:secretsmanager:us-east-1:<xyzaccount>:secret:OriginVerifySecret-KXuaqbeP0xTQ-SNchAS”,
“Name”: “OriginVerifySecret-KXuaqbeP0xTQ”,
“VersionId”: “21ed4”,
“SecretString”: “{\”HEADERVALUE\”:\”<some value stage AWSCURRENT>\”}”,
“VersionStages”: [
“AWSCURRENT”,
“AWSPENDING”
],
“CreatedDate”: “2021–11–04T22:24:30.126000–04:00”
}
C:\Users\rquan>aws secretsmanager get-secret-value — secret-id arn:aws:secretsmanager:us-east-1:<xyzaccount>:secret:OriginVerifySecret-KXuaqbeP0xTQ-SNchAS — version-stage AWSPENDING — profile user1
{
“ARN”: “arn:aws:secretsmanager:us-east-1:<xyzaccount>:secret:OriginVerifySecret-KXuaqbeP0xTQ-SNchAS”,
“Name”: “OriginVerifySecret-KXuaqbeP0xTQ”,
“VersionId”: “21ed41”,
“SecretString”: “{\”HEADERVALUE\”:\”<some value same as AWS CURRENT>\”}”,
“VersionStages”: [
“AWSCURRENT”,
“AWSPENDING”
],
“CreatedDate”: “2021–11–04T22:24:30.126000–04:00”
}
C:\Users\rquan>aws secretsmanager get-secret-value — secret-id arn:aws:secretsmanager:us-east-1:<xyzaccount>:secret:OriginVerifySecret-KXuaqbeP0xTQ-SNchAS — version-stage AWSPREVIOUS — profile user1
{
“ARN”: “arn:aws:secretsmanager:us-east-1:<xyzaccount>:secret:OriginVerifySecret-KXuaqbeP0xTQ-SNchAS”,
“Name”: “OriginVerifySecret-KXuaqbeP0xTQ”,
“VersionId”: “7bbfe7”,
“SecretString”: “{\”HEADERVALUE\”:\”<some other value>\”}”,
“VersionStages”: [
“AWSPREVIOUS”
],
“CreatedDate”: “2021–11–04T22:17:18.424000–04:00”
}

Current secret value is configured in CloudFront origin setting:

C:\Users\rquan>aws cloudfront get-distribution-config --id <my cloudfront id> --profile user1
{
"ETag": "E159B4I4IB6SZW",
"DistributionConfig": {
"CallerReference": "8040ef0a",
"Aliases": {
"Quantity": 0
},
"DefaultRootObject": "",
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "arn:aws:elasticloadbalancing:us-east-1:<myaccount>:loadbalancer/app/<myalb>/124a7",
"DomainName": "<myalb>.us-east-1.elb.amazonaws.com",
"OriginPath": "",
"CustomHeaders": {
"Quantity": 1,
"Items": [
{
"HeaderName": "X-Origin-Verify",
"HeaderValue": "<some value stage AWSCURRENT>
"
}
]
},
"CustomOriginConfig": {
"HTTPPort": 80,
"HTTPSPort": 443,
"OriginProtocolPolicy": "http-only",
"OriginSslProtocols": {
"Quantity": 1,
"Items": [
"TLSv1.2"
]
},
"OriginReadTimeout": 30,
"OriginKeepaliveTimeout": 5
},
"ConnectionAttempts": 3,
"ConnectionTimeout": 10,
"OriginShield": {
"Enabled": false
}
}
]
},
......

Customization

Above GitHub CloudFormation template deploys WAF with origin ALB, and use WAF WebACL to verify custom request header.

You may utilize following example template to add ALB listener rule to verify custom request header.

You may also want to update ALB listener rule default action to not forward to target group.

Negative Test

For test purpose, I updated CloudFront custom header to different secret value, meanwhile I also invalidate the cache, otherwise, content is from CloufFront cache, it does not even send http request to origin.

When open CloudFront URL, I got response code 403, this is exactly what we expect, the origin only responds when the request has the expected custom header value.

curl -vL http://<mydistribution>.cloudfront.net
......
< HTTP/1.1 301 Moved Permanently
......
< HTTP/2 403

Positive Test

Let’s revert back CloudFront header value:

It works now with http status code 200 when open site through CloudFront.

$ curl -IL http://<mydistribution>.cloudfront.net
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 183 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
0 3630 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0HTTP/1.1
301 Moved Permanently
Server: CloudFront
....
HTTP/2 200

More About Secret Rotation

In the original CloudFormation stack, secret manager secret rotation is enabled, and integrated with Lambda to update the custom header value in CloudFront and WAF.

You may add following python code to lambda to update origin ALB listener rule with rotated secret, also add another environment variable to lambda configuration to pass over rule ARN.

def update_alb_rule(NewSecret, PrevSecret):
client = boto3.client('elbv2')
logger.info("Update alb rule, %s." % RuleArn)
response = client.modify_rule(
RuleArn = RuleArn,
Conditions=[
{
'Field': 'http-header',
'HttpHeaderConfig': {
'HttpHeaderName': HeaderName,
'Values': [NewSecret, PrevSecret]
}
}
]
)

You may find revised complete lambda code from here:
https://gist.github.com/Ronnie-personal/eaa1fd7a0981f8ad59df1a04db835e3e#file-lambda_function-py

Submit Rotation

To test rotation, you can either wait for the scheduled internal or manually start the rotation immediately, lambda send log to CloudWatch log group, in case any error, lookup the log and do the troubleshooting.

There are two tips, firstly the original stack does not grant lambda to update ALB listener rule, so you need to update the service role.

Secondly when rotation failed in between, you will need to clean up before try another rotation.

#run this command to get versionId for "AWSPENDING"
aws secretsmanager list-secret-version-ids --secret-id <id>
#delete pending version
aws secretsmanager update-secret-version-stage --secret-id <id> --remove-from-version-id <version id> --version-stage AWSPENDING

Conclusion

You may consider from operation, granular control and security perspective to determine the best feature for your use case.

Custom request header is considered as a secret between your CDN and origin, you may want to rotate it periodically. If you need to keep the secret at each CloudFront level, it might make more sense to use CloudFront to insert custom request header.

If you have a list of common WAF rules, you wouldn’t want to insert header through them, because you don’t want all your origin and CDN use the same header secret value.

If you want to add custom request header via WAF, you have to determine from which rule you add the header action, that will be tricky, or you add another standalone WebACL for just custom request header purpose.

If you have multiple users and multiple CloudFront, most likely they will manage their own header value separately.

Use another WAF rule to validate custom request header might not be the best approach, I think at least it will charge you more, if you already have ALB, why not just add another ALB listener rule to validate the header?

Unless you have WAF already added to ALB for other purpose, you wouldn’t want to add complexity to introduce another component.

At the end of lab, don’t forget to delete the CloudFormation stack to cleanup all resources to avoid further charges.

References

AWS::ElasticLoadBalancingV2::ListenerRule — AWS CloudFormation (amazon.com)

Implementation Of Custom-header to Origin requests (halodoc.io)

How to enhance Amazon CloudFront origin security with AWS WAF and AWS Secrets Manager | AWS Security Blog

Custom request header insertions for allow and count actions — AWS WAF, AWS Firewall Manager, and AWS Shield Advanced (amazon.com)

Listener rules for your Application Load Balancer — Elastic Load Balancing (amazon.com)

GitHub — aws-samples/amazon-cloudfront-waf-secretsmanager: Enhance Amazon CloudFront Origin Security with AWS WAF and AWS Secrets Manager

How to Generate a CloudFormation Template in AWS CDK | bobbyhadz

GitHub — iann0036/former2: Generate CloudFormation / Terraform / Troposphere templates from your existing AWS resources.

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html#ElasticLoadBalancingv2.Client.modify_rule

https://github.com/aws/aws-sdk-js/issues/2658

--

--

Cloud Journey

All blogs are strictly personal and do not reflect the views of my employer. https://github.com/Ronnie-personal