Deprecated Nov 2024 -> See AWS::WAF::Rule
The AWS Web Application Firewall (WAF) is a managed firewall service that supports a large variety of protection options in the form of rules. These rules should be linked to web access control lists (ACLs) which in turn are attached to an Amazon Cloudfront or Application Load Balancer.
IMPORTANT: It is currently not possible to attach rate-based rules to a Web ACL using CloudFormation. Until this is possible this project is on hold.
Note: This provider was created for and is currently only tested for the CloudFront variant, the regional variant should also work for basic usage.
Rate-based Rules are a new type of rule that can be configured for the AWS WAF. This feature allows you to specify the number of web requests that are allowed by a client IP in a trailing, continuously updated, 5 minute period. If an IP address breaches the configured limit, new requests will be blocked until the request rate falls below the configured threshold. Even better is the fact that by default the rate-based rule will track and manage the 5-minute window for each IP address for you.
While most WAF resources can be deployed using CloudFormation, rate-based rules are not supported and are currently only creatable using the AWS Console or with API calls. As we consider it a best practice to deploy infrastructure as code we want to have this functionality available to us in CloudFormation. This custom resource provider does exactly that!
Creating a rate-based rule without any predicates does not have any prerequisites, however if you want to attach any predicates to the rule they have to be created before they can be attached to the rule.
IMPORTANT: Due to the behavior of the WAF API actions only one rule should be in creation at a time, see
here. The current workaround is to make each rate-based rule depend on the next using the
DependsOn:
CloudFormation statement.
- Custom::RateBasedRule
- AWS WAF Predicate(s) (Optional)
If you have deployed your desired predicates you can add the MatchPredicates section to rate-based rule's cloudformation template. MatchPredicates expects a list of predicate definitions such as the example below:
MatchPredicates:
-
Negated: False
Type: XssMatch
DataId: !Ref XssMatchSetPredicate
-
Negated: True
Type: IPMatch
DataId: !ImportValue IPSetPredicate
Note: The predicates must be created/exist before the custom resource is created.
Checkout the sample rules in the demo-stack.yaml for more information.
Type: Custom::RateBasedRule
Properties:
Name: string
MetricName: string
RateKey: string (Only valid value is IP)
RateLimit: integer (>=2000)
MatchPredicates:
-
Negated: True |False
Type: IPMatch | ByteMatch | SqlInjectionMatch | GeoMatch | SizeConstraint | XssMatch | RegexMatch
DataId: Predicate's physical ID
ServiceToken: Arn of the custom resource provider lambda function
To install this custom resource follow these steps:
-
Package the Lambda using docker and upload it to S3 using
make deploy
(See the makefile for more info) -
Deploy the cfn-waf-provider stack using
make deploy-provider
or type (remember to fill in your bucket name and key):
aws cloudformation create-stack \
--capabilities CAPABILITY_IAM \
--stack-name cfn-waf-provider \
--template-body file://cloudformation/cfn-waf-provider.yaml
--parameters \
ParameterKey=S3BucketPrefix,ParameterValue=INSERT_BUCKET_NAME_HERE \
ParameterKey=CFNCustomProviderZipFileName,ParameterValue=INSERT_BUCKET_KEY_HERE
aws cloudformation wait stack-create-complete --stack-name cfn-waf-provider
To try out the custom resource type the following to deploy the demo:
aws cloudformation create-stack --stack-name cfn-waf-provider-predicates-demo \
--template-body file://cloudformation/demo-predicates-stack.yaml \
aws cloudformation wait stack-create-complete --stack-name cfn-waf-provider-predicates-demo
aws cloudformation create-stack --stack-name cfn-waf-provider-demo \
--template-body file://cloudformation/demo-stack.yaml \
aws cloudformation wait stack-create-complete --stack-name cfn-waf-provider-demo
This will deploy a cfn-waf-provider-predicates-demo stack containing several example predicates and a cfn-waf-provider-demo stack containing some examples of rate-based rules. Some with predicates and some without.
In order to create, update, or delete a rule we have to request a ChangeToken from WAF. We send this token with such a request, which allows the WAF to prevent conflicting requests from being submitted. However, once a change token is requested using the getChangeToken action it continues to return the same token until it is actually used in a create, update or delete request. For the custom provider this means that if at least two resources are created simultaneously they can potentially receive the same change token. The first to use the token succeeds while the others will receive a stale token error.
The current behavior means that we have to either force sequential create, update and delete requests for rate-based
rules using the DependsOn:
CloudFormation statement or configure the lambda to have max concurrency one.
This does come with the downside that rule creation plus an update to add predicates can take up to 10 minutes per rule.
Another option is to request a new token whenever a conflict occurs, however, I do not like the idea of hoping that these conflicts don’t cascade to the point of “too many request timeouts” on the API. Lastly, one could also attempt to force synchronization during the token request and it being used, however, that personally seems like a poor design choice.