Protecting Resources (CloudFront) with WAF Rate-Based Rules
This is one of the AWS SOA topics related to networking and content delivery.
The WAF Web ACL allows you to define a variety of rules, one of which is a rate-based rule.
A rate-based rule tracks the rate of requests for each originating IP address, and triggers the rule action on IPs with rates that go over a limit. You set the limit as the number of requests per 5-minute time span.
Rate-based rule statement
By creating rate-based rules, you can protect your resources from DoS attacks.
In this case, CloudFront is the target for applying WAF.
However, other resources (ALB, API Gateway, AppSync) can also be configured in the same way.
Environment
Create a WAF Web ACL.
Create rate-based rules.
Apply the created Web ACL to CloudFront.
Create a CloudFront distribution.
Specify an S3 bucket as the origin.
Create an OAI in CloudFront and restrict the S3 bucket policy to allow access only from the OAI.
Create a Lambda function.
Configure this function as a CloudFormation custom resource.
The function’s function is to automatically create and delete objects in the bucket when creating and deleting stacks.
The object to be created is index.html for static website hosting.
The runtime for the function is Python 3.8.
Create a VPC and prepare two subnets.
One is a private subnet, and an EC2 instance is placed inside.
The other is a public subnet, and a NAT gateway is deployed.
The instance will be created on the latest version of Amazon Linux 2 and used as a client to access the CloudFront distribution.
Apache Bench will be used to test rate-based rules.
CloudFormation template files
The above configuration is built with CloudFormation.
The CloudFormation templates are located at the following URL
https://github.com/awstut-an-r/awstut-soa/tree/main/05/002
Explanation of key points of template files
This page focuses on how to define rate-based rules for WAF Web ACLs.
For more information on WAF basics, please refer to the following page
For information on how to apply WAF to CloudFront, please refer to the following page
For information on custom resources in CloudFormation, please refer to the following page
Rate-based WAF Web ACL Rules
Resources:
RuleGroup:
Type: AWS::WAFv2::RuleGroup
Properties:
Capacity: 10
Name: !Sub "${Prefix}-RateRestrictionRuleGroup"
Rules:
- Action:
Block: {}
Name: !Sub "${Prefix}-RateRestrictionRule"
Priority: 0
Statement:
RateBasedStatement:
AggregateKeyType: IP
Limit: !Ref Limit
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${Prefix}-RateRestrictionRule"
SampledRequestsEnabled: true
Scope: CLOUDFRONT
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${Prefix}-RateRestrictionRuleGroup"
SampledRequestsEnabled: true
Code language: YAML (yaml)
This time we define a rate-based rule in the rule group.
The key point is the RateBasedStatement property.
Use this property when creating a rate-based rule.
The AggregateKeyType property sets the target for counting requests.
Specify “IP” to count based on the IP address of the request originator, or “FORWARDED_IP” to count based on the IP address included in the HTTP header.
In this case, the former is selected.
Set the upper limit of the count with the “Limit” property.
Set the minimum value of this property to “100”.
In other words, if the number of requests from a specific IP address exceeds 100 during a 5-minute period, access from that IP address will be blocked.
(Reference) EC2 instance for verification
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref InstanceProfile
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
NetworkInterfaces:
- DeviceIndex: 0
SubnetId: !Ref PrivateSubnet
GroupSet:
- !Ref InstanceSecurityGroup
UserData: !Base64 |
#!/bin/bash -xe
yum update -y
yum install -y httpd
Code language: YAML (yaml)
No special configuration is required.
Define the initialization process for the instance in the user data.
By installing Apache, Apache Bench will also be installed together.
For more information on the instance initialization process, please refer to the following page
Architecting
Use CloudFormation to build this environment and check the actual behavior.
Create CloudFormation stacks and check resources in stacks
Create CloudFormation stacks.
For information on how to create stacks and check each stack, please refer to the following page
This time, be careful which region you create the stack in.
When creating a stack with the AWS CLI, specify the us-east-1 region in the region option as shown in the command below.
This is because in order to apply WAF to CloudFront, WAF must be created in the Virginia region.
$ aws cloudformation create-stack \
--stack-name soa-05-002 \
--template-url https://[bucket-name].s3.[region].amazonaws.com/[folder-name]/soa-05-002.yaml \
--capabilities CAPABILITY_IAM \
--region us-east-1
Code language: Bash (bash)
After checking the resources in each stack, information on the main resources created in this case is as follows
- S3 bucket: soa-05-002
- CloudFront distribution domain: https://d1suf56e055zgg.cloudfront.net
- Name of WAF Web ACL: soa-05-002-WebACL
- ID of WAF Web ACL: 2cec36bc-ec03-4e00-b70a-aab7cb984c7a
- Name of the rule in the WAF Web ACL: soa-05-002-WebACL-RateRestriction
- Name of the WAF rule group: soa-05-002-RateRestrictionRuleGroup
- Name of the rule created in the WAF rule group: soa-05-002-RateRestrictionRule
- ID of the EC2 instance: i-0e5e54ddc051b5f55
- EIP attached to the NAT gateway: 3.233.183.144
We also check the resources from the AWS Management Console.
First is CloudFront.
We can see that a CloudFront distribution has been created and AWS WAF has been applied.
Next, check the Web ACL.
From the Web ACL side, we can also see that this ACL has been applied to CloudFront.
Next, check the rules created in the Web ACL.
The rules are defined in the form of references to rule groups.
We also check the contents of the rule.
It is a rate-based rule, counting requests based on IP addresses, and the limit is set to 100.
Checking Action
Now that everything is ready, access the EC2 instance that is the client for verification.
Use SSM Session Manager for access.
% aws ssm start-session \
--target i-0e5e54ddc051b5f55 \
--region us-east-1
Starting session with SessionId: root-09545bd848d2d2fae
sh-4.2$
Code language: Bash (bash)
For more information on SSM Session Manager, please visit
Access the CloudFront distribution in this situation.
sh-4.2$ curl https://d1suf56e055zgg.cloudfront.net
<html>
<head></head>
<body>
<h1>index.html</h1>
<p>soa-05-002</p>
</body>
</html>
Code language: Bash (bash)
The index.html placed in the S3 bucket, which is the origin of CloudFront, is returned.
Check the status of WAF at this time.
Requests from EIP attached to the NAT gateway are allowed.
At this point, there are no restrictions enabled by rate-based rules, which means that access is allowed as usual.
Next, use Apache Bench to reproduce the spike in the number of requests.
sh-4.2$ ab -n 100 https://d1suf56e055zgg.cloudfront.net/
Code language: JavaScript (javascript)
Perform 100 requests to the CloudFront distribution URL.
Check the status of the WAF.
You can see that it is receiving a large number of requests from the EIP attached to the NAT gateway.
This means that the rate-based rule limit has been reached.
Access CloudFront again.
sh-4.2$ curl https://d1suf56e055zgg.cloudfront.net/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: 1ULqdHi16O_YVBoiAcGSy6_cc8_Lhw7TyWlxsJ0OEEk5Tb7CTRsrNA==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>
Code language: Bash (bash)
This time an error page is returned.
We also check the WAF.
This time we see that it was blocked.
The rate-based rule means that the EIP attached to the NAT gateway has blocked the request.
You can check the blocked IP addresses from the AWS CLI.
$ aws wafv2 get-rate-based-statement-managed-keys \
--scope CLOUDFRONT \
--region us-east-1 \
--web-acl-name soa-05-002-WebACL \
--web-acl-id 2cec36bc-ec03-4e00-b70a-aab7cb984c7a \
--rule-group-rule-name soa-05-002-WebACL-RateRestriction \
--rule-name soa-05-002-RateRestrictionRule
{
"ManagedKeysIPV4": {
"IPAddressVersion": "IPV4",
"Addresses": [
"3.233.183.144/32"
]
},
"ManagedKeysIPV6": {
"IPAddressVersion": "IPV6",
"Addresses": []
}
}
Code language: Bash (bash)
Blocked IP addresses can be checked from the AWS CLI.
https://docs.aws.amazon.com/waf/latest/developerguide/listing-managed-ips.html
The details are detailed in the following page, there are three key points.
https://docs.aws.amazon.com/cli/latest/reference/wafv2/get-rate-based-statement-managed-keys.html
The first is the region option.
If “CLOUDFRONT” is specified for the scope option, the region option must also be specified and its value must be “us-east-1”.
The second is the rule-group-rule-name option.
This option is mandatory when using rule groups to create Web ACLs.
The value of this option specifies the rule name defined within the WAF Web ACL.
The third is the rule-name option.
If you are using rule groups, the value of this option specifies the rule name as defined within the rule group.
Rate-based groups are actioned based on the number of requests per 5 minutes.
So allow some time and check the list of blocked IP addresses.
$ aws wafv2 get-rate-based-statement-managed-keys \
--scope CLOUDFRONT \
--region us-east-1 \
--web-acl-name soa-05-002-WebACL \
--web-acl-id 2cec36bc-ec03-4e00-b70a-aab7cb984c7a \
--rule-group-rule-name soa-05-002-WebACL-RateRestriction \
--rule-name soa-05-002-RateRestrictionRule
{
"ManagedKeysIPV4": {
"IPAddressVersion": "IPV4",
"Addresses": []
},
"ManagedKeysIPV6": {
"IPAddressVersion": "IPV6",
"Addresses": []
}
}
Code language: Bash (bash)
EIP has disappeared.
This means that the EIP is no longer subject to rate-based rules.
Finally, access CloudFront again.
sh-4.2$ curl https://d1suf56e055zgg.cloudfront.net
<html>
<head></head>
<body>
<h1>index.html</h1>
<p>soa-05-002</p>
</body>
</html>
Code language: Bash (bash)
index.hml was returned.
This means that the restriction by the rate-based rule has been lifted and the file is now accessible again.
Summary
We have reviewed how to create rate-based rules in the WAF Web ACL.