From 25ec9193e404f659b2906bdb9212b067c8f387a1 Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Thu, 3 Sep 2026 16:33:27 -0600 Subject: [PATCH 1/5] feat: bootstrap OpenTofu root --- .checkov.yml | 13 +++++++ .github/dependabot.yml | 19 ++++++++++ .github/workflows/dependabot-notify.yml | 15 ++++++++ .github/workflows/opentofu.yml | 23 ++++++++++++ .gitignore | 14 +++++++ .gitleaks.toml | 6 +++ .sops.yaml | 3 ++ .terraform-docs.yml | 20 ++++++++++ .tflint.hcl | 12 ++++++ AGENTS.md | 9 +++++ Makefile | 50 +++++++++++++++++++++++++ README.md | 30 ++++++++++++++- docs/bootstrap.md | 25 +++++++++++++ main.tf | 3 ++ providers.tf | 18 +++++++++ 15 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 .checkov.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependabot-notify.yml create mode 100644 .github/workflows/opentofu.yml create mode 100644 .gitignore create mode 100644 .gitleaks.toml create mode 100644 .sops.yaml create mode 100644 .terraform-docs.yml create mode 100644 .tflint.hcl create mode 100644 AGENTS.md create mode 100644 Makefile create mode 100644 docs/bootstrap.md create mode 100644 main.tf create mode 100644 providers.tf 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/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..03c5f79 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +# Managed by tfroot-github (gh-dependabot.tf); local edits are overwritten. +"updates": +- "directory": "/" + "groups": + "github-actions": + "patterns": + - "*" + "package-ecosystem": "github-actions" + "schedule": + "interval": "daily" +- "directory": "/" + "groups": + "opentofu": + "patterns": + - "*" + "package-ecosystem": "opentofu" + "schedule": + "interval": "daily" +"version": 2 diff --git a/.github/workflows/dependabot-notify.yml b/.github/workflows/dependabot-notify.yml new file mode 100644 index 0000000..de27250 --- /dev/null +++ b/.github/workflows/dependabot-notify.yml @@ -0,0 +1,15 @@ +--- +# Managed by tfroot-github (gh-dependabot.tf); local edits are overwritten. +name: dependabot-notify + +on: + pull_request: + types: [opened, reopened] + +permissions: {} + +jobs: + notify: + if: github.actor == 'dependabot[bot]' + uses: makeitworkcloud/shared-workflows/.github/workflows/_dependabot-notify.yml@main + secrets: inherit diff --git a/.github/workflows/opentofu.yml b/.github/workflows/opentofu.yml new file mode 100644 index 0000000..098bcca --- /dev/null +++ b/.github/workflows/opentofu.yml @@ -0,0 +1,23 @@ +name: opentofu + +on: + pull_request: + branches: + - main + push: + branches: + - main + +permissions: + contents: write + id-token: write + pull-requests: write + +jobs: + opentofu: + if: >- + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository + uses: makeitworkcloud/shared-workflows/.github/workflows/opentofu.yml@main + secrets: + CHART_UPDATER_GITHUB_APP_PRIVATE_KEY: ${{ secrets.CHART_UPDATER_GITHUB_APP_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca178e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +**/*.sw[po] + +# Do not commit OpenTofu state, lock files, or working directories. +**/.terraform.lock.hcl +**/.terraform + +**/.vscode +**/.DS_Store + +plan-output.txt + +# Generated canonical pre-commit configuration +/.pre-commit-config.yaml +/.pre-commit-config.yaml.tmp diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..925bb96 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,6 @@ +[extend] +useDefault = true + +[[allowlists]] +description = "Generated OpenTofu working data is ignored and never committed" +paths = ['''(^|/)\.terraform(?:/.*)?$'''] diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..99901db --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,3 @@ +--- +creation_rules: + - kms: arn:aws:kms:us-west-2:332355796717:key/0a45c0f6-71dc-4d54-ab33-9df4de1a9e91 diff --git a/.terraform-docs.yml b/.terraform-docs.yml new file mode 100644 index 0000000..c11fd54 --- /dev/null +++ b/.terraform-docs.yml @@ -0,0 +1,20 @@ +formatter: "markdown" + +output: + file: "README.md" + mode: replace + template: | + + {{ .Content }} + + +settings: + color: false + lockfile: false + +sort: + enabled: true + by: name + +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/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b877f8b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,9 @@ +# Agent Instructions + +OpenTofu root for Make IT Work Cloud Twilio control-plane infrastructure. + +This root owns only Twilio phone-number inventory and inbound messaging-webhook configuration. It must not own the OpenCode SMS bridge workload, Cloudflare workload route or DNS, number-to-agent mapping, approved-source allowlist, bridge credentials, or encryption material. Those runtime concerns belong to `kustomize-cluster`. + +This bootstrap contains no Twilio resources, provider configuration, backend credentials, encrypted secrets, state, or number identifiers. Use GitHub MCP and pull-request CI plans as validation authority. `main` is an environment-gated apply path; use scoped branches and pull requests, never direct pushes. Do not run OpenTofu, SOPS, state, import, or apply commands from this server. + +The shared workflow is owned by `shared-workflows`; the runner image and canonical pre-commit configuration are owned by `images/tfroot-runner`. Keep any future SOPS data encrypted and never expose credentials, decrypted values, state, private keys, or sensitive plans. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a8476b8 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +SHELL := /bin/bash +TERRAFORM := $(shell which tofu) + +.PHONY: clean init plan apply test pre-commit-config pre-commit-check-deps pre-commit-install-hooks + +clean: + @find . -name .terraform -type d | xargs -r rm -rf + +# Bootstrap deliberately uses no remote backend until the encrypted backend +# contract and least-privilege CI access are established in a later PR. +init: clean + @${TERRAFORM} init -backend=false -upgrade -input=false + +plan: init + @${TERRAFORM} plan -refresh=false -input=false -lock=false -compact-warnings + +# There are intentionally no provider configurations or Twilio resources in +# this bootstrap, so the main-branch apply has no provider-side effect. +apply: init + @${TERRAFORM} apply -auto-approve -refresh=false -input=false -lock=false -compact-warnings + +test: pre-commit-config pre-commit-install-hooks + @pre-commit run -a + +pre-commit-config: + @curl --fail --silent --show-error --location \ + --output .pre-commit-config.yaml.tmp \ + https://raw.githubusercontent.com/makeitworkcloud/images/main/tfroot-runner/pre-commit-config.yaml + @if cmp -s .pre-commit-config.yaml.tmp .pre-commit-config.yaml; then \ + rm -f .pre-commit-config.yaml.tmp; \ + else \ + mv .pre-commit-config.yaml.tmp .pre-commit-config.yaml; \ + fi + +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: pre-commit-config pre-commit-check-deps + @pre-commit install --install-hooks --hook-type pre-commit --hook-type commit-msg diff --git a/README.md b/README.md index 3dcf5aa..c1fcfc7 100644 --- a/README.md +++ b/README.md @@ -1 +1,29 @@ -# tfroot-twilio \ No newline at end of file + +## Requirements + +| Name | Version | +| ---- | ------- | +| [terraform](#requirement_terraform) | > 1.3 | + +## Providers + +| Name | Version | +| ---- | ------- | +| [twilio](#provider_twilio) | 0.27.1 | + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/docs/bootstrap.md b/docs/bootstrap.md new file mode 100644 index 0000000..eaf9785 --- /dev/null +++ b/docs/bootstrap.md @@ -0,0 +1,25 @@ +# Twilio root bootstrap + +## Scope + +This initial root validates the OpenTofu toolchain and provider installation only. It creates, imports, updates, or deletes **no** Twilio resources. In particular, it does not purchase or configure phone numbers, webhooks, credentials, messaging services, or runtime routing. + +The evaluated provider is `RJPearson94/twilio` `0.27.1`. Registry documentation confirms it supports Twilio phone-number resources and inbound `messaging` webhook fields, but it is community-maintained and requires existing account credentials. The bootstrap intentionally declares no provider configuration, so CI receives no Twilio credentials and performs no provider-side API call. + +## Ownership boundary + +The future root may own only Twilio phone-number inventory and inbound messaging-webhook configuration. `kustomize-cluster` remains the owner of the OpenCode bridge workload, workload `TunnelBinding`/DNS, fixed number-to-agent map, approved-source allowlist, runtime secrets, and state-encryption inputs. The root must never create a bridge-worker API key or duplicate a runtime owner. + +## Backend and credential contract + +The checked-in `.sops.yaml` identifies the approved encryption recipient but no encrypted secret file exists yet. A later, separately reviewed change must: + +1. establish least-privilege GitHub Actions OIDC access for this exact repository to decrypt SOPS material; +2. add an encrypted backend configuration for a dedicated Twilio state object; and +3. define an existing Twilio credential delivery path that cannot expose a token in source, CI logs, or OpenTofu state. + +Only after those prerequisites pass pull-request validation may a later root change add provider configuration or Twilio inventory. Any actual Twilio provisioning or webhook update still requires explicit owner confirmation before merge because `main` invokes the environment-gated apply path. + +## Central generated files + +`.github/dependabot.yml` and `.github/workflows/dependabot-notify.yml` are seeded here with the exact content generated by `tfroot-github`. They are not locally owned. This PR-based seed resolves the branch-protection bootstrap conflict; after merge, the central `github_repository_file` resources can adopt and continue managing them with `overwrite_on_create = true`. diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..adbd32e --- /dev/null +++ b/main.tf @@ -0,0 +1,3 @@ +# Bootstrap intentionally contains no Twilio resources, data sources, provider +# configuration, or secret material. The next root change must first establish +# the encrypted backend and credential-delivery contract described in docs/bootstrap.md. diff --git a/providers.tf b/providers.tf new file mode 100644 index 0000000..e66cc84 --- /dev/null +++ b/providers.tf @@ -0,0 +1,18 @@ +terraform { + required_version = "> 1.3" + + # The permanent S3 backend is enabled only after its encrypted contract and + # least-privilege CI access are reviewed in a follow-up pull request. + backend "s3" {} + + required_providers { + twilio = { + source = "RJPearson94/twilio" + version = "0.27.1" + } + } +} + +# No provider block is declared during bootstrap. The candidate provider is +# installed and syntax-validated without receiving credentials or managing +# Twilio resources. From 76734da027f65900bef5a2c72c55111095099025 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 22:35:16 +0000 Subject: [PATCH 2/5] chore: apply pre-commit fixes --- AGENTS.md | 2 +- README.md | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b877f8b..47f6717 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,4 +6,4 @@ This root owns only Twilio phone-number inventory and inbound messaging-webhook This bootstrap contains no Twilio resources, provider configuration, backend credentials, encrypted secrets, state, or number identifiers. Use GitHub MCP and pull-request CI plans as validation authority. `main` is an environment-gated apply path; use scoped branches and pull requests, never direct pushes. Do not run OpenTofu, SOPS, state, import, or apply commands from this server. -The shared workflow is owned by `shared-workflows`; the runner image and canonical pre-commit configuration are owned by `images/tfroot-runner`. Keep any future SOPS data encrypted and never expose credentials, decrypted values, state, private keys, or sensitive plans. \ No newline at end of file +The shared workflow is owned by `shared-workflows`; the runner image and canonical pre-commit configuration are owned by `images/tfroot-runner`. Keep any future SOPS data encrypted and never expose credentials, decrypted values, state, private keys, or sensitive plans. diff --git a/README.md b/README.md index c1fcfc7..bf81611 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,12 @@ | Name | Version | | ---- | ------- | -| [terraform](#requirement_terraform) | > 1.3 | +| [terraform](#requirement\_terraform) | > 1.3 | +| [twilio](#requirement\_twilio) | 0.27.1 | ## Providers -| Name | Version | -| ---- | ------- | -| [twilio](#provider_twilio) | 0.27.1 | +No providers. ## Modules From ca9d31fd5b1d767003228ea939e75a58f4cf9061 Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Thu, 3 Sep 2026 16:53:03 -0600 Subject: [PATCH 3/5] ci: capture Twilio OIDC subject --- .github/workflows/opentofu.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/opentofu.yml b/.github/workflows/opentofu.yml index 098bcca..862a5cd 100644 --- a/.github/workflows/opentofu.yml +++ b/.github/workflows/opentofu.yml @@ -14,6 +14,27 @@ permissions: pull-requests: write jobs: + # Temporary diagnostic: emits only the non-sensitive OIDC subject needed to + # scope the existing AWS KMS trust policy to this exact repository. Remove + # this job once that trust-policy correction is validated. + oidc-subject: + if: >- + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: read + id-token: write + runs-on: arc-tf + steps: + - name: Emit non-sensitive OIDC subject + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const token = await core.getIDToken('sts.amazonaws.com'); + const payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'); + const claims = JSON.parse(Buffer.from(payload, 'base64').toString('utf8')); + console.log(`OIDC subject: ${claims.sub}`); + opentofu: if: >- github.event_name != 'pull_request' || From b65958f2f9722b92a220f79e736d23f845ea9b11 Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Thu, 3 Sep 2026 16:54:17 -0600 Subject: [PATCH 4/5] ci: remove OIDC subject diagnostic --- .github/workflows/opentofu.yml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.github/workflows/opentofu.yml b/.github/workflows/opentofu.yml index 862a5cd..098bcca 100644 --- a/.github/workflows/opentofu.yml +++ b/.github/workflows/opentofu.yml @@ -14,27 +14,6 @@ permissions: pull-requests: write jobs: - # Temporary diagnostic: emits only the non-sensitive OIDC subject needed to - # scope the existing AWS KMS trust policy to this exact repository. Remove - # this job once that trust-policy correction is validated. - oidc-subject: - if: >- - github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name == github.repository - permissions: - contents: read - id-token: write - runs-on: arc-tf - steps: - - name: Emit non-sensitive OIDC subject - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const token = await core.getIDToken('sts.amazonaws.com'); - const payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'); - const claims = JSON.parse(Buffer.from(payload, 'base64').toString('utf8')); - console.log(`OIDC subject: ${claims.sub}`); - opentofu: if: >- github.event_name != 'pull_request' || From b514b7869345a6e8d0c52fd79035840d55bf225f Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Thu, 3 Sep 2026 17:34:07 -0600 Subject: [PATCH 5/5] fix: omit backend from bootstrap root --- providers.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/providers.tf b/providers.tf index e66cc84..e580aec 100644 --- a/providers.tf +++ b/providers.tf @@ -1,10 +1,6 @@ terraform { required_version = "> 1.3" - # The permanent S3 backend is enabled only after its encrypted contract and - # least-privilege CI access are reviewed in a follow-up pull request. - backend "s3" {} - required_providers { twilio = { source = "RJPearson94/twilio" @@ -13,6 +9,10 @@ terraform { } } +# The permanent S3 backend is intentionally omitted until its encrypted +# contract and least-privilege CI access are reviewed in a follow-up PR. +# The bootstrap Makefile initializes with -backend=false. + # No provider block is declared during bootstrap. The candidate provider is # installed and syntax-validated without receiving credentials or managing # Twilio resources.