In my previous post, I walked you through configuring AWS Route53 with DNSSEC and Terraform. Cloudflare, a SaaS security provider, provides a number of different services including web application firewall (WAF), DNS, load balancing, and zero trust. By utilizing Terraform with Cloudflare, you can automate a number of infrastructure services just like those offered at AWS.

What is Cloudflare

Cloudflare is a major security services provider. Known for their DNS and WAF services, they provide additional security services such as DDoS mitigation, zero trust, caching, and CDN. Cloudflare is also a domain registrar allowing customers to buy or transfer their domains to the service. Another benefit of using Cloudflare is their free TLS certificates. By utilizing their services, you can leverage their TLS certificates to ensure privacy and security of your site.

They provide these services by proxying web traffic. When configuring the service, you provide your FQDN and the origin’s IP address. Once configured, Cloudflare will advertise their IP address instead of the origin’s IP. This forces web traffic to run through their datacenters to scrub any malicious traffic before heading to your website.

Terraforming DNS Records

The Terraform syntax used for Route53 records are a little different than those used with Cloudflare but easy to use. First we create our zone record:

data "cloudflare_zone" "example_domain" {
  name = "example.com"
  account_id = "xxxxxxx"
}

Next we create the A record. The syntax below configures the origin IP address of 1.1.1.1 to point to example.com. To force the origin’s IP address to be proxied, set the value to true.

resource "cloudflare_record" "example_a_record" {
  zone_id = data.cloudflare_zone.example_domain.id
  name = "example.com"
  type = "A"
  ttl = "1"
  value = "1.1.1.1"
  proxied = true
}

Now create a CNAME record which points www to example.com

resource "cloudflare_record" "example_www_cname_record" {
  zone_id = data.cloudflare_zone.example_domain.id
  name = "www"
  type = "CNAME"
  value = "example.com"
  ttl = "1"
  proxied = true
}

To ensure the security of your website, you should have both the A and CNAME records proxied. This will prevent those trying to find your origin IP address. Further examples for TXT and MX records can be found in my GitLab repo.

Enhancing Security Through Terraform

Cloudflare also allows you to create firewall rules and configure security enhancements through Terraform. First, lets create the file for the security enhancements.

resource "cloudflare_zone_settings_override" "example_security" {
    zone_id = data.cloudflare_zone.example_domain.id
    settings {
        brotli = "on"
        ssl = "full"
        waf = "on"
        automatic_https_rewrites = "on"
        min_tls_version = "1.2"
        hotlink_protection = "on"
        http2 = "on"
        http3 = "on"
        mirage = "on"
        webp = "on"
        security_header {
            enabled = true
            nosniff = true
        }
    }
}

This syntax ensures that TLS v1.2 encryption is being used between the user and Cloudflare and then from Cloudflare to our origin server. It also provides protections for your images and prevents those who wish to hotlink images from your site to theirs.

Cloudflare Firewall Rules

Lastly, we need to configure firewall rules to enhance the security of our website. There are a number of ways to enhance the security of a WordPress website, those being

  • XML RPC attacks
  • Spamming your site with comments
  • Geographic IP blocks

To put these mitigations in place, we first need to create the firewall filters. To do so, create the following:

resource "cloudflare_filter" "example_geoip_filter" {
    zone_id = data.cloudflare_zone.example_domain.id
    description = "GeoIP Blocks"
    expression = "(ip.geoip.country eq \"RU\")"
}

resource "cloudflare_filter" "example_xmlrpc_filter" {
    zone_id = data.cloudflare_zone.example_domain.id
    description = "Block XML RPC"
    expression = "(http.request.uri.path contains \"/xmlrpc.php\")"
}

resource "cloudflare_filter" "example_direct_comments_filter" {
    zone_id = data.cloudflare_zone.example_domain.id
    description = "Block Direct Requests To WP Comments"
    expression = "(http.request.uri.path eq \"/wp-comments-post.php\" and http.request.method eq \"POST\" and http.referer ne \"example.com\")"
}

Next create the firewall rules

resource "cloudflare_firewall_rule" "example_geoip_rule" {
    zone_id = data.cloudflare_zone.example_domain.id
    description = "GeoIP Blocks"
    filter_id = cloudflare_filter.example_geoip_filter.id
    action = "block"
    priority = "1"
}

resource "cloudflare_firewall_rule" "example_xmlrpc_rule" {
    zone_id = data.cloudflare_zone.example_domain.id
    description = "Block XMLRPC"
    filter_id = cloudflare_filter.example_xmlrpc_filter.id
    action = "block"
    priority = "2"
}

resource "cloudflare_firewall_rule" "example_comments_rule" {
    zone_id = data.cloudflare_zone.example_domain.id
    description = "Block Direct Requests To WP Comments"
    filter_id = cloudflare_filter.example_direct_comments_filter.id
    action = "block"
    priority = "3"
}

The syntax for the rule set is straightforward. The “filter_id” is used to identify the filter rule created above. Action, what you would like to do with the filter which in this instance is “block.” “Priority” is where in the rule set this filter should be applied to. For instance, these rules go in order, “1”, “2”, “3.” This means the first rule is the GeoIP block, then XMLRPC, and then the comments filter. If you do not place the “priority” syntax in the stanza then Terraform will put the filters in whatever order it chooses.

Wrapping It Up

There are plenty of other Cloudflare configurations that can be scripted for Terraform. For the full list, check out the Cloudflare Provider Terraform documentation.