Skip to main content

Secrets & Identity Management

This page describes the design of secret management and identity/access control across the Shokunin Platform. It covers how secrets are stored, who can access them, how they flow to the places that need them, and how new developers are onboarded.

Three Core Principles

Every decision in this design follows three rules:
  1. GCP Secret Manager is the single source of truth. No secret has an authoritative home anywhere else. GitHub Actions secrets, Vercel environment variables, and local .env files are all derived copies populated from Secret Manager.
  2. Terraform is the pipe, not the vault. Terraform reads values from Secret Manager (or from TF_VAR_* environment variables during bootstrapping) and distributes them outward. It never invents secret values.
  3. A human manually sets a secret exactly once. After initial creation, all distribution and rotation is automated.

Secret Namespaces

All secrets live in GCP Secret Manager in project shokunin-480309. They are organised into two namespaces by naming convention and access scope.

Platform namespace

Naming pattern: shokunin-{env}-{component} These secrets are shared across the team and consumed by CI/CD, Vercel runtime, and Cloud Run services.
shokunin-dev-dolt-db-password and shokunin-dev-beads-api-key are created by the staging and production Terraform environments, not dev-shared. The dev-shared environment does not deploy a Dolt VM or Beads API Cloud Run service.
All platform secrets carry a standard label set:
The required = "true" label drives monitoring alerts — any required secret with zero active versions triggers an alert.

Tenant namespace

Naming pattern: shokunin-{env}-{tenant_id}-{component} Tenant-scoped secrets are provisioned per tenant by the tenant Terraform module. They are isolated from platform secrets and from each other.

Identity Map

The table below describes every actor in the system, how it authenticates, and what it can access.

Service accounts

Every service account is managed in Terraform under platform/environments/foundation/ (project-level SAs) or platform/modules/iam/ (environment-level SAs).

Secret Flow Pipeline

GCP Secret Manager is the authoritative store. Terraform reads values from it and distributes them to every consumer. The local .env is populated by a developer script (scripts/env-sync) that reads from the same source.

Bootstrap exceptions

Two secrets cannot be stored in Secret Manager because accessing Secret Manager itself requires a credential — a circular dependency. These are the only intentional exceptions to Principle 1: These are set by the operator before running terraform apply and are never committed or persisted:

Workload Identity Federation (WIF)

GitHub Actions authenticates to GCP without any stored credentials using Workload Identity Federation. The GitHub OIDC token is exchanged for a short-lived GCP access token at runtime.
WIF is configured in terraform/platform/environments/foundation/wif.tf. The pool is scoped to the Horizon-AI-dev GitHub organisation — tokens from other organisations are rejected.
WIF covers GitHub Actions → GCP only. Deployments from GitHub Actions to Vercel still require a VERCEL_TOKEN in GitHub secrets — Vercel does not currently support OIDC federation for inbound API authentication.

Terraform Modules

Secret and IAM management is split across four Terraform modules:

SA key lifecycle

Two service account keys are managed entirely by Terraform — no manual gcloud iam service-accounts keys create steps:
  • vercel-caller-sa key — created in iam module, stored in shokunin-dev-vercel-caller-sa-key, pushed to Vercel as GOOGLE_SERVICE_ACCOUNT_KEY by the vercel-env module.
  • firebase-admin-sa key — created in iam module, stored in shokunin-dev-firebase-admin-key, pushed to Vercel as GOOGLE_FIREBASE_ADMIN_KEY by the vercel-env module.
To rotate a key: run terraform apply. Terraform replaces the google_service_account_key resource, updates Secret Manager, and re-pushes to Vercel in a single operation.

Developer Onboarding

Adding a new developer is a two-step process split between an admin and the developer.

Step 1 — Admin: grant bootstrap access

Create the developer’s identity file and grant them the permission needed to run their own sandbox terraform.
Foundation grants serviceAccountTokenCreator on shokunin-dev-platform-sa to the developer’s Google account. This is the bootstrap permission that allows ./scripts/dev-setup to apply the developer’s own sandbox — without it, SA impersonation fails and dev-setup exits immediately.
serviceAccountTokenCreator lives in foundation (not in dev/main.tf) to break a circular dependency: dev/main.tf is applied via scripts/tf, which requires this very permission to impersonate the platform SA.

Step 2 — Developer: one-command local setup

dev-setup handles everything in order:
  1. Verifies prerequisites (gcloud, terraform, bun) and that gcloud is authenticated
  2. Creates config/developers/alice.tfvars if it does not already exist
  3. Runs ./scripts/tf dev alice apply — creates the developer’s Firestore database and grants Secret Manager read access
  4. Calls scripts/env-sync to populate .env from Secret Manager
  5. Runs a health check to verify the environment is working

scripts/env-sync

env-sync is the developer’s tool for keeping their local .env in sync with Secret Manager. It:
  • Authenticates using the developer’s own GCP user account (no SA key required)
  • Reads all secrets from the shokunin-dev-* platform namespace
  • Writes values to .env, skipping any that are already set
  • Prints a summary: what was updated, what was skipped, what is missing a version
env-sync requires gcloud auth login to have been run first. The developer’s GCP account must have been added to the platform by an admin (Step 1 above) before env-sync will have access to secrets.

Secret Monitoring

A Cloud Monitoring dashboard deployed via Terraform provides visibility into the state of all secrets:
  • All platform secrets are listed with their version count and last rotation date
  • Required secrets (required = "true" label) with zero active versions trigger an alert
  • Expiring secrets — any secret version with an expire_time set triggers an alert 30 days before expiry
The dashboard URL is output from Terraform after dev-shared apply. Developers can access it directly in the GCP Console under Monitoring → Dashboards.

Runbooks

Adding a new shared secret

  1. Create the Secret Manager container in platform/modules/secret-manager/main.tf
  2. Add the appropriate label (required, consumer, purpose)
  3. Add a vercel_project_environment_variable resource in platform/modules/vercel-env/main.tf if the secret is consumed by Vercel
  4. Run ./terraform/scripts/tf dev-shared apply
  5. Set the initial value: gcloud secrets versions add shokunin-dev-<name> --data-file=-

Rotating a secret

For secrets whose values are managed externally (not SA keys):
For SA key secrets (vercel-caller-sa-key, firebase-admin-key): simply run terraform apply — Terraform recreates the key, updates Secret Manager, and re-pushes to Vercel.

Removing a developer’s access

Delete or remove the IAM binding in their developer .tfvars and re-apply. The developer’s GCP user account will lose secretmanager.secretAccessor on the platform namespace immediately.