diff --git a/.checkov.yml b/.checkov.yml new file mode 100644 index 0000000..02f3a56 --- /dev/null +++ b/.checkov.yml @@ -0,0 +1,13 @@ +block-list-secret-scan: [] +compact: true +directory: + - . +download-external-modules: false +evaluate-variables: true +framework: + - all +output: + - cli +quiet: true +soft-fail: true +summary-position: top diff --git a/.github/workflows/opentofu.yml b/.github/workflows/opentofu.yml new file mode 100644 index 0000000..80a5bfa --- /dev/null +++ b/.github/workflows/opentofu.yml @@ -0,0 +1,19 @@ +name: OpenTofu + +on: + pull_request: + branches: + - main + push: + branches: + - main + +permissions: + contents: read + pull-requests: write + +jobs: + opentofu: + uses: makeitworkcloud/shared-workflows/.github/workflows/opentofu.yml@main + secrets: + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45a1d23 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# vim swap files +**/*.sw[po] + +# don't commit terraform state or lock. the repo code is the only state we care about. +# the provider state cache is auto-upgraded by default to ensure compatibility with upstream cloud provider APIs +**/.terraform.lock.hcl +**/.terraform + +# IDE Folders +**/.vscode + +# Mac Finder cache +**/.DS_Store + +# Plan output +plan-output.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f3e633f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-vcs-permalinks + - id: destroyed-symlinks + - id: detect-private-key + - id: mixed-line-ending + - id: trailing-whitespace +- repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.104.0 + hooks: + - id: terraform_validate + args: + - --hook-config=--retry-once-with-cleanup=true + - --args=-no-color + - --tf-init-args=-reconfigure + - --tf-init-args=-upgrade + - id: terraform_tflint + args: + - --args=--minimum-failure-severity=error + - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl + - id: terraform_checkov + args: + - --args=--config-file __GIT_WORKING_DIR__/.checkov.yml + - id: terraform_fmt + args: + - --args=-no-color + - --args=-diff + - --args=-recursive + - id: terraform_docs + args: + - --args=--config=.terraform-docs.yml diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..8967c45 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,3 @@ +--- +creation_rules: + - age: age152ek83tm4fj5u70r3fecytn4kg7c5xca24erjchxexx4pfqg6das7q763l diff --git a/.terraform-docs.yml b/.terraform-docs.yml new file mode 100644 index 0000000..2cbbb30 --- /dev/null +++ b/.terraform-docs.yml @@ -0,0 +1,18 @@ +formatter: "markdown" + +output: + file: "README.md" + mode: replace + +settings: + color: false + lockfile: false + +sort: + enabled: true + by: name + +# recursive can't be enabled until this bug is fixed: +# https://github.com/terraform-docs/terraform-docs/issues/654 +recursive: + enabled: false diff --git a/.tflint.hcl b/.tflint.hcl new file mode 100644 index 0000000..062eb57 --- /dev/null +++ b/.tflint.hcl @@ -0,0 +1,12 @@ +plugin "terraform" { + enabled = true + preset = "recommended" +} + +rule "terraform_required_providers" { + enabled = false +} + +rule "terraform_required_version" { + enabled = false +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..08a54ca --- /dev/null +++ b/Makefile @@ -0,0 +1,78 @@ +SHELL := /bin/bash +TERRAFORM := $(shell which tofu) +S3_REGION := $(shell sops decrypt secrets/secrets.yaml | grep ^s3_region | cut -d ' ' -f 2) +S3_BUCKET := $(shell sops decrypt secrets/secrets.yaml | grep ^s3_bucket | cut -d ' ' -f 2) +S3_KEY := $(shell sops decrypt secrets/secrets.yaml | grep ^s3_key | cut -d ' ' -f 2) +S3_ACCESS_KEY := $(shell sops decrypt secrets/secrets.yaml | grep ^s3_access_key | cut -d ' ' -f 2) +S3_SECRET_KEY := $(shell sops decrypt secrets/secrets.yaml | grep ^s3_secret_key | cut -d ' ' -f 2) + +.PHONY: help init plan apply migrate test pre-commit-check-deps pre-commit-install-hooks argcd-login + +help: + @echo "General targets" + @echo "----------------" + @echo + @echo "\thelp: show this help text" + @echo "\tclean: removes all .terraform directories" + @echo + @echo "Terraform targets" + @echo "-----------------" + @echo + @echo "\tinit: run 'terraform init'" + @echo "\ttest: run pre-commmit checks" + @echo "\tplan: run 'terraform plan'" + @echo "\tapply: run 'terraform apply'" + @echo "\tmigrate; run terraform init -migrate-state" + @echo + @echo "One-time repo init targets" + @echo "--------------------------" + @echo + @echo "\tpre-commit-install-hooks: install pre-commit hooks" + @echo "\tpre-commit-check-deps: check pre-commit dependencies" + @echo + +clean: + @find . -name .terraform -type d | xargs -I {} rm -rf {} + +init: clean .terraform/terraform.tfstate + +.terraform/terraform.tfstate: + @${TERRAFORM} init -reconfigure -upgrade -input=false -backend-config="key=${S3_KEY}" -backend-config="bucket=${S3_BUCKET}" -backend-config="region=${S3_REGION}" -backend-config="access_key=${S3_ACCESS_KEY}" -backend-config="secret_key=${S3_SECRET_KEY}" + +plan: init .terraform/plan + +.terraform/plan: + @${TERRAFORM} plan -compact-warnings -no-color -out tfplan.bin + @${TERRAFORM} show -no-color tfplan.bin | tee plan-output.txt + @rm -f tfplan.bin + +apply: init .terraform/apply + +.terraform/apply: + @${TERRAFORM} apply -auto-approve -compact-warnings + +migrate: + @echo "First use -make init- using the old S3 backend, then run -make migrate- to use the new one." + @${TERRAFORM} init -migrate-state -backend-config="key=${S3_KEY}" -backend-config="bucket=${S3_BUCKET}" -backend-config="region=${S3_REGION}" -backend-config="access_key=${S3_ACCESS_KEY}" -backend-config="secret_key=${S3_SECRET_KEY}" + +test: .git/hooks/pre-commit + @pre-commit run -a + +DEPS_PRE_COMMIT=$(shell which pre-commit || echo "pre-commit not found") +DEPS_TERRAFORM_DOCS=$(shell which terraform-docs || echo "terraform-docs not found") +DEPS_TFLINT=$(shell which tflint || echo "tflint not found,") +DEPS_CHECKOV=$(shell which checkov || echo "checkov not found,") +DEPS_JQ=$(shell which jq || echo "jq not found,") +pre-commit-check-deps: + @echo "Checking for pre-commit and its dependencies:" + @echo " pre-commit: ${DEPS_PRE_COMMIT}" + @echo " terraform-docs: ${DEPS_TERRAFORM_DOCS}" + @echo " tflint: ${DEPS_TFLINT}" + @echo " checkov: ${DEPS_CHECKOV}" + @echo " jq: ${DEPS_JQ}" + @echo "" + +pre-commit-install-hooks: .git/hooks/pre-commit + +.git/hooks/pre-commit: pre-commit-check-deps + @pre-commit install --install-hooks diff --git a/cf-dns.tf b/cf-dns.tf new file mode 100644 index 0000000..e7c637e --- /dev/null +++ b/cf-dns.tf @@ -0,0 +1,43 @@ +resource "cloudflare_dns_record" "root" { + zone_id = local.zone_id + type = "CNAME" + name = "@" + content = "makeitwork.cloud.s3-website.us-west-2.amazonaws.com" + proxied = true + ttl = 1 +} + +resource "cloudflare_dns_record" "www" { + zone_id = local.zone_id + type = "CNAME" + name = "www" + content = "makeitwork.cloud.s3-website.us-west-2.amazonaws.com" + proxied = true + ttl = 1 +} + +resource "cloudflare_dns_record" "mx_primary" { + zone_id = local.zone_id + type = "MX" + name = "@" + content = "mx1.privateemail.com" + priority = 10 + ttl = 1 +} + +resource "cloudflare_dns_record" "mx_secondary" { + zone_id = local.zone_id + type = "MX" + name = "@" + content = "mx2.privateemail.com" + priority = 20 + ttl = 1 +} + +resource "cloudflare_dns_record" "spf" { + zone_id = local.zone_id + type = "TXT" + name = "@" + content = "v=spf1 include:spf.privateemail.com ~all" + ttl = 1 +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..6c7f701 --- /dev/null +++ b/main.tf @@ -0,0 +1,12 @@ +data "sops_file" "secret_vars" { + source_file = "${path.module}/secrets/secrets.yaml" +} + +locals { + account_id = data.sops_file.secret_vars.data["cloudflare_account_id"] + zone_id = data.sops_file.secret_vars.data["cloudflare_zone_id"] +} + +data "cloudflare_zone" "makeitwork_cloud" { + zone_id = local.zone_id +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/providers.tf b/providers.tf new file mode 100644 index 0000000..055d938 --- /dev/null +++ b/providers.tf @@ -0,0 +1,20 @@ +terraform { + required_version = "> 1.3" + + backend "s3" {} + + required_providers { + sops = { + source = "carlpett/sops" + } + cloudflare = { + source = "cloudflare/cloudflare" + } + } +} + +provider "sops" {} + +provider "cloudflare" { + api_token = data.sops_file.secret_vars.data["cloudflare_api_token"] +} diff --git a/secrets/secrets.yaml b/secrets/secrets.yaml new file mode 100644 index 0000000..3312630 --- /dev/null +++ b/secrets/secrets.yaml @@ -0,0 +1,23 @@ +s3_bucket: ENC[AES256_GCM,data:jT+AwsE2YfnumTBuPPqQTVFn/juML+Bi,iv:Sxw+IxDdWfczBQwJhbhCSTCf/Jt8jmSvacYu1WW3qWM=,tag:iJ1W6X2fLsdItrMzX7v1ew==,type:str] +s3_key: ENC[AES256_GCM,data:Izipy2MdiTx5XAnosdmFHWI=,iv:9zuSRx381hnc+bKMjGTNPU+LG4UHTC++OvcAAJugNk0=,tag:GSt3l+7l8d0TLRXoMpmLMA==,type:str] +s3_region: ENC[AES256_GCM,data:k3DctzAskFYM,iv:IhoNmH219ySrM5U39I2W4008abAWpQaajpX/CsxyunA=,tag:XiUr2Tq3sSfFHRKONOv7yw==,type:str] +s3_access_key: ENC[AES256_GCM,data:wQsT7hUqlcOk/MNk5GGlXlPFC84=,iv:UMqn1nVjreRVWu1RKFMksJlz7v9azhYG5oK+1hN9+cw=,tag:t3j1grOOnATBSbOD2vFNKg==,type:str] +s3_secret_key: ENC[AES256_GCM,data:SAv6odIom2BPEYUtHrzdxvRaQcB3Ik1oZi8tO8edrJ/mcH8dRqSJRA==,iv:GJJ9QZXcnFpIHxs/d4UbI6iFl0gMThSZbqXclgLNXpY=,tag:irM0u94+MdoRxNKfEpSugQ==,type:str] +cloudflare_api_token: ENC[AES256_GCM,data:xSrXTp/uRw2NPk5vXutq8AsKzzyn3Y/hpW5K8Jik5kiTCfenZRHKtw==,iv:4ozBTzr1zK50AQ4ropFg7KVHA26AzqOPIaFcAQ+leyA=,tag:gKWOk4CQh7AfF2hFkeE23w==,type:str] +cloudflare_account_id: ENC[AES256_GCM,data:fVKqNcHAnRtsq+D3EkcIISq4JBJFCsekUsURBsTqiZY=,iv:L0NKM/qyvt/SSeKzKpfNqSkmanfCExa9vH/NVHSamco=,tag:n0chWSay6kPF8MPgJsgNSg==,type:str] +cloudflare_zone_id: ENC[AES256_GCM,data:D4ZmzpT9DOy/IA7Mbqh1Vq3ZQvRlggk9rq9HPjyZOw0=,iv:ieYg2a48Zl/hnEwWuvn1Oni+rtG70NB9ZicOAOpKPXA=,tag:5kq2Sw3ehBs24uOBUF1Xlg==,type:str] +sops: + age: + - recipient: age152ek83tm4fj5u70r3fecytn4kg7c5xca24erjchxexx4pfqg6das7q763l + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4K2ZkQWNxMDNUc1A2SDhO + cEM1amR3RUN1Z0kyelZSTVlGS3RPbXRiWTFVCkw4QURFTkk5QTlOZlRORktTVHlN + MWw5QlJSOVcwdXFyanltU0dlcGllVm8KLS0tIEVpZ3BmQW1hcUVqK0U4Qkp6akp6 + enZWZVdMQ0s1T3NHRUdvaUhZNTVVYlkKimr9myWIePyJOw3yCpcO3l4WivlBUUoz + BpvuAdvjA31LIjhnxSzVTLTTNOsPWFjGGtF6hGbzgZKNOHTEoOWv6g== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-12-21T07:05:35Z" + mac: ENC[AES256_GCM,data:YoRZKokhD2W9xQhLpGW0Apdqk4+dbd/UUGN2pcItchkH5GSrpG2zXk4NWJqlVX99VLuC+5avqGjk5YLjWKVhDmFvg/gcakO7eok1p5czA1zyoVEpiODvAQtvQAWCEiurlbWYuR4kvl7c19LxkC2NLfR+AVQLgp9HmKtFjgAEn+U=,iv:WJBQ1jLBCY06qjyhYOnEALuDJ9DVCqvDzIZz42UNIbc=,tag:FHgnXKhueIriyN6mFrOPZQ==,type:str] + unencrypted_suffix: _unencrypted + version: 3.10.2