Terraforming Amazon’s Web Application Firewall

Tom Brice
Kudos Engineering
Published in
3 min readJul 3, 2018
by Lisa Walton on Unsplash

Known to our team as ‘The Woff’ (like a knock-off version of ‘The Hoff’, a mispronunciation of it’s acronym), Amazon’s Web Application Firewall (WAF) is by AWS standards very quick and simple to set up.

The firewall is structured as so:

  • You create specific conditions to be run against an incoming request. Such as a string match for a user agent, an IP match, or for the presence of dodgy SQL.
  • You create rules based off of a singular, or multiple, conditions. Standard can be configured to block or allow requests matching it’s conditions. Rate-based rules block or allow requests matching it’s conditions, based on the amount of matching requests received in a five minute period.
  • Your rules are compiled into an access-control list (ACL), which you attach to either Cloudfront, or a load balancer.

In our case, we wanted to use the WAF prevent the consumption of excess resources due to a high volume of bot traffic coming to a specific endpoint.

The WAF interface provides a wizard which does make setup quite quick and easy, but we decided to use Terraform to be consistent with the rest of our infrastructure.

We are going to set up a simple ACL, that has one rule, consisting of two conditions.

To begin with we need to specify the provider for Terraform to use:

NOTE: Any values like ${var.something} are using variables listed in a variables.tf file. The syntax for the variables in this file is like so:

It’s often conventional with Terraform to start with the granular resources first, and build up to the the larger pieces of infrastructure.The most granular resources we will be working with are the WAF conditions.

We are trying to match bot traffic directed at a specific endpoint, so we are going to create one condition to match the string ‘bot’ in the user-agent, and another condition to match part of the route of the URI for that endpoint.

NOTE: The WAF interface ‘String and Regex matching’ condition is the same as aws_wafregional_byte_match_set.

The positional_constraint value here will be where in the request we want to look for the target_string. We have used CONTAINS, but some other options are CONTAINS_WORD ,EXACTLY, or STARTS_WITH / ENDS_WITH .

Now we have these two conditions, we want to create one rule which uses them.

The predicate’s are the conditions that we want this rule to consist of, which we link to our two conditions using the data_id.

The negated field, if set to true, is a way of stating that you want to block, allow, or count, all other requests than the one that the condition matches. For our case we want to block, allow, or count, the request itself that matches the condition.

In essence our rule is creating an ‘AND’ of the two conditions, stating:

Match a request if it contains: ‘bot’ in the user-agent AND ‘example_route’ in the URI.

The action we then decide to take on these matches requests is defined in our ACL:

We have both a default here, and an actionfor the rule that we attach to the ACL. In most cases these will be opposite things, such as here, where we decide to make the firewall’s default action to allow requests through, unless they match the rule, in which case they are blocked.

The final thing for us to do is to actually attach this ACL to either Cloudfront, or an ALB, with an acl_associationresource, like so:

If you already have Cloudfront or an ALB in place, but you don’t have it terraformed, you can hard code in the resource_arnwithout pointing it to the value of another resourced, which is what we are doing here.

One final gotcha’: If you have put the WAF on an ALB, when you go to view the ACL in the interface, it will by default be filtering for Global (Cloudfront). Click the dropdown and select the region of your ALB and you should see the ACL there.

If you like what you read and think you could contribute to our team at Kudos then take a look at our careers page to see what roles we currently have open.

--

--