Terraforming my Cloudflare configuration
A couple of weeks ago I was having a conversation with a friend regarding Cloudflare. He was saying how it is not uncommon for people to make configuration changes using the Cloudflare UI potentially introducing breaking changes that could go unnoticed. I meantioned that with Infrastructure-as-Code implemented there is no excuse for this to happen. I also came to a realisation that I do not practice what I preached so to rectify this I have decided to capture my Cloudflare configuration into my Infrastructure-as-Code tool of choice Terraform.
Cloudflare handles a number of things for me on this domain but most notably it handles my DNS records for this website and my emails. An outages of either of these would not be ideal for me so to avoid this we are going to use cf-terraforming to generate Terraform code based on my current DNS configuration in the Cloudflare UI and to generate the terraform import commands so I can import my Cloudflare configuration into my Terraform state.
Building the application
Unfortunately cf-terraforming is not packaged in the Debian repositories and there is no flatpak or snap availible for it so we do have to resort to building it. If you’re following along on a Macbook there is a brew package for this which would save some time. Fortunately building this was incredibly straigtforward:
git clone https://github.com/cloudflare/cf-terraforming.git
cd cf-terraforming
make build
There may be some dependancies that need to be installed such as the Go programming language but I already had this on my laptop. Once the make command has successfully ran a binary file will appear in the same directory called cf-terraforming. I was able to interact with this binary in the terminal by running ./cf-terraforming
Create Cloudflare API token with sufficiant permissions
I needed to create a Cloudflare API token so cf-terraforming and Terraform can interact with Cloudflare. To do this I navigate to the Cloudflare API Token page, Select create token and then create custom token. Give the token sufficiant privalages to do what I needed it to do. For the purpose of DNS I selected Zone DNS Edit from the permissions dropdown and under the Zone Resource dropdown I selected Include Specific Zone samtripp.co.uk. I then followed the rest of the wizard to create my API Token.
Once this was created I was able to export my API Token along with my zone id which can be found on the Cloudflare dashboard onto my laptop as an environment varible in my terminal
export CLOUDFLARE_API_TOKEN='YOURAPITOKEN'
export CLOUDFLARE_ZONE_ID='YOURZONEID'
Initialise Terraform working directory with Cloudflare module present
To start using the cf-terraforming tool I need to add the Cloudflare provider to my terraform code. I created a cloudflare.tf file and added the following
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
Once this was added I ran a terraform init to initialise the terraform working directory.
Generating the Cloudflare terraform code
This is where the magic happens. I was able to generate the Cloudflare terraform code from the terraform working directory by running the following command:
cf-terraforming generate --resource-type "cloudflare_record" --zone $CLOUDFLARE_ZONE_ID
This outputted:
resource "cloudflare_record" "terraform_managed_resource_05e4f175dfbb21e818e9ff4e30e75796" {
name = "samtripp.co.uk"
proxied = true
ttl = 1
type = "CNAME"
value = "dnsrecordvalue.co.uk"
zone_id = "myzoneid"
}
...
Perfect that worked as expected. I copied the output into a new file called cloudflare-dns.tf. I ran a terraform plan and saw that terraform wants to create the infrastructure. I need to import this resource into the terraform state to avoid destroying anything already configured. To do this I ran the following command:
cf-terraforming import --resource-type "cloudflare_record" --zone $CLOUDFLARE_ZONE_ID
This outputted:
terraform import cloudflare_record.terraform_managed_resource_05e4f175dfbb21e818e9ff4e30e75796 myzoneid/05e4f175dfbb21e818e9ff4e30e75796
This command would be sufficiant to import the Cloudflare DNS record into terraform state. One thing I was not happy about was the names of these resources. As this was not in state yet I was able to manually edit the resource name to something more meaningful.
resource "cloudflare_record" "cname_root" {
...
}
Once renamed I ran the terraform import command with the ammended name
terraform import cloudflare_record.cname_root myzoneid/05e4f175dfbb21e818e9ff4e30e75796
The Cloudflare DNS record is now in state. I was able to run a terraform plan and see there are no changes to be made to my infrastructure. If you have multiple DNS records like I did you may want to run import and then plan each time to ensure no issues. You will see the number of changes to be made decrease after each successful import.
Add Cloudflare API key to Github Actions
Github Actions provides me with my all my Continuious Intergration and Continuous Delivery needs. I use dflook/terraform-github-actions to give me the functionality of running a terraform plan and apply when I make infrastructure changes. For Github Actions to interact with Cloudflare via Terraform I will need to add my API key to Github Actions. I managed this by navigating to my Github repository, select Settings > Secrets and Varibles > Actions > New Repository Secret. I named the secret CLOUDFLARE_API_TOKEN and then added the API token that I generated earlier. Once thats done I added the following lines to my Github actions .yaml file.
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Conclusion
The cf-terraforming tool was straightforward to use and made the process of importing some of my Cloudflare configuration into Terraform a pain-free experience. Credit to all the developers involved in making this tool possible. I have also been able to import my page rules successfully following the same process as above. I will continued to use this tool to capure the rest of my Cloudflare configuration into Infrastructure-as-Code.
It does not support every resource type in the Cloudflare provider currently but it at least covers some of the critical ones that could be disruptive without being able to import it into my Terraform state.