Skip to content

ADR 0001: secret 管理を External Secrets Operator + GCP Secret Manager に統一 ​

ステータス ​

提案 (UZU-5262)

コンテキスト ​

UZU の K8s manifest では、すべてのサービス (backend / mdms_jp / uzutokyo / studio_editor_backend / auth / analytics_server / 以降の追加サービス) が、各 overlay ({service}/k8s/{env}/kustomization.template.yaml) の secretGenerator.literals に 機密値を平文で直書き している。

具体的には DB password, R2 HMAC key, Cloudflare API token, LiveKit secret, OAuth client secret などが git 履歴に永久に残っている。

問題 ​

  • private repo であっても repo 読み取り権限を持つ全員が値を見られる
  • rotation のたびに dev/stg/prd の複数 PR / 複数箇所を更新する必要がある (SoT が分散)
  • 退職者・権限変更時の invalidation には先に rotation が必須

決定 ​

External Secrets Operator (ESO) を 3 クラスタ全てに導入し、GCP Secret Manager を Source of Truth とする。K8s Secret は ESO が ExternalSecret リソース経由で同期する。

機密の配置 (ハイブリッド) ​

env 境界を GCP プロジェクト単位 で物理的に作るため、Secret Manager を 2 階層で使う:

種別配置先命名規則ClusterSecretStore
env 固有uzu-{env} の Secret Manager{service}-{key-kebab} (env suffix なし、project が env を表現)gsm-uzu-{env}
env 共通uzu-shared の Secret Manager{prefix}-{key-kebab} (env なし)gsm-uzu-shared

例:

  • uzu-dev/Secret Manager/uzutokyo-database-url ← env 固有
  • uzu-shared/Secret Manager/cloudflare-api-token ← env 共通 (dev/stg/prd で同一値)

env 共通は rotation を 1 箇所更新するだけで全 env に反映される。

ESO のインストール ​

Helm chart は使わず、公式 release の external-secrets.yaml (install.yaml) を infra/k8s/external-secrets/base/install.yaml に pin する。

namespace は default のまま運用する。install.yaml は Helm chart の template 展開出力で、cert-controller の --service-namespace / --secret-namespace と webhook の --dns-name に default が hardcode されているため、別 namespace で動かすには毎リリースで args patch が必要になる。UZU の他サービス (backend / mdms_jp / uzutokyo / auth / studio_editor_backend 等) も default namespace で動いているので素直に揃える。

apply は infra_k8s_external_secrets.yml workflow が infra/k8s/external-secrets/** の差分検知で自動実行する (terraform_apply.yml と同じパターン: dev/stg は main push、prd は tag push)。手元から kubectl apply -k を直接打つのは検証時の例外のみ。

kubectl apply は server-side で行う (CRD schema が ~250KB と大きく client-side apply の last-applied-configuration annotation 上限 262144 byte を超えるため)。さらに base (CRD + controller) と overlay (ClusterSecretStore = CR) は 2 段に分けて間で CRD の establish を待つ (CRD register 完了前に CR apply が走ると no matches for kind で失敗する)。

Helm を採用しない理由:

  • UZU は他に Helm を使っていない。ESO 単独のために helm provider を terraform に追加する利得が小さい
  • CRD のバージョン差分は manifest 比較で十分追える (install.yaml を release ごとに置き換える)
  • 既存の kustomize 流儀と一貫する

Workload Identity 設計 ​

各環境に external-secrets@uzu-{env}.iam.gserviceaccount.com を作成し、クラスタ内の KSA external-secrets/external-secrets から WI 経由で impersonate させる (GSA 名は GCP の account_id 制約 6 文字以上を満たすため eso ではなく external-secrets を採用)。GSA には以下 2 つの IAM:

  1. 自プロジェクト (uzu-{env}) の roles/secretmanager.secretAccessor — env 固有 secret 用 (infra/terraform/{env}/eso.tf)
  2. uzu-shared の roles/secretmanager.secretAccessor — env 共通 secret 用 (infra/terraform/shared/eso.tf)

段階的移行 (UZU-5262 のスコープ) ​

  1. 検証フェーズ (本 ADR 提案時点) — dev クラスタに ESO 基盤を導入し、env 固有 (uzutokyo の DATABASE_URL) と env 共通 (backend の CLOUDFLARE_API_TOKEN) の 2 系統で sync 動作を確認する。中間段階の ExternalSecret は別名 ({service}-secret-external) で K8s Secret を生成し、Pod は既存の secretGenerator と並行する形で envFrom を 2 段重ねる (後勝ちで ESO 経由の値が採用される)
  2. 本実装フェーズ — secret 単位で順次 ExternalSecret に置き換え、kustomization.template.yaml から secretGenerator.literals を削除する。stg / prd へ展開
  3. clean up — git 履歴に残った漏洩 secret は別 issue で rotation 対応

代替案 (採用しなかったもの) ​

案 S: 全 secret を uzu-shared に集約 ​

env 固有も {service}-{env}-{key} 形式で uzu-shared に置き、ClusterSecretStore を 1 つにする案。

不採用理由: env 境界が project IAM で物理的に切れない。external-secrets@uzu-dev GSA が uzu-shared 全体の secretAccessor を持つと、dev クラスタから prd 用 secret が読める状態になる。これを防ぐには IAM Condition で secret 名の正規表現マッチが必要だが、Condition の書き間違いで漏洩する事故リスクがある。

案 V: Sealed Secrets / SOPS で git にコミット ​

公開鍵で暗号化された値を git に置き、クラスタ内の controller が復号する案。

不採用理由: rotation は依然として「git に commit + push」が必要 (SoT が git のままになる)。privacy 観点の改善は限定的で、退職時の鍵 invalidation 運用も追加で必要になる。GCP の Secret Manager をすでに使っている前提では ESO の方が薄い差分で導入できる。

案 H': env プロジェクトのみで env 共通も複製 ​

env 共通 secret も dev/stg/prd の各 Secret Manager に同じ値を 3 個投入する案。

不採用理由: rotation が 3 箇所更新になる。値が drift するリスクが復活する。

影響 ​

  • インフラ: 各 env スタックに eso.tf、shared スタックに越境 IAM を追加。infra/k8s/external-secrets/ を新設
  • 開発フロー: 新規 secret 追加は Secret Manager への投入 + ExternalSecret 追記の 2 ステップ。詳細は別途 runbook を整備する
  • security: 平文 secret が git から消える (履歴に残った分は別 issue で rotation)

参照 ​

  • External Secrets Operator docs
  • UZU-5262 (本 ADR の親 issue)
  • 既存パターン: uzu-shared/Secret Manager の translator-github-app-* (env 共通 secret の前例)