From 89564ccec7cd537f635df86221db9fb8a439f407 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 4 May 2026 09:57:33 -0500 Subject: [PATCH] bring unmanaged GCP resources under Terraform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vault-kms.tf: KMS keyring/key for Vault auto-unseal + vault-unseal SA and IAM bindings — all imported from existing GCP resources. prevent_destroy=true on the crypto key to protect against accidental deletion. unmanaged-secrets.tf: 8 secrets that were referenced by live Cloud Run services but had no TF resource — anthropic-api-key, resend-api-key, supabase-service-key, docuseal-webhook-token, stripe-price-professional, stripe-price-founding, stripe-price-family-child, gitea-runner-token. service-account.tf: 4 manually-created SAs — neuron-workspace-dwd, docuseal-storage, analytics-reader, harmonic-search-console. --- servers/gcp/service-account.tf | 30 +++++++++++ servers/gcp/unmanaged-secrets.tf | 88 ++++++++++++++++++++++++++++++++ servers/gcp/vault-kms.tf | 46 +++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 servers/gcp/unmanaged-secrets.tf create mode 100644 servers/gcp/vault-kms.tf diff --git a/servers/gcp/service-account.tf b/servers/gcp/service-account.tf index d97fc5d..ece1e97 100644 --- a/servers/gcp/service-account.tf +++ b/servers/gcp/service-account.tf @@ -128,3 +128,33 @@ resource "google_cloud_run_v2_service_iam_member" "api_apac_public" { role = "roles/run.invoker" member = "allUsers" } + +# ── Service accounts brought under Terraform management ─────────────────────── +# Created manually; imported into TF state. + +resource "google_service_account" "neuron_workspace_dwd" { + account_id = "neuron-workspace-dwd" + display_name = "Neuron Workspace DWD (Gmail access)" + description = "Service account for domain-wide delegation to access neuron@neurontechnologies.ai Gmail" + project = var.project_id +} + +resource "google_service_account" "docuseal_storage" { + account_id = "docuseal-storage" + display_name = "DocuSeal Storage" + description = "Storage backend for DocuSeal document signing platform - read/write access to neuron-docuseal-prod bucket only" + project = var.project_id +} + +resource "google_service_account" "analytics_reader" { + account_id = "analytics-reader" + display_name = "Analytics Reader" + project = var.project_id +} + +resource "google_service_account" "harmonic_search_console" { + account_id = "harmonic-search-console" + display_name = "Harmonic Framework Search Console" + description = "Read-only access to Search Console for harmonic-framework.com" + project = var.project_id +} diff --git a/servers/gcp/unmanaged-secrets.tf b/servers/gcp/unmanaged-secrets.tf new file mode 100644 index 0000000..17a2147 --- /dev/null +++ b/servers/gcp/unmanaged-secrets.tf @@ -0,0 +1,88 @@ +# ── Secrets brought under Terraform management ──────────────────────────────── +# These secrets were created manually (via gcloud or out-of-band provisioning) +# before the Terraform config was written. Secret values (versions) are NOT +# managed by Terraform — they were populated out-of-band and are left intact. +# Terraform owns the secret resource (metadata, replication, IAM) only. +# +# All resources here were imported via `terraform import` — they already exist +# in GCP. Do not add version resources unless you intend to rotate the value. + +# ── Marketing / web services ────────────────────────────────────────────────── + +resource "google_secret_manager_secret" "anthropic_api_key" { + secret_id = "anthropic-api-key" + project = var.project_id + + replication { + auto {} + } +} + +resource "google_secret_manager_secret" "resend_api_key" { + secret_id = "resend-api-key" + project = var.project_id + + replication { + auto {} + } +} + +resource "google_secret_manager_secret" "supabase_service_key" { + secret_id = "supabase-service-key" + project = var.project_id + + replication { + auto {} + } +} + +resource "google_secret_manager_secret" "docuseal_webhook_token" { + secret_id = "docuseal-webhook-token" + project = var.project_id + + replication { + auto {} + } +} + +# ── Stripe price IDs (old naming scheme) ───────────────────────────────────── +# These predate the stripe-price-*-plan naming in stripe-billing.tf. +# Consumed by marketing-prod-* and accounts-prod-* Cloud Run services. + +resource "google_secret_manager_secret" "stripe_price_professional" { + secret_id = "stripe-price-professional" + project = var.project_id + + replication { + auto {} + } +} + +resource "google_secret_manager_secret" "stripe_price_founding" { + secret_id = "stripe-price-founding" + project = var.project_id + + replication { + auto {} + } +} + +resource "google_secret_manager_secret" "stripe_price_family_child" { + secret_id = "stripe-price-family-child" + project = var.project_id + + replication { + auto {} + } +} + +# ── CI infrastructure ───────────────────────────────────────────────────────── + +resource "google_secret_manager_secret" "gitea_runner_token" { + secret_id = "gitea-runner-token" + project = var.project_id + + replication { + auto {} + } +} diff --git a/servers/gcp/vault-kms.tf b/servers/gcp/vault-kms.tf new file mode 100644 index 0000000..021942f --- /dev/null +++ b/servers/gcp/vault-kms.tf @@ -0,0 +1,46 @@ +# ── Vault KMS Auto-Unseal ───────────────────────────────────────────────────── +# Vault running on Legion uses GCP KMS for auto-unseal. Configured in: +# servers/legion/apps/vault.yaml (seal "gcpckms" block) +# Key ring: vault, key: vault-unseal, location: global +# +# CRITICAL: prevent_destroy = true on the crypto key. +# Deleting this key makes Vault permanently sealed — all secrets inaccessible. +# +# These resources were created manually and are imported into TF state. +# Values already exist in GCP — TF is just taking ownership. + +resource "google_kms_key_ring" "vault" { + name = "vault" + location = "global" + project = var.project_id +} + +resource "google_kms_crypto_key" "vault_unseal" { + name = "vault-unseal" + key_ring = google_kms_key_ring.vault.id + purpose = "ENCRYPT_DECRYPT" + rotation_period = "7776000s" # 90 days + + lifecycle { + prevent_destroy = true + } +} + +resource "google_service_account" "vault_unseal" { + account_id = "vault-unseal" + display_name = "Vault KMS Auto-Unseal" + description = "Service account for Vault auto-unseal via GCP KMS. Used by the Vault pod on Legion." + project = var.project_id +} + +resource "google_kms_crypto_key_iam_member" "vault_unseal_encrypter_decrypter" { + crypto_key_id = google_kms_crypto_key.vault_unseal.id + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:${google_service_account.vault_unseal.email}" +} + +resource "google_kms_crypto_key_iam_member" "vault_unseal_viewer" { + crypto_key_id = google_kms_crypto_key.vault_unseal.id + role = "roles/cloudkms.viewer" + member = "serviceAccount:${google_service_account.vault_unseal.email}" +}