I enjoy sharing content on the Internet using this WordPress blog as a vehicle. With that joy comes the responsibility of protecting the site against attackers to avoid causing harm to those that visit. I have used Cloudflare to assist with the security of my website for the past 7 years and am a big fan of their tools and extensible set of APIs.
One of the easiest and most powerful tools is the Cloudflare firewall. This can be used to block access to the WordPress admin console based on filters such as the public IP address of the visitor. In this post, I show how to build a Cloudflare firewall rule to filter malicious access requests to the admin console using the web UI and Terraform code.
Contents
Basic Firewall Rule Configuration
I’ll start by creating a firewall rule by hand as a basic configuration example. I’ve navigated to my account > Firewall > Firewall Rules and selected Create a Firewall Rule to show the example below:

This rule is triggered when a visitor requests the WordPress admin panel, located at /wp-admin
in a default installation, using a public IP address other than mine. Traffic matching this rule is blocked. If my public IP address changes, I can re-visit the firewall rule and update the configuration as required.
This approach works for an ad-hoc configuration but does not scale well and requires more operational overhead than I would prefer. Time to automate!
Firewall Rules as Code
I’ve put together an infrastructure as code approach to the Cloudflare firewall rule using Terraform. The example below is split into four objects: the Terraform Cloudflare provider, zone data, a filter, and a firewall rule.
I’ve taken apart the code in the sections below.
Cloudflare Provider
The provider requires an API token in order to authenticate. I suggest reading the Managing API Tokens and Keys documentation prior to making a token. For this example, the API token will need:
- Zone.Zone Settings – Read
- Zone.Zone – Read
- Zone.Firewall Services – Edit
Zone Data
Next is the zone data. This provides the zone id
value needed for further configuration. I could have statically defined the zone id
value the configuration, but prefer to pull id
values dynamically from the actual deployment.
Filter Resource
The third object is the filter resource. This is an API-only resource that defines the filter expression used by other objects, such as firewall rules. Note that I’ve supplied zones[0]
to the zone_id
key. This is because the zone data pulled earlier returns an array of zones stored in zones
regardless of the quantity of zones returned.
I’ve shared the raw schema below:
"instances": [
{
"schema_version": 0,
"attributes": {
"filter": [
{
"name": "wahlnetwork.com",
"paused": false,
"status": "active"
}
],
"id": "2020-06-22 20:10:15.651915722 +0000 UTC",
"zones": [
{
"id": "111111111111111111111111111111111111",
"name": "wahlnetwork.com"
}
]
}
}
]
I feel safe with this code construction because there’s only a single zone named wahlnetwork.com
and I don’t see why I would add more.
Firewall Rule Resource
The fourth and final object is the firewall rule resource. I’ve supplied all of the earlier information to construct a firewall rule: the zone_id
from the zone data along with a description
and filter_id
from the filter resource. The block action
supplies the final piece needed to generate a firewall rule. Additionally, I’ve embedded earlier values in this resource to ensure that the Terraform graph maps the dependencies between these objects.
In a real-world deployment, I would suggest placing the api_token
value into a secret stored in the repository or elsewhere such as Vault. The secret value can be passed along as an environmental variable to the Terraform CLI using something like -var 'api_token=${api_token}'
in most cases.
Next Steps
Please accept a crisp high five for reaching this point in the post!
If you’d like to learn more about Infrastructure as Code, or other modern technology approaches, head over to the Guided Learning page.