Maintaining a list of allowed CIDR blocks from Cloudflare requires effort. These lists do not change often but would it not be nicer to automate it for your Fortigate firewall? I think so, so here’s an example set of files and code to maintain the list of address
and address group
objects for both IPv4 and IPv6.
Let’s just dive in as this is relatively straight forward.
If you follow this guide verbatim you will end up with a list of addresses for both IPv4 and IPv6 (many) and 2 address groups. You can then use the new address groups to allow access through your firewall rules for ports for HTTP
and HTTPS
while blocking the greater internet from accessing same ports to your servers.
This requires:
- Terraform 0.13 or above (recommend 1.x or higher)
- Cloudflare provider: 3.0 or above
- FortiOS provider: 1.7 or above
Optional:
- FORTIOS_ACCESS_HOSTNAME environment variable set to your remote host
- FORTIOS_ACCESS_TOKEN environment variable set to your access token
We need to pull in the providers for both Cloudflare and FortiOS:
provider "fortios" { # better to use ENV variables than hard coding in your config (see above) # hostname = <remote name or IP> # token = <authorization token> # this allows you to connect without validating the TLS certificate insecure = true } terraform { required_providers { fortios = { source = "fortinetdev/fortios" version = "~> 1.7" } cloudflare = { source = "cloudflare/cloudflare" version = "~> 3.0" } } required_version = ">= 1.0" }
Next we need to use a data
call to Cloudflare to return the address list objects:
data "cloudflare_ip_ranges" "cloudflare_address_list" {}
Create resources for the addresses:
Note: using count
to iterate over the list of entries in the object(s) returned
resource "fortios_firewall_address" "cloudflare_ipv4_block" { count = length(data.cloudflare_ip_ranges.cloudflare_address_list.ipv4_cidr_blocks) name = "CloudFlare IPv4 ${count.index}" type = "ipmask" subnet = data.cloudflare_ip_ranges.cloudflare_address_list.ipv4_cidr_blocks[count.index] } resource "fortios_firewall_address6" "cloudflare_ipv6_block" { count = length(data.cloudflare_ip_ranges.cloudflare_address_list.ipv6_cidr_blocks) name = "CloudFlare IPv6 ${count.index}" type = "ipprefix" ip6 = data.cloudflare_ip_ranges.cloudflare_address_list.ipv6_cidr_blocks[count.index] }
Create resources for the address groups:
Note: using for_each
to iterate over the list of entries in the resources created
resource "fortios_firewall_addrgrp" "cloudflare_ipv4_group" { name = "CloudFlare IPv4 Group" dynamic "member" { for_each = fortios_firewall_address.cloudflare_ipv4_block[*].name content { name = member.value } } depends_on = [ fortios_firewall_address.cloudflare_ipv4_block ] } resource "fortios_firewall_addrgrp6" "cloudflare_ipv6_group" { name = "CloudFlare IPv6 Group" dynamic "member" { for_each = fortios_firewall_address6.cloudflare_ipv6_block[*].name content { name = member.value } } depends_on = [ fortios_firewall_address6.cloudflare_ipv6_block ] }
And we’re done!
If you look at your Fortigate firewall you should see additional address
objects for both IPv4 and IPv6 subnets and 2 new access groups
that you can use in your firewall rules.
Going forward, as Cloudflare adds and/or removes addresses, you can just run Terraform again to keep that list updated instead of doing a lot of copy and paste of entries and manual upkeep of the address group(s).
If you are a fan of all terraform definitions in one file (I am not) you can download the combined file then rename it to have a .tf
extension.