From efb6cefbb31b3425dac832c7a3f3d8731920a64a Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Fri, 4 Sep 2026 13:10:37 -0600 Subject: [PATCH 1/2] feat: configure Twilio provider authentication --- AGENTS.md | 2 +- Makefile | 13 ++++++------- docs/bootstrap.md | 14 ++++++++------ providers.tf | 5 +++-- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 307f5af..e8e9fed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,6 @@ 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 runtime encryption material. Those runtime concerns belong to `kustomize-cluster`. -The root owns the SOPS-encrypted `secrets/secrets.yaml` provider credential file and no other secret material. It contains only the Twilio provider environment-variable names. The root contains no Twilio resources, provider configuration, 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 root owns the SOPS-encrypted `secrets/secrets.yaml` provider credential file and no other secret material. It contains only the Twilio provider environment-variable names. The root configures the Twilio provider exclusively from those process-scoped environment variables, but contains no Twilio resources, 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. diff --git a/Makefile b/Makefile index 573da03..e5cce01 100644 --- a/Makefile +++ b/Makefile @@ -19,21 +19,20 @@ init: clean -backend-config="region=${S3_REGION}" \ -backend-config="use_lockfile=true" -# The encrypted file is verified and exposed only to this short-lived child -# process. It does not configure the Twilio provider or call Twilio APIs. +# Verify ciphertext and required keys without printing values. plan and apply run +# OpenTofu inside a separate SOPS exec-env child so the provider can authenticate. credentials-check: @test -f "${TWILIO_CREDENTIALS}" @sops filestatus "${TWILIO_CREDENTIALS}" | jq -e '.encrypted == true' >/dev/null @sops exec-env "${TWILIO_CREDENTIALS}" 'test -n "$$TWILIO_ACCOUNT_SID" && test -n "$$TWILIO_API_KEY" && test -n "$$TWILIO_API_SECRET"' plan: init credentials-check - @${TERRAFORM} plan -refresh=false -input=false -compact-warnings + @sops exec-env "${TWILIO_CREDENTIALS}" '${TERRAFORM} plan -refresh=false -input=false -compact-warnings' -# There are intentionally no provider configurations or Twilio resources in -# this root, so backend selection and encrypted-credential validation are the -# only stateful behaviors on main. +# This root has no Twilio resources. Provider authentication may perform its +# own read-only validation, but apply has no Twilio resource action to perform. apply: init credentials-check - @${TERRAFORM} apply -auto-approve -refresh=false -input=false -compact-warnings + @sops exec-env "${TWILIO_CREDENTIALS}" '${TERRAFORM} apply -auto-approve -refresh=false -input=false -compact-warnings' test: pre-commit-config pre-commit-install-hooks @pre-commit run -a diff --git a/docs/bootstrap.md b/docs/bootstrap.md index 8742993..e630c3f 100644 --- a/docs/bootstrap.md +++ b/docs/bootstrap.md @@ -2,21 +2,23 @@ ## Scope -This root validates the OpenTofu toolchain, remote state backend, and encrypted provider-credential delivery only. It creates, imports, updates, or deletes **no** Twilio resources. In particular, it does not purchase or configure phone numbers, webhooks, messaging services, or runtime routing. +This root validates the OpenTofu toolchain, remote state backend, encrypted provider-credential delivery, and Twilio provider authentication only. It creates, imports, updates, or deletes **no** Twilio resources. In particular, it does not purchase or configure phone numbers, webhooks, 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 root intentionally declares no provider configuration, so CI validates SOPS credential delivery without performing a provider-side API call. +The evaluated provider is `RJPearson94/twilio` `0.27.1`. It is community-maintained and requires existing account credentials. The provider is intentionally configured with no static attributes: OpenTofu receives its credentials only in a SOPS `exec-env` child. Its authentication validation may perform a read-only Twilio API request; no resource is declared or changed by this root. -## Ownership boundary +## Intended message flow -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 runtime-encryption inputs. The root must never create a bridge-worker API key or duplicate a runtime owner. +The owner’s intended outcome is one Twilio phone number for each primary agent: `lawnmowerman`, `grillmaster`, `homesteader`, and `homerepair`. Each number will accept inbound SMS/MMS only from the one owner-approved source phone number, then route the request to its corresponding agent for a timely reply using that agent’s existing context, knowledge base, and MCP integrations. + +`kustomize-cluster` exclusively owns the bridge workload, number-to-agent map, one-number sender allowlist, runtime secrets, `TunnelBinding`/DNS, and public route. This root may later own only the Twilio number inventory and the inbound messaging-webhook fields pointing at the already-healthy bridge. It must not duplicate any bridge/runtime configuration. ## Backend and credential contract [`tfroot-aws` PR #43](https://github.com/makeitworkcloud/tfroot-aws/pull/43) created the canonical backend producer: a dedicated private, encrypted, versioned bucket and an exact-repository GitHub OIDC role restricted to the state object, lockfile, and SOPS KMS decrypt/describe access. This root selects that backend through the reusable workflow's `aws-role-to-assume` input and uses S3 native locking. No static AWS backend credential is stored in source or GitHub Actions secrets. -The checked-in `.sops.yaml` identifies the approved encryption recipient. `secrets/secrets.yaml` is the only accepted provider credential file. It holds the encrypted `TWILIO_ACCOUNT_SID`, `TWILIO_API_KEY`, and `TWILIO_API_SECRET` values; it must never contain bridge runtime inputs. `make plan` and `make apply` first require SOPS to recognize the file as encrypted, then use `sops exec-env` to assert the three variables in a short-lived child process. They never write plaintext files, set static provider attributes, or use GitHub Actions secrets. The older `secrets/twilio.sops.env.example` placeholder is superseded and must not be used. +The checked-in `.sops.yaml` identifies the approved encryption recipient. `secrets/secrets.yaml` is the only accepted provider credential file. It holds the encrypted `TWILIO_ACCOUNT_SID`, `TWILIO_API_KEY`, and `TWILIO_API_SECRET` values; it must never contain bridge runtime inputs. `make plan` and `make apply` first require SOPS to recognize the file as encrypted, then run OpenTofu inside `sops exec-env`. They never write plaintext files, set static provider attributes, or use GitHub Actions secrets. -Only after the encrypted credential path passes 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. +This provider-only change must pass pull-request validation before later Twilio inventory or webhook work is proposed. 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 diff --git a/providers.tf b/providers.tf index 1b1e825..05c3986 100644 --- a/providers.tf +++ b/providers.tf @@ -11,5 +11,6 @@ terraform { } } -# No provider block is declared. The selected backend is independent of Twilio -# credentials and resource management; those remain separate follow-up gates. +# Authentication is supplied only through TWILIO_ACCOUNT_SID, TWILIO_API_KEY, +# and TWILIO_API_SECRET in SOPS exec-env. Do not add static credentials here. +provider "twilio" {} From 6f88117a5a18bda4545d422d8697521f61153676 Mon Sep 17 00:00:00 2001 From: Steven Welch Date: Fri, 4 Sep 2026 13:11:17 -0600 Subject: [PATCH 2/2] docs: describe extensible sender allowlist --- docs/bootstrap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/bootstrap.md b/docs/bootstrap.md index e630c3f..fe08a96 100644 --- a/docs/bootstrap.md +++ b/docs/bootstrap.md @@ -8,9 +8,9 @@ The evaluated provider is `RJPearson94/twilio` `0.27.1`. It is community-maintai ## Intended message flow -The owner’s intended outcome is one Twilio phone number for each primary agent: `lawnmowerman`, `grillmaster`, `homesteader`, and `homerepair`. Each number will accept inbound SMS/MMS only from the one owner-approved source phone number, then route the request to its corresponding agent for a timely reply using that agent’s existing context, knowledge base, and MCP integrations. +The owner’s intended outcome is one Twilio phone number for each primary agent: `lawnmowerman`, `grillmaster`, `homesteader`, and `homerepair`. Each number will initially accept inbound SMS/MMS only from the one owner-approved source phone number, then route the request to its corresponding agent for a timely reply using that agent’s existing context, knowledge base, and MCP integrations. The sender allowlist is intentionally extensible: adding a further approved source number after the pilot is a separate runtime-configuration change, not a Twilio-root redesign. -`kustomize-cluster` exclusively owns the bridge workload, number-to-agent map, one-number sender allowlist, runtime secrets, `TunnelBinding`/DNS, and public route. This root may later own only the Twilio number inventory and the inbound messaging-webhook fields pointing at the already-healthy bridge. It must not duplicate any bridge/runtime configuration. +`kustomize-cluster` exclusively owns the bridge workload, number-to-agent map, sender allowlist and its later expansion, runtime secrets, `TunnelBinding`/DNS, and public route. This root may later own only the Twilio number inventory and the inbound messaging-webhook fields pointing at the already-healthy bridge. It must not duplicate any bridge/runtime configuration. ## Backend and credential contract