diff --git a/.github/workflows/apply.yaml b/.github/workflows/apply.yaml index 19bc7bf..dce569e 100644 --- a/.github/workflows/apply.yaml +++ b/.github/workflows/apply.yaml @@ -35,6 +35,7 @@ jobs: name: main env: NIX_CACHE_DIR: /home/runner/.nixcache/ + TERRAFORM_BACKEND_CONFIG: /home/runner/config.s3.tfbackend TERRAFORM_CACHE_DIR: /home/runner/.terraformcache/ permissions: # Needed to checkout code @@ -73,6 +74,32 @@ jobs: nix-store --import < ${{ env.NIX_CACHE_DIR }}/archive.nar + - name: Create backend configuration + run: | + cat < ${{ env.TERRAFORM_BACKEND_CONFIG }} + access_key = "${{ secrets.TERRAFORM_BACKEND_ACCESS_KEY }}" + endpoints = { + s3 = "${{ secrets.TERRAFORM_BACKEND_ENDPOINT }}" + } + secret_key = "${{ secrets.TERRAFORM_BACKEND_SECRET_KEY }}" + EOF + - name: Initialize + env: + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + TF_PLUGIN_CACHE_DIR: ${{ env.TERRAFORM_CACHE_DIR }} + # yamllint disable rule:line-length + run: > + nix + develop + ./#terraform + --command + -- + task + init + -- + -input=false + -backend-config=${{ env.TERRAFORM_BACKEND_CONFIG }} + # yamllint enable rule:line-length - name: Apply env: SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} diff --git a/.github/workflows/plan.yaml b/.github/workflows/plan.yaml index 325e74f..04164de 100644 --- a/.github/workflows/plan.yaml +++ b/.github/workflows/plan.yaml @@ -29,6 +29,7 @@ jobs: name: main env: NIX_CACHE_DIR: /home/runner/.nixcache/ + TERRAFORM_BACKEND_CONFIG: /home/runner/config.s3.tfbackend TERRAFORM_CACHE_DIR: /home/runner/.terraformcache/ permissions: # Needed to checkout code @@ -67,6 +68,32 @@ jobs: nix-store --import < ${{ env.NIX_CACHE_DIR }}/archive.nar + - name: Create backend configuration + run: | + cat < ${{ env.TERRAFORM_BACKEND_CONFIG }} + access_key = "${{ secrets.TERRAFORM_BACKEND_ACCESS_KEY }}" + endpoints = { + s3 = "${{ secrets.TERRAFORM_BACKEND_ENDPOINT }}" + } + secret_key = "${{ secrets.TERRAFORM_BACKEND_SECRET_KEY }}" + EOF + - name: Initialize + env: + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + TF_PLUGIN_CACHE_DIR: ${{ env.TERRAFORM_CACHE_DIR }} + # yamllint disable rule:line-length + run: > + nix + develop + ./#terraform + --command + -- + task + init + -- + -input=false + -backend-config=${{ env.TERRAFORM_BACKEND_CONFIG }} + # yamllint enable rule:line-length - name: Plan env: SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} diff --git a/.gitignore b/.gitignore index cffd74a..1bc78ec 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ # Terraform state *.tfstate *.tfstate.* + +# Terraform backend configuration +*.tfbackend diff --git a/.vscode/settings.json b/.vscode/settings.json index 0967ef4..afe949a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,8 @@ -{} +{ + "[terraform]": { + "editor.tabSize": 2 + }, + "[terraform-vars]": { + "editor.tabSize": 2 + } +} diff --git a/Taskfile.dist.yaml b/Taskfile.dist.yaml index 513c31d..d415436 100644 --- a/Taskfile.dist.yaml +++ b/Taskfile.dist.yaml @@ -137,21 +137,10 @@ tasks: -chdir=src/ init {{ .CLI_ARGS }} - init-internal: - desc: Initialize Terraform quietly - internal: true - env: - TF_IN_AUTOMATION: "true" - cmds: - - task: init - vars: - CLI_ARGS: > - -input=false plan: desc: Create an execution plan interactive: true cmds: - - task: init-internal - > terraform -chdir=src/ @@ -161,7 +150,6 @@ tasks: desc: Apply changes interactive: true cmds: - - task: init-internal - > terraform -chdir=src/ diff --git a/src/.terraform.lock.hcl b/src/.terraform.lock.hcl new file mode 100644 index 0000000..373a37f --- /dev/null +++ b/src/.terraform.lock.hcl @@ -0,0 +1,40 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/carlpett/sops" { + version = "1.0.0" + constraints = "~> 1.0" + hashes = [ + "h1:tnN2Mgl0NUF3cg7a0HtGmtOhHcG+tkaT6ncOPRuA9l8=", + "zh:064e63ea800cd1a8e575064097bc7de6fd5faa8ad50dbb3f2f9d8a3ebc9d7b97", + "zh:0663900085949d2faf24c170c7cdfbf76e545797915cc331da8304144c02bf27", + "zh:2ff26c7e5ee356c30791a12dd8e114c6237bd873d09e52805cb30dd5d758ed23", + "zh:44211fa474112ad0c9fcdae03f13ec7c75cdefd3ab29979b99cb834208055593", + "zh:6c3ab441c12b9679ad1dcac580d1ee7782f0d94efe6da6e983435ed39335cd3f", + "zh:8924cc939b52382ef042dc38bde93cdf438ff0aeab5e1801fbd198f05b80cd47", + "zh:ebc189ce22c23b903399f71e33d465001a79d7de7f7bf115c7763fcf794f4b58", + ] +} + +provider "registry.terraform.io/cloudflare/cloudflare" { + version = "4.25.0" + constraints = "~> 4.0" + hashes = [ + "h1:vbWDpJEWXnMqD2wfV4F+KODUduDgBH5dxCApwWfiSvM=", + "zh:0f625b34ce28e924124bbb42dc3f54fd0c5ab66a27a0b7fb38b2009bed1390a6", + "zh:2ce2db996077378358275900cc45c11836b3de7be85c262c33d74d01ae000e44", + "zh:323bac0be11f2ddbcc827ffa3d71895b4edeb4a62f6246dbf2aeb69bb30ca9ca", + "zh:364b12f04f3acc6cd79468214c3f4dc080317f0177fa6bc7f01b7131f8f33b8e", + "zh:38c664cbaf8dffd2bc76cfa478fbab0547f3e15e36e79eba2159b3fdbcc67999", + "zh:3fd7100ea5b362ff66ee3599a102bfdc0c02b21e4a1bbe5203cdd4cf37382502", + "zh:421496d7ccd63001b9397c604accd2f55c51bc8ffcc01b38704f48a6b95f35e0", + "zh:5d27baeb39003309759cf70ff70c1db4dc5c751ecd62f0158549628331a3ec69", + "zh:6a4fa0eb3017bbc688bc727fc53c5f8c2be017d3605645173f1acb3812ff91a1", + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", + "zh:9dcb12f5064eab93aecf7257c908cf6d578d7a18644c460f9f6a5fabe81df801", + "zh:9e5e7eef1259ff30b2b715b149c94de4af4137bdb675767ead11715d66e2284e", + "zh:9f16a636c9703f990a16c30f9dde4c5146895fe05da5a93d3bfcbd8cc30bff47", + "zh:a49f83b2b783a1ac17b187b00d30578e4d0871c6cd53eb3245bc1f23893da4a5", + "zh:ebe7bce25c3efc375a2c8af10e812e3dc14e6993d8f902a28f5ec5231005e730", + ] +} diff --git a/src/cloudflare/accounts.tf b/src/cloudflare/accounts.tf new file mode 100644 index 0000000..2621bb5 --- /dev/null +++ b/src/cloudflare/accounts.tf @@ -0,0 +1,22 @@ +# Add main account +resource "cloudflare_account" "main" { + # Name of the account + name = local.account.name +} + +# Add me as a member to the account +resource "cloudflare_account_member" "spietras" { + # Identifier of the account to add the member to + account_id = cloudflare_account.main.id + + # Email address of the member + email_address = local.account.members.spietras.email + + # Role IDs to assign to the member + role_ids = [ + local.roles["Super Administrator - All Privileges"], + ] + + # Member is accepted + status = "accepted" +} diff --git a/src/cloudflare/data.tf b/src/cloudflare/data.tf new file mode 100644 index 0000000..c5155cc --- /dev/null +++ b/src/cloudflare/data.tf @@ -0,0 +1,4 @@ +# Data source with account roles +data "cloudflare_account_roles" "account_roles" { + account_id = cloudflare_account.main.id +} diff --git a/src/cloudflare/dns.tf b/src/cloudflare/dns.tf new file mode 100644 index 0000000..ad76b1b --- /dev/null +++ b/src/cloudflare/dns.tf @@ -0,0 +1,578 @@ +# Add record to verify domain ownership for ProtonMail +resource "cloudflare_record" "protonmail_verification" { + # Add a comment to the record + comment = "This record is used to verify domain ownership for ProtonMail" + + # Use root domain + name = "@" + + # Don't proxy through Cloudflare + proxied = false + + # This is a TXT record + type = "TXT" + + # Value of the record + value = "protonmail-verification=${var.secrets.dns.protonmail.challenge}" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for mail server for ProtonMail (1) +resource "cloudflare_record" "protonmail_mail_1" { + # Add a comment to the record + comment = "This record is used to point to a mail server for ProtonMail" + + # Use root domain + name = "@" + + # Priority of the record + priority = 10 + + # Don't proxy through Cloudflare + proxied = false + + # This is an MX record + type = "MX" + + # Value of the record + value = "mail.protonmail.ch" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for mail server for ProtonMail (2) +resource "cloudflare_record" "protonmail_mail_2" { + # Add a comment to the record + comment = "This record is used to point to a mail server for ProtonMail" + + # Use root domain + name = "@" + + # Priority of the record + priority = 20 + + # Don't proxy through Cloudflare + proxied = false + + # This is an MX record + type = "MX" + + # Value of the record + value = "mailsec.protonmail.ch" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for SPF policy for ProtonMail +resource "cloudflare_record" "protonmail_spf" { + # Add a comment to the record + comment = "This record is used to define SPF policy for ProtonMail" + + # Use root domain + name = "@" + + # Don't proxy through Cloudflare + proxied = false + + # This is a TXT record + type = "TXT" + + # Value of the record + value = "v=spf1 include:_spf.protonmail.ch ~all" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for DKIM policy for ProtonMail (1) +resource "cloudflare_record" "protonmail_dkim_1" { + # Add a comment to the record + comment = "This record is used to define DKIM policy for ProtonMail" + + # Use this subdomain + name = "protonmail._domainkey" + + # Don't proxy through Cloudflare + proxied = false + + # This is a CNAME record + type = "CNAME" + + # Value of the record + value = "protonmail.${var.secrets.dns.protonmail.dkim.domain}" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for DKIM policy for ProtonMail (2) +resource "cloudflare_record" "protonmail_dkim_2" { + # Add a comment to the record + comment = "This record is used to define DKIM policy for ProtonMail" + + # Use this subdomain + name = "protonmail2._domainkey" + + # Don't proxy through Cloudflare + proxied = false + + # This is a CNAME record + type = "CNAME" + + # Value of the record + value = "protonmail2.${var.secrets.dns.protonmail.dkim.domain}" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for DKIM policy for ProtonMail (3) +resource "cloudflare_record" "protonmail_dkim_3" { + # Add a comment to the record + comment = "This record is used to define DKIM policy for ProtonMail" + + # Use this subdomain + name = "protonmail3._domainkey" + + # Don't proxy through Cloudflare + proxied = false + + # This is a CNAME record + type = "CNAME" + + # Value of the record + value = "protonmail3.${var.secrets.dns.protonmail.dkim.domain}" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for DMARC policy for ProtonMail +resource "cloudflare_record" "protonmail_dmarc" { + # Add a comment to the record + comment = "This record is used to define DMARC policy for ProtonMail" + + # Use this subdomain + name = "_dmarc" + + # Don't proxy through Cloudflare + proxied = false + + # This is a TXT record + type = "TXT" + + # Value of the record + value = "v=DMARC1; p=quarantine;" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record to verify domain ownership for SimpleLogin +resource "cloudflare_record" "simplelogin_verification" { + # Add a comment to the record + comment = "This record is used to verify domain ownership for SimpleLogin" + + # Use mail domain + name = "mail" + + # Don't proxy through Cloudflare + proxied = false + + # This is a TXT record + type = "TXT" + + # Value of the record + value = "sl-verification=${var.secrets.dns.simplelogin.challenge}" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for mail server for SimpleLogin (1) +resource "cloudflare_record" "simplelogin_mail_1" { + # Add a comment to the record + comment = "This record is used to point to a mail server for SimpleLogin" + + # Use mail domain + name = "mail" + + # Priority of the record + priority = 10 + + # Don't proxy through Cloudflare + proxied = false + + # This is an MX record + type = "MX" + + # Value of the record + value = "mx1.simplelogin.co" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for mail server for SimpleLogin (2) +resource "cloudflare_record" "simplelogin_mail_2" { + # Add a comment to the record + comment = "This record is used to point to a mail server for SimpleLogin" + + # Use mail domain + name = "mail" + + # Priority of the record + priority = 20 + + # Don't proxy through Cloudflare + proxied = false + + # This is an MX record + type = "MX" + + # Value of the record + value = "mx2.simplelogin.co" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for SPF policy for SimpleLogin +resource "cloudflare_record" "simplelogin_spf" { + # Add a comment to the record + comment = "This record is used to define SPF policy for SimpleLogin" + + # Use mail domain + name = "mail" + + # Don't proxy through Cloudflare + proxied = false + + # This is a TXT record + type = "TXT" + + # Value of the record + value = "v=spf1 include:simplelogin.co ~all" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for DKIM policy for SimpleLogin (1) +resource "cloudflare_record" "simplelogin_dkim_1" { + # Add a comment to the record + comment = "This record is used to define DKIM policy for SimpleLogin" + + # Use mail domain + name = "dkim._domainkey.mail" + + # Don't proxy through Cloudflare + proxied = false + + # This is a CNAME record + type = "CNAME" + + # Value of the record + value = "dkim._domainkey.simplelogin.co" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for DKIM policy for SimpleLogin (2) +resource "cloudflare_record" "simplelogin_dkim_2" { + # Add a comment to the record + comment = "This record is used to define DKIM policy for SimpleLogin" + + # Use mail domain + name = "dkim02._domainkey.mail" + + # Don't proxy through Cloudflare + proxied = false + + # This is a CNAME record + type = "CNAME" + + # Value of the record + value = "dkim02._domainkey.simplelogin.co" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for DKIM policy for SimpleLogin (3) +resource "cloudflare_record" "simplelogin_dkim_3" { + # Add a comment to the record + comment = "This record is used to define DKIM policy for SimpleLogin" + + # Use mail domain + name = "dkim03._domainkey.mail" + + # Don't proxy through Cloudflare + proxied = false + + # This is a CNAME record + type = "CNAME" + + # Value of the record + value = "dkim03._domainkey.simplelogin.co" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for DMARC policy for SimpleLogin +resource "cloudflare_record" "simplelogin_dmarc" { + # Add a comment to the record + comment = "This record is used to define DMARC policy for SimpleLogin" + + # Use mail domain + name = "_dmarc.mail" + + # Don't proxy through Cloudflare + proxied = false + + # This is a TXT record + type = "TXT" + + # Value of the record + value = "v=DMARC1; p=quarantine; pct=100; adkim=s; aspf=s;" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for GitHub Pages verification +resource "cloudflare_record" "github_pages_verification" { + # Add a comment to the record + comment = "This record is used to verify domain ownership for GitHub Pages" + + # Use this subdomain + name = "_github-pages-challenge-spietras" + + # Don't proxy through Cloudflare + proxied = false + + # This is a TXT record + type = "TXT" + + # Value of the record + value = var.secrets.dns.github.pages.challenge + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for root domain to point to Cloudflare +resource "cloudflare_record" "root" { + # Add a comment to the record + comment = "This record is used to point the root domain to Cloudflare" + + # Use root domain + name = "@" + + # Proxy through Cloudflare + proxied = true + + # This is an A record + type = "A" + + # This results in Cloudflare handling the traffic + value = "192.0.2.1" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for tunnel +resource "cloudflare_record" "tunnel" { + # Add a comment to the record + comment = "This record is used to point to a tunnel" + + # Use this subdomain + name = local.domains.subdomains.tunnel + + # Proxy through Cloudflare + proxied = true + + # This is a CNAME record + type = "CNAME" + + # Value of the record + value = cloudflare_tunnel.main.cname + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for Xenon in Tailscale +resource "cloudflare_record" "xenon" { + # Add a comment to the record + comment = "This record is used to point to Xenon in Tailscale" + + # Use this subdomain + name = "xenon.${local.domains.subdomains.tailscale}" + + # Don't proxy through Cloudflare + proxied = false + + # This is an A record + type = "A" + + # IP address of Xenon in Tailscale + value = "100.127.131.11" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for Xenon wildcard in Tailscale +resource "cloudflare_record" "xenon_wildcard" { + # Add a comment to the record + comment = "This record is used to point to Xenon wildcard in Tailscale" + + # Use this subdomain + name = "*.xenon.${local.domains.subdomains.tailscale}" + + # Don't proxy through Cloudflare + proxied = false + + # This is an A record + type = "A" + + # IP address of Xenon in Tailscale + value = "100.127.131.11" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for Oxygen in Tailscale +resource "cloudflare_record" "oxygen" { + # Add a comment to the record + comment = "This record is used to point to Oxygen in Tailscale" + + # Use this subdomain + name = "oxygen.${local.domains.subdomains.tailscale}" + + # Don't proxy through Cloudflare + proxied = false + + # This is an A record + type = "A" + + # IP address of Oxygen in Tailscale + value = "100.119.51.47" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for Oxygen wildcard in Tailscale +resource "cloudflare_record" "oxygen_wildcard" { + # Add a comment to the record + comment = "This record is used to point to Oxygen wildcard in Tailscale" + + # Use this subdomain + name = "*.oxygen.${local.domains.subdomains.tailscale}" + + # Don't proxy through Cloudflare + proxied = false + + # This is an A record + type = "A" + + # IP address of Oxygen in Tailscale + value = "100.119.51.47" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for Carbon in Tailscale +resource "cloudflare_record" "carbon" { + # Add a comment to the record + comment = "This record is used to point to Carbon in Tailscale" + + # Use this subdomain + name = "carbon.${local.domains.subdomains.tailscale}" + + # Don't proxy through Cloudflare + proxied = false + + # This is an A record + type = "A" + + # IP address of Carbon in Tailscale + value = "100.86.6.103" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for Carbon wildcard in Tailscale +resource "cloudflare_record" "carbon_wildcard" { + # Add a comment to the record + comment = "This record is used to point to Carbon wildcard in Tailscale" + + # Use this subdomain + name = "*.carbon.${local.domains.subdomains.tailscale}" + + # Don't proxy through Cloudflare + proxied = false + + # This is an A record + type = "A" + + # IP address of Carbon in Tailscale + value = "100.86.6.103" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for Kubernetes +resource "cloudflare_record" "kubernetes" { + # Add a comment to the record + comment = "This record is used to point to Kubernetes" + + # Use this subdomain + name = local.domains.subdomains.kubernetes + + # Don't proxy through Cloudflare + proxied = false + + # This is an A record + type = "A" + + # IP address of Kubernetes + value = "100.106.61.38" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} + +# Add record for Kubernetes wildcard +resource "cloudflare_record" "kubernetes_wildcard" { + # Add a comment to the record + comment = "This record is used to point to Kubernetes wildcard" + + # Use this subdomain + name = "*.${local.domains.subdomains.kubernetes}" + + # Don't proxy through Cloudflare + proxied = false + + # This is an A record + type = "A" + + # IP address of Kubernetes + value = "100.106.61.38" + + # Identifier of the zone to add the record to + zone_id = cloudflare_zone.main.id +} diff --git a/src/cloudflare/locals.tf b/src/cloudflare/locals.tf new file mode 100644 index 0000000..f360282 --- /dev/null +++ b/src/cloudflare/locals.tf @@ -0,0 +1,36 @@ +# Local variables to use in this module +locals { + account = { + # Name of the account + name = "spietras" + + # Members of the account + members = { + spietras = { + # Email address of the member + email = "cloudflare@mail.spietras.dev" + } + } + } + + domains = { + # Root domain + root = "spietras.dev" + + subdomains = { + # Subdomain for Kubernetes + kubernetes = "k8s" + + # Subdomain for Tailscale + tailscale = "ts" + + # Subdomain for the main tunnel + tunnel = "demo" + } + } + + # Map of role names to role IDs + roles = { + for role in data.cloudflare_account_roles.account_roles.roles : role.name => role.id + } +} diff --git a/src/cloudflare/redirects.tf b/src/cloudflare/redirects.tf new file mode 100644 index 0000000..2e9a358 --- /dev/null +++ b/src/cloudflare/redirects.tf @@ -0,0 +1,50 @@ +# Add a ruleset to redirect requests for the root domain to another URL +resource "cloudflare_ruleset" "redirect_root" { + # Description of the ruleset + description = "Redirect requests for the root domain to another URL" + + # This ruleset applies to the zone + kind = "zone" + + # The name of the ruleset + name = "Redirect root domain" + + # This ruleset is applied when evaluating redirects for HTTP requests + phase = "http_request_dynamic_redirect" + + # Rules to apply to the ruleset + rules { + # Perform a redirect + action = "redirect" + + # Parameters for the redirect action + action_parameters { + # Use a value to lookup information for the action + from_value { + # Preserve the query string when redirecting + preserve_query_string = true + + # Use a 301 status code for permanent redirects + status_code = 301 + + # Redirect to this URL + target_url { + # Use my GitHub profile as the target URL + value = "https://github.com/spietras" + } + } + } + + # Description of the rule + description = "Redirect requests for the root domain to another URL" + + # Enable the rule + enabled = true + + # Use the rule on requests to spietras.dev + expression = "(http.host eq \"${local.domains.root}\")" + } + + # Identifier of the zone to apply the ruleset to + zone_id = cloudflare_zone.main.id +} diff --git a/src/cloudflare/terraform.tf b/src/cloudflare/terraform.tf new file mode 100644 index 0000000..0d5a9dc --- /dev/null +++ b/src/cloudflare/terraform.tf @@ -0,0 +1,17 @@ +# Terraform settings +terraform { + # Require the following providers + required_providers { + # Cloudflare provider used to interact with Cloudflare API + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 4.0" + } + + # SOPS provider used to decrypt secrets + sops = { + source = "carlpett/sops" + version = "~> 1.0" + } + } +} diff --git a/src/cloudflare/tunnels.tf b/src/cloudflare/tunnels.tf new file mode 100644 index 0000000..869510b --- /dev/null +++ b/src/cloudflare/tunnels.tf @@ -0,0 +1,11 @@ +# Add main tunnel +resource "cloudflare_tunnel" "main" { + # Identifier of the account to add the tunnel to + account_id = cloudflare_account.main.id + + # Name of the tunnel + name = "tunnel" + + # Password for the tunnel + secret = var.secrets.tunnel.token +} diff --git a/src/cloudflare/variables.tf b/src/cloudflare/variables.tf new file mode 100644 index 0000000..f466d0e --- /dev/null +++ b/src/cloudflare/variables.tf @@ -0,0 +1,35 @@ +# Declare variable for secrets +variable "secrets" { + # Description of the variable + description = "Secrets for the Cloudflare module" + + # Mark the variable as sensitive + sensitive = true + + # Type of the variable + type = object({ + dns = object({ + github = object({ + pages = object({ + challenge = string + }) + }) + + protonmail = object({ + challenge = string + + dkim = object({ + domain = string + }) + }) + + simplelogin = object({ + challenge = string + }) + }) + + tunnel = object({ + token = string + }) + }) +} diff --git a/src/cloudflare/zones.tf b/src/cloudflare/zones.tf new file mode 100644 index 0000000..a9f1a06 --- /dev/null +++ b/src/cloudflare/zones.tf @@ -0,0 +1,17 @@ +# Add main zone +resource "cloudflare_zone" "main" { + # Identifier of the account to add the zone to + account_id = cloudflare_account.main.id + + # Use the free plan + plan = "free" + + # Domain of the zone + zone = local.domains.root +} + +# Add DNSSEC to the zone +resource "cloudflare_zone_dnssec" "main" { + # Identifier of the zone to add DNSSEC to + zone_id = cloudflare_zone.main.id +} diff --git a/src/data.tf b/src/data.tf new file mode 100644 index 0000000..c1b3357 --- /dev/null +++ b/src/data.tf @@ -0,0 +1,5 @@ +# Data source with decrypted secrets +data "sops_file" "secrets" { + # Path to the encrypted file + source_file = "secrets.yaml" +} diff --git a/src/locals.tf b/src/locals.tf new file mode 100644 index 0000000..6639b6b --- /dev/null +++ b/src/locals.tf @@ -0,0 +1,5 @@ +# Local variables to use in this module +locals { + # Decrypted secrets + secrets = yamldecode(data.sops_file.secrets.raw) +} diff --git a/src/main.tf b/src/main.tf index 8b13789..24ce418 100644 --- a/src/main.tf +++ b/src/main.tf @@ -1 +1,8 @@ +# Use Cloudflare module +module "cloudflare" { + # Source of the module + source = "./cloudflare/" + # Pass secrets to the module + secrets = local.secrets.cloudflare +} diff --git a/src/providers.tf b/src/providers.tf new file mode 100644 index 0000000..aa4ecc2 --- /dev/null +++ b/src/providers.tf @@ -0,0 +1,5 @@ +# Use Cloudflare provider +provider "cloudflare" { + # API token to use for Cloudflare API requests + api_token = local.secrets.cloudflare.api.token +} diff --git a/src/secrets.yaml b/src/secrets.yaml new file mode 100644 index 0000000..cb375dc --- /dev/null +++ b/src/secrets.yaml @@ -0,0 +1,44 @@ +cloudflare: + api: + token: ENC[AES256_GCM,data:DtXcBpvVwg6Cc41e66uCz4rXDGT05vHNtTN+BLB/JXKy28dQ9vbBZQ==,iv:wjYn5ZZaPvuePLaTAiOBR04SqgIRHocdMz0na6C8eZI=,tag:Zpau4GZ2N/Lid3LBtETUOA==,type:str] + dns: + github: + pages: + challenge: ENC[AES256_GCM,data:Qr2HFupstROjRRxRBXB5BlG4GswYbV8tl4eLas71,iv:ke5VNz3ri5P3fLsuqE87Mz0ZqQ9974IC26amoaH+sto=,tag:3SQeSRaPzpSr6JkXPywpgw==,type:str] + protonmail: + challenge: ENC[AES256_GCM,data:5tp1r1J9UlDHHeln97FPiZHlsplyECjgqD42R2/4CbdS0kQw8bYVJA==,iv:6YbeGPNx2KbeT0hegefZzeuEtQIqybHn4I7dRFkdgoU=,tag:rACSdA3c5/uLIO1TkT41hQ==,type:str] + dkim: + domain: ENC[AES256_GCM,data:J7/HxYi9S5EECaHkEao8QI9Oym44PrJ0hxhvIcztgp2pPboFSRUdxTHrlOntWLcWhstj6GmnnXYUHalgZI4kIxhp2PHKsTrNkcnnGBn9KZjQ,iv:IqiNLxNbVSDT2YywjtvOuUgqoDFSZ6jbAHBSYzQ81zA=,tag:tfGRC3BG3CMRbzdZq4q0yw==,type:str] + simplelogin: + challenge: ENC[AES256_GCM,data:TJo1Hiuoe8lUHezGv7ScRuOgns62aixqLCATqkaN,iv:sl2rOQbc3+E9MTRWuqCRsDhIUnRbHgQ8UQ01Q2j2Bns=,tag:vPVrhUlFtIktYOZC6CunYw==,type:str] + tunnel: + token: ENC[AES256_GCM,data:8pUjYmftnbSXYrKZTOozU/qj+r6voscF/8rLBgga2QGx9MMf9jGDwH/bpS1Q0oYWdKM5vbWws1vfZpUWg4kCK/wk0yr0zYo0DAfvGt0QcPPW8gxdjxu1zrfPjjbWxmnTbJr2H2pOJ0clygDoqRgoQYdabkgmG8aWifXaOXh9j3y48XOYEf8Lp84VOEbmdvJ+3C8UeCz0G9foU7Ns0ad6tQH2Qzg9coHll5VXnX9bFrktQTjgEKxC+w==,iv:mroMDkoxwiCAdySuYp3Fdxmb7mEuDXyCMawab9duwf4=,tag:BNg/i6D09hC0goz3g3g13w==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age14uepygtepskwehywergh9fe9j2a3ytqd80y9r2ekfmett6rq3peqjtgxns + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4dUlJKzBpNGFDSlJsTEFt + TWtzWnR2c2d5WStOcG1sRGxCRmhEaXlPWWowCnNMTDZHT3huekcxMWxWcTRWTWJU + L0ZFNlJiVG5XWnZCOGxxMUpWeDdEYm8KLS0tIHRnN0pSZzA0OVh1MGNlbnJGMjgw + SU5Na29Kc0NJR1hHdXRiSFFnQWtqRmcKJK7S67HUDuBMvgVUtraOLEX5bp7kBcOt + k90fKBEf0mEGKvOL2YbEcGFvXyzvTwZvuuoQHm/UxfPDOtLQgrgbHw== + -----END AGE ENCRYPTED FILE----- + - recipient: age1rtdpdhuyrmzcvl5pqamjca6pxwdyzxnfrzg54zyle9w6j7c06yfsxmdhp3 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4RVRObkVIZUU4dHdqOWxH + M3lPQXJ0b2dtQVhPM2IyTlhVSjgrMWQwYmtjCmkyNmkyU0FnUTJwVkJLWk9lVE14 + UldjUjNiMGNiejVLaFZCdEpGQXBPQk0KLS0tIFU3S3h5SGlOTnJYTkRoV2J1dy9O + ckJjQnl2S2pUTEhKQ1V5TzFCM1pMVFUKzv/pnf9nmJ13BqpaQ2OmT7eMRvJdagUS + u+0WSqJNtO6Mxj/OUrgnDndgEypDOgakt4kCFDCkn4pCI1AmUYDgvA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2024-02-25T15:33:59Z" + mac: ENC[AES256_GCM,data:7QAPBytQ2pwW8OQ32qfMYaEcQaxNO8tD8RANbJelVccZa05ceTZ4TIsjdxJLojiuNDaa6ORt83YNjZsbvA3YyQV3jlytbhKHnyup9n10qrvkQ1LWuseeD0S/QT4Ci6TqVNPcxoaTBFyip1m3dssu7tmeriuTuSfWeU0qDiRolbE=,iv:I+m1FrBLXAr1tOLKKbJmCaS5k5uw6RBiwNfy8AcEGgQ=,tag:i3o3d8lNP+GLZwSazQ28Tw==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.8.1 diff --git a/src/terraform.tf b/src/terraform.tf new file mode 100644 index 0000000..4b38b74 --- /dev/null +++ b/src/terraform.tf @@ -0,0 +1,36 @@ +# Terraform settings +terraform { + # Store state in a remote backend + backend "s3" { + # Bucket name + bucket = "terraform" + + # Path to the state file in the bucket + key = "terraform.tfstate" + + # Region of the bucket + region = "eeur" + + # These are required, because it's actually Cloudflare R2, not AWS S3 + skip_credentials_validation = true + skip_metadata_api_check = true + skip_region_validation = true + skip_requesting_account_id = true + skip_s3_checksum = true + } + + # Require the following providers + required_providers { + # Cloudflare provider used to interact with Cloudflare API + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 4.0" + } + + # SOPS provider used to decrypt secrets + sops = { + source = "carlpett/sops" + version = "~> 1.0" + } + } +}