> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shokunin.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Secrets & Identity Management

> How secrets, service accounts, and IAM are organised across the Shokunin Platform

# 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.

| Secret ID                               | Purpose                                              | How the value is set                                                               |
| --------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `shokunin-dev-dolt-db-password`         | Dolt MySQL root password                             | `TF_VAR_dolt_db_password` on first `terraform apply` — **staging/production only** |
| `shokunin-dev-beads-api-key`            | Beads API authentication key                         | `TF_VAR_beads_api_key` on first `terraform apply` — **staging/production only**    |
| `shokunin-dev-firebase-admin-key`       | Firebase Admin SDK service account JSON              | Terraform creates and stores the SA key automatically                              |
| `shokunin-dev-firebase-browser-api-key` | Firebase client SDK API key                          | Human runs `gcloud secrets versions add` once                                      |
| `shokunin-dev-liveblocks-secret-key`    | Liveblocks server-side key                           | Human runs `gcloud secrets versions add` once                                      |
| `shokunin-dev-eraser-api-token`         | Eraser diagram API token (shared)                    | Human runs `gcloud secrets versions add` once                                      |
| `shokunin-dev-basehub-token`            | BaseHub CMS token (shared)                           | Human runs `gcloud secrets versions add` once                                      |
| `shokunin-dev-vercel-caller-sa-key`     | SA key for Vercel → GCP API auth                     | Terraform creates and stores the SA key automatically                              |
| `shokunin-dev-codespace-iap-key`        | Codespace IAP SA key (manual fallback tunnel access) | Terraform creates and stores the SA key automatically                              |

<Note>
  `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.
</Note>

All platform secrets carry a standard label set:

```
managed_by  = "terraform"
environment = "dev"
namespace   = "platform"
required    = "true" | "false"
consumer    = "vercel" | "cloud-run" | "local-dev" | "ci"
purpose     = "db-password" | "api-key" | "sa-key" | "sdk-token"
```

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.

| Actor                          | Authenticates as                                                                               | Can access                                                                                                                                                     |
| ------------------------------ | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Human developer**            | GCP user account (`gcloud auth login`)                                                         | Secret Manager platform namespace (read-only); SA impersonation of `dev-platform-sa` for Terraform                                                             |
| **Local Terraform**            | `dev-platform-sa` via short-lived token (SA impersonation)                                     | Secret Manager (admin); GitHub via `TF_VAR_github_token`; Vercel via `TF_VAR_vercel_token`                                                                     |
| **GitHub Actions → GCP**       | `shokunin-dev-gha-sa` via Workload Identity Federation (keyless)                               | Artifact Registry (write); Cloud Run (deploy)                                                                                                                  |
| **GitHub Actions → Vercel**    | `VERCEL_TOKEN` stored in GitHub secrets (populated by Terraform)                               | Vercel API (deploy)                                                                                                                                            |
| **Vercel runtime → Cloud Run** | `vercel-caller-sa` JSON key in Vercel env var (populated by Terraform)                         | Cloud Run service (invoke)                                                                                                                                     |
| **Cloud Run service**          | `shokunin-dev-run-sa` workload identity                                                        | Secret Manager (accessor on Dolt password, Beads API key); Dolt database                                                                                       |
| **Dolt VM**                    | `shokunin-dev-dolt-vm-sa` workload identity                                                    | Secret Manager (accessor on Dolt password)                                                                                                                     |
| **Firebase Admin SDK**         | `shokunin-dev-firebase-admin` SA key in Vercel env var (populated by Terraform)                | Firebase Authentication; Firestore admin; `roles/run.viewer`, `roles/container.viewer`, `roles/cloudtasks.viewer` (used by Infrastructure Dashboard API route) |
| **OpenCode agent**             | `opencode-agent` SA (read-only)                                                                | GCP project viewer, logging viewer, monitoring viewer                                                                                                          |
| **Workshop Provisioner**       | `shokunin-dev-provisioner-sa` workload identity on Cloud Run; OIDC token issued by Cloud Tasks | GKE API (create Deployments/Services in tenant namespaces); Firestore (write provisioning state)                                                               |

### Service accounts

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

| Service account                 | Purpose                                                                 |
| ------------------------------- | ----------------------------------------------------------------------- |
| `shokunin-dev-platform-sa`      | Runs Terraform for dev-shared and developer sandbox environments        |
| `shokunin-dev-tenant-sa`        | Runs Terraform for per-tenant resources                                 |
| `shokunin-dev-gha-sa`           | GitHub Actions CI/CD deployments (WIF, no key)                          |
| `shokunin-dev-run-sa`           | Cloud Run service identity                                              |
| `shokunin-dev-dolt-vm-sa`       | Dolt VM identity                                                        |
| `shokunin-dev-codespace-iap`    | IAP tunnel SA (manual fallback for direct Dolt VM access)               |
| `shokunin-dev-firebase-admin`   | Firebase Admin SDK operations; Infrastructure Dashboard GCP API queries |
| `shokunin-dev-vercel-caller-sa` | Vercel runtime → GCP API authentication                                 |
| `shokunin-dev-provisioner-sa`   | Workshop Provisioner Cloud Run identity                                 |
| `opencode-agent`                | Read-only AI coding assistant diagnostics                               |

***

## 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.

```
Human sets value once (gcloud secrets versions add  OR  TF_VAR_* env var)
        │
        ▼
GCP Secret Manager  ←── single source of truth
        │
        ├── [terraform apply] ──► GitHub Actions secrets
        │                          VERCEL_TOKEN
        │                          VERCEL_ORG_ID
        │                          VERCEL_PROJECT_ID
        │
        ├── [terraform apply] ──► Vercel environment variables
        │                          GOOGLE_SERVICE_ACCOUNT_KEY  (vercel-caller SA key)
        │                          GOOGLE_FIREBASE_ADMIN_KEY   (firebase-admin SA key)
        │                          NEXT_PUBLIC_FIREBASE_*      (browser SDK config)
        │                          LIVEBLOCKS_SECRET_KEY
        │                          BEADS_API_KEY               (staging/production only)
        │                          ERASER_API_TOKEN
        │                          BASEHUB_TOKEN
        │                          AGENT_HOST_SUFFIX           (e.g. dev.shokunin.app)
        │                          GCP_PROJECT_ID
        │                          GCP_REGION
        │                          PROVISIONER_URL             (Provisioner Cloud Run URL)
        │                          PROVISIONER_SA_EMAIL        (for Cloud Tasks OIDC)
        │                          CLOUD_TASKS_QUEUE_PATH      (full queue resource path)
        │
        ├── [secret_key_ref]  ──► Cloud Run runtime env vars (at container start)
        │                          DOLT_PASSWORD
        │                          BEADS_API_KEY
        │
        └── [scripts/env-sync] ──► Local .env file
                                    Developer authenticates with their GCP user account.
                                    No manual secret handling required.
```

### 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:

| Secret                | Where it lives                     | Why                                                                                                                                                          |
| --------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `TF_VAR_github_token` | Developer's shell environment only | Terraform needs this to authenticate to GitHub to write GitHub secrets. Storing it in Secret Manager would require a working Terraform apply to retrieve it. |
| `TF_VAR_vercel_token` | Developer's shell environment only | Same reason — Terraform needs this to authenticate to Vercel before it can write Vercel env vars.                                                            |

These are set by the operator before running `terraform apply` and are never committed or persisted:

```bash theme={null}
export TF_VAR_github_token="ghp_..."
export TF_VAR_vercel_token="..."
./terraform/scripts/tf dev-shared apply
```

***

## 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.

```
GitHub Actions workflow
        │  GitHub OIDC token
        ▼
GCP Security Token Service
        │  Short-lived access token
        ▼
shokunin-dev-gha-sa (impersonated)
        │
        ├── Artifact Registry (push Docker images)
        └── Cloud Run (deploy services)
```

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.

<Note>
  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.
</Note>

***

## Terraform Modules

Secret and IAM management is split across four Terraform modules:

| Module                              | What it manages                                                                                                                     |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `platform/environments/foundation/` | State bucket; project-level SAs (`dev-platform-sa`, `dev-tenant-sa`, `opencode-agent`); WIF pool and provider; project IAM bindings |
| `platform/modules/iam/`             | Environment-level SAs (Cloud Run, Dolt VM, Codespace IAP, GHA, Firebase Admin, Vercel caller); IAM bindings scoped to resources     |
| `platform/modules/secret-manager/`  | Secret Manager containers and versions for all platform secrets                                                                     |
| `platform/modules/github-secrets/`  | Pushes secrets from Secret Manager → GitHub Actions secrets                                                                         |
| `platform/modules/vercel-env/`      | Pushes secrets from Secret Manager → Vercel environment variables                                                                   |

### 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.

```bash theme={null}
# 1. Create the developer's tfvars (no secrets — identity only)
cat > terraform/config/developers/alice.tfvars <<EOF
developer_id    = "alice"
developer_email = "alice@horizon.ai"
EOF

git add terraform/config/developers/alice.tfvars
git commit -m "add developer: alice"
git push

# 2. Add alice@horizon.ai to developer_emails in foundation.tfvars, then apply
./scripts/tf foundation apply
```

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.

<Note>
  `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.
</Note>

### Step 2 — Developer: one-command local setup

```bash theme={null}
# Authenticate with GCP (once per machine/container)
gcloud auth login
gcloud auth application-default login

# Run the setup script
./scripts/dev-setup alice alice@horizon.ai
```

`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

```bash theme={null}
# Re-sync at any time (safe to re-run)
./scripts/env-sync
```

<Warning>
  `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.
</Warning>

***

## 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):

```bash theme={null}
# Add a new version (GCP automatically makes it the latest active version)
echo -n "new-value" | gcloud secrets versions add shokunin-dev-<name> --data-file=-

# Re-push to consumers
./terraform/scripts/tf dev-shared apply
```

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.
