目次
29 章

シークレット運用

5部の3番目の章です。K8s Secret の base64 の限界と etcd encryption-at-rest の意味から始め、保存 · 回転 · 注入 · 監査の4軸でシークレットライフサイクルを扱います。sealed-secrets · external-secrets · SOPS の3つのオプションの比較、IRSA と組み合わせたパスワード0運用 (AWS API は IRSA、DB は RDS IAM auth)、envFrom vs mount の回転の違い、RBAC でネームスペース単位の分離、Audit log と GuardDuty の監査の観点までを本格的な運用マニュアルとして束ねます。

5部 (運用 · デバッグ · コスト) の3番目の章です。本書の複数の章 (6章 ConfigMap · Secret14章 RBAC / NetworkPolicy / ResourceQuota16章 RBAC / ServiceAccount 深掘り18章 CRD と Operator20章 GitOps23章 DB 連携) でシークレットが断片的に登場しました。本章はその断片を1つの運用マニュアルとして束ねます。「secret YAML をそのまま git にコミットするな」の次の段階 — プロダクションのシークレットライフサイクル全体を扱います。

本章の目標は 保存 · 回転 · 注入 · 監査の4軸が1つの運用モデルとして整理された状態 です。sealed-secrets / external-secrets / SOPS の3つの道具の違いを比較し、IRSA と組み合わせた「パスワード0」運用を本格的に扱ってシークレットガバナンスの基準線を定めます。

K8s Secret の限界 — base64 は暗号化ではない #

6章 ConfigMap · Secret §「Secret の本質的な限界」で挙げた一行が本章の出発点です。

ありふれた Secret — base64 の意味
apiVersion: v1
kind: Secret
metadata:
  name: myshop-api
type: Opaque
data:
  DATABASE_PASSWORD: cG9zdGdyZXNAcHJvZA==   # postgres@prod

data の値は base64 エンコーディングであるだけで暗号化ではありません。base64 -d の一行で誰でも原本を見ることができます。このマニフェストが git にコミットされると、秘密がそのまま外部に露出します。

etcd encryption-at-rest の意味と限界 #

K8s API Server は etcd にオブジェクトを保存します。EKS は基本的に etcd のディスクが KMS で暗号化されていますが、etcd の中のオブジェクト自体 は平文です。ノードの etcd データにアクセスできる誰かは Secret の値を見ることができるという意味です。

これを防ぐのが encryption-at-rest です。

terraform — EKS の Secret encryption 有効化
module "eks" {
  # ...
  encryption_config = [{
    provider_key_arn = aws_kms_key.eks.arn
    resources        = ["secrets"]
  }]
}

この設定がオンになっていれば、etcd に保存される前に Secret オブジェクト自体が KMS キーで暗号化されます。運用クラスタの基本セットアップです。ただし これがオンでもマニフェストの git コミット問題は解けません — etcd の中の保護であって、マニフェスト段階の保護ではありません。

この2つを分けておくのがシークレット運用の最初のメンタルモデルです。

位置保護道具
git repo の中のマニフェストsealed-secrets / external-secrets / SOPS
クラスタの中の etcdencryption-at-rest (KMS)
Pod の中の環境変数 / ファイルPod 隔離、RBAC、監査

シークレット運用の4軸 #

production のシークレットライフサイクルは4つの軸に分解されます。

問い
保存 (Storage)本当の秘密値がどこにあるか
回転 (Rotation)パスワードがどのように更新されるか
注入 (Injection)Pod がその値をどう受け取るか
監査 (Audit)誰がいつその値にアクセスしたか

ほとんどのシークレット事故は1軸の欠陥ではなく 複数の軸の漏れが合わさった結果 です。1つの道具だけを導入すれば1つ2つの軸は解決されますが、他の軸が空いていれば結局セキュリティが壊れます。4軸をまとめて見るのが運用の出発点です。

注入パターン — envFrom vs mount #

秘密を Pod に注入する2つの標準パターンがあります。

envFrom — 環境変数として注入
spec:
  containers:
    - name: api
      envFrom:
        - secretRef:
            name: myshop-api-db
volumeMount — ファイルとして注入
spec:
  containers:
    - name: api
      volumeMounts:
        - name: db-secret
          mountPath: /var/secrets/db
          readOnly: true
  volumes:
    - name: db-secret
      secret:
        secretName: myshop-api-db

2つのパターンの決定的な違いは 回転時の動作 です。

観点envFromvolumeMount
Secret 更新時Pod の中の環境変数はそのまま (Pod 起動時点でのみ固定)約1分以内にファイルが自動更新
回転対応Pod 再起動が必要アプリケーションがファイルを再び読むだけでよい
デバッグenv コマンドで即座に確認ファイルパス確認 + cat

23章 DB 連携 で挙げた「Secret 更新時 kubectl rollout restart が必要」の罠が envFrom の典型例です。回転が頻繁なシークレットは volumeMount のほうが運用的に自然です。アプリケーションコード側でファイルを定期的に再読み込みするようにすれば、Pod 再起動なしに新しい秘密が適用されます。

ただし環境変数モデルのほうがシンプルで、ほとんどの 12 factor app は環境変数を期待します。回転頻度とアプリケーションの要件を考慮して2つの間で選び取るのが標準です。

sealed-secrets — git に安全にコミット #

Bitnami の sealed-secrets は秘密を git にコミットしても安全な形で封印 する道具です。

sealed-secrets インストール
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets \
  -n kube-system

インストール後、コントローラがクラスタの中で RSA キーペアを生成します。ユーザーは公開鍵で秘密を封印し、コントローラが秘密鍵で復号します。

秘密を封印
echo -n "postgres@prod" | kubectl create secret generic myshop-db \
  --dry-run=client --from-file=password=/dev/stdin -o yaml \
  | kubeseal --controller-namespace kube-system \
             --controller-name sealed-secrets \
             --format yaml \
  > sealedsecret.yaml
sealedsecret.yaml — git にコミット可能
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: myshop-db
spec:
  encryptedData:
    password: AgB7K8x...  # クラスタのコントローラのみ復号可能
  template:
    type: Opaque

このマニフェストを git にコミットして ArgoCD でクラスタに同期すると、コントローラが自動的に復号して一般の Secret を作ります。20章 GitOps §「秘密の単一ソースモデル」で挙げた3つのオプションのうち最初のものが本節の道具です。

sealed-secrets のトレードオフ #

  • 長所 — 外部依存がありません。クラスタの中のコントローラ1個で終わります。
  • 短所 — コントローラの秘密鍵がクラスタの中にあるので、クラスタのバックアップがすなわち鍵のバックアップです。dev / staging / prod の鍵が異なるので 環境間の SealedSecret マニフェストは互換性がありません。
  • 回転 — 秘密の回転時に新しく封印して git にコミットする必要があります — 手動の段階です。

小さなチーム + 単一クラスタ環境では最もシンプルなオプションです。環境間のマニフェスト共有 / 自動回転が必要になれば次の道具が適しています。

external-secrets — 外部の秘密ストアと同期 #

18章 CRD と Operator の Operator パターンであり、23章 DB 連携 で本格的に扱った道具です。AWS Secrets Manager / HashiCorp Vault / GCP Secret Manager の秘密を K8s Secret へ自動同期します。

核心となる運用上の違いを sealed-secrets と比較すると次のようになります。

観点sealed-secretsexternal-secrets
秘密の source of truthgit repo外部の秘密ストア (AWS SM など)
マニフェストの内容封印された値 (暗号文)秘密の参照 (path / key)
回転手動 (新しい封印 + git push)自動 (外部ストアで更新、ESO が自動同期)
環境間のマニフェスト環境ごとに異なる (鍵が異なる)環境ごとに同一 (参照のみ異なる)
外部依存なしAWS SM / Vault のコスト + 可用性

運用環境で回転が頻繁な秘密 (DB パスワード、API キーなど) には external-secrets が自然です。ESO 自体のマニフェストは 23章 で扱ったそのままです — ClusterSecretStore + ExternalSecret の2つの CRD です。

ESO の回転 — 自動回転の仕組み #

ExternalSecret — 回転の自動化
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: myshop-api-db
spec:
  refreshInterval: 1h   # 1時間ごとに外部ストア確認
  # ...

refreshInterval が核心 — ESO が外部ストアを定期的に polling して変更を検知し K8s Secret を更新します。AWS Secrets Manager の自動回転 (Lambda ベース) と連携すれば、パスワードの回転が完全に自動化されます。

自動回転の一連の流れ
1. AWS Secrets Manager が Lambda を呼び出す (30 日周期など)
2. Lambda が RDS に新しいパスワードを適用 + Secrets Manager を更新
3. ESO が1時間以内に変更を検知し K8s Secret を更新
4. Reloader (別のコンポーネント) が Secret の変更を検知し Pod rollout restart
5. myshop-api Pod が新しいパスワードで RDS 接続

このサイクルが回れば 人が一度もパスワードを触らずに四半期回転が自動化 されます。運用シークレットの目標の1つです。

SOPS — 小さなチームのシンプルオプション #

Mozilla の SOPS (Secrets OPerationS) は sealed-secrets / ESO と異なる結の道具です。ローカルでファイルを暗号化 し、そのファイルを git にコミットします。

SOPS + age — 最もシンプルなセットアップ
# age キーペア生成 (一度)
age-keygen -o ~/.config/sops/age/keys.txt

# ありふれた secret YAML 作成
cat > secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: myshop-db
stringData:
  password: postgres@prod
EOF

# 暗号化
sops --age $(cat ~/.config/sops/age/keys.txt | grep public | cut -d: -f2) \
     --encrypt --in-place secret.yaml

暗号化されたファイルは鍵がなければ平文が見えません。git にコミットしても安全です。適用時点では SOPS が復号してありふれたマニフェストへ解いた後 kubectl apply します。

ArgoCD と連携するには helm-secrets または argocd-vault-plugin のような補助道具が必要です。AWS KMS と連携すれば鍵管理を AWS に委任できるので運用負担が減ります。

SOPS の位置 #

  • 長所 — 最もシンプルです。1ファイル = 1つの秘密の束なのでメンタルモデルが直観的です。
  • 短所 — 自動回転なし、環境間の鍵管理に手がかかります。秘密が増えるとファイル管理が煩わしくなります。

小さなチームの単一環境 + 秘密が10個未満の場合に自然です。秘密が増えたり回転の自動化が必要になれば ESO へ移るのが自然な流れです。

3つの道具の決定ツリー #

シークレット道具の選択
- 外部の秘密ストアを使わずクラスタの中で完結させたい
  -> sealed-secrets

- AWS / GCP / Vault のような外部ストアがすでにある
  + 自動回転が運用要件
  -> external-secrets (ESO)

- 小さなチーム + 単一環境 + 秘密の数が少ない
  + 1ファイル = 1つの秘密のシンプルさが良い
  -> SOPS

- 上の3つの組み合わせ (インフラ秘密は SOPS、アプリ秘密は ESO など)
  -> 可能だが運用負担が増加

本書の標準経路は 21 ~ 26 章 で扱ったそのまま — AWS Secrets Manager + External Secrets Operator です。EKS 環境の運用標準であり、23章 の RDS 秘密の自動同期がこのモデルの本格的な応用です。

IRSA と連携した「パスワード0」運用 #

最も進んだパターンは パスワード自体をなくすこと です。16章 RBAC / ServiceAccount 深掘り で扱った IRSA がこのパターンの土台です。

2つの仕組みの組み合わせ #

運用ワークロードの外部の認証情報は大きく2つに分かれます。

パスワード0の2つの組み合わせ
[AWS API 呼び出し — S3, Secrets Manager, CloudWatch]
   -> IRSA + projected token + STS AssumeRoleWithWebIdentity
   -> 静的キーなし、トークンは1時間自動回転

[DB 接続 — RDS PostgreSQL / MySQL]
   -> RDS IAM auth + 15分のIAMトークン
   -> DB パスワードなし、IAM 権限で認証

2つの仕組みを組み合わせると myshop-api のどこにも永続的な秘密がない運用モデル が作られます。パスワードの回転を気にする必要がなく、すべてのアクセスが CloudTrail に記録されます。

RDS IAM auth の適用 #

Python — IRSA トークンで RDS 接続
import os
import boto3
import psycopg2

def get_db_connection():
    rds_client = boto3.client("rds")
    token = rds_client.generate_db_auth_token(
        DBHostname=os.environ["DB_HOST"],
        Port=5432,
        DBUsername=os.environ["DB_USER"],
        Region="ap-northeast-2",
    )

    return psycopg2.connect(
        host=os.environ["DB_HOST"],
        port=5432,
        user=os.environ["DB_USER"],
        password=token,           # パスワードではなく IAM トークン
        dbname=os.environ["DB_NAME"],
        sslmode="require",
    )

ここで boto3.client("rds") が IRSA の projected token で自動認証され、generate_db_auth_token が15分のトークンを作ります。K8s Secret も、AWS Secrets Manager の秘密も必要ありません。

「パスワード0」の限界 #

  • トークンの失効 — 15分ごとに更新しなければならないので long-lived connection との連携がやっかいです。プーラーを間に置くとプーラー自体がトークンを受け取らなければなりません。
  • PostgreSQL ユーザーの設定が必要rds_iam グループにユーザーを追加して grants を定めておく必要があります。
  • すべての DB が対応していない — Aurora MySQL / PostgreSQL は対応、一部の古い RDS エンジンは非対応。
  • PgBouncer transaction pooling と一緒に使いにくい23章 §「transaction pooling の罠」で挙げた制約です。

この限界のため、本書の標準経路は 伝統的なパスワード + Secrets Manager + ESO + IRSA の組み合わせです。「パスワード0」は一層セキュリティが厳格な環境で一部のワークロードに適用するオプションです。運用負担とセキュリティ強度の均衡 を一通り評価する必要があります。

RBAC との連携 — ネームスペース単位の分離 #

シークレット自体の運用だけでなく 誰がそのシークレットを読めるか もシークレットセキュリティの核心です。14章 RBAC / NetworkPolicy / ResourceQuota の RBAC モデルが本節のキーです。

namespace 単位の secret 読み取り権限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
  namespace: myshop
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch"]

この Role を myshop ネームスペースの ServiceAccount にのみ付与すれば、他のネームスペースのワークロードは myshop の秘密を読めません。 1つのクラスタの中でチーム別隔離の基本セットアップです。

ServiceAccount トークンの無効化 #

14章 §「ServiceAccount トークンの自動マウント解除」のパターンがセキュリティの最後の安全線です。

トークンの自動マウント解除
apiVersion: v1
kind: ServiceAccount
metadata:
  name: myshop-api
  namespace: myshop
automountServiceAccountToken: false

この一行を入れておけば、Pod が侵害されても K8s API に直接アクセスするトークンがありません。IRSA が必要なワークロードだけが明示的にトークンを受け取るようにし、それ以外はすべてオフにするのがセキュリティガイドの定番の推奨です。

dev / staging / prod の鍵の分離 #

sealed-secrets の場合は環境ごとにコントローラの鍵が分離されていてこそ自然であり、ESO の場合は環境別の IRSA Role の trust policy が分離されている必要があります。prod の鍵が dev コントローラで復号可能ならセキュリティの大きな穴 です。

20章 GitOps の環境別の分離が本節の鍵の分離と自然に連動します。環境の分離はマニフェストの次元だけでなく、秘密鍵の次元でも一貫して定められている必要があります。

監査 — 誰がいつ何にアクセスしたか #

シークレット事故の事後分析には audit が必須です。3つの監査ツールがあります。

K8s Audit log #

terraform — EKS audit log 有効化
module "eks" {
  # ...
  cluster_enabled_log_types = ["api", "audit", "authenticator"]
}

この設定がオンになっていれば、すべての API リクエストが CloudWatch Logs の audit log グループに記録されます。「どの ServiceAccount がいつ myshop-api-db Secret を読んだか」が追跡可能になります。

audit log クエリ — CloudWatch Insights
fields @timestamp, user.username, verb, objectRef.resource, objectRef.name
| filter objectRef.resource = "secrets"
| filter verb in ["get", "list"]
| sort @timestamp desc
| limit 100

四半期に一度ずつこのクエリを回して、異常なアクセスパターンがないかを点検するのが標準です。26章 運用チェックリスト の四半期セキュリティ点検項目に追加するのによいです。

AWS CloudTrail — Secrets Manager アクセス #

Secrets Manager アクセス履歴
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue \
  --max-results 50

AWS Secrets Manager のすべての呼び出しが CloudTrail に記録されます。誰が、どの IAM Role で、どの秘密にアクセスしたかが見えます。23章 の ESO IRSA Role が一貫して使われているかを検証する道具です。

GuardDuty / Kubescape — 異常検知 #

GuardDuty の EKS Protection がオンになっていれば、異常なシークレットアクセスパターン (例: 新しく生成された ServiceAccount が突然多くの秘密を読む) を自動検知します。Kubescape はマニフェスト段階のセキュリティポリシー違反 (Secret 平文コミット、トークン自動マウントの未無効化など) を CI 段階で捕まえます。

シークレットガバナンスチェックリスト #

四半期点検の1ページチェックリストを整理します。

四半期シークレットガバナンス点検
[保存]
- EKS encryption-at-rest (KMS) 有効化の有無
- git repo のマニフェストに平文の秘密がないか — gitleaks / trufflehog スキャン
- sealed-secrets / ESO / SOPS のうち選択された道具の一貫性

[回転]
- 90 日以上回転されていない秘密のリスト
- AWS Secrets Manager 自動回転の有効化の有無 (RDS, API キーなど)
- 回転失敗アラートの動作の有無

[注入]
- envFrom vs volumeMount の決定が回転頻度に合っているか
- Reloader との統合 — Secret 更新時の Pod 再起動の自動化
- IRSA + RDS IAM auth でパスワード0で運用可能なワークロードのリスト

[監査]
- EKS audit log 有効化 + CloudWatch Insights の四半期点検
- CloudTrail の Secrets Manager GetSecretValue 点検
- GuardDuty / Kubescape の alert 処理状態
- automountServiceAccountToken: false の適用比率

このチェックリストが1ページに収まり、四半期ごとに定期的に埋められるのがシークレット運用の目標です。26章 の定期運用カレンダーと本章のチェックリストをまとめて、運用クラスタのセキュリティを支えます。

練習問題 #

  1. 自分の dev クラスタに sealed-secrets と external-secrets を両方インストールし、同じ秘密 (例: dummy の DB パスワード) を2つの道具でそれぞれ運用してみます。秘密の回転シナリオ (値の変更) を2つの経路でたどり、sealed-secrets は何段階が、ESO は何段階が必要かを比較します。マニフェストの git diff の形、ArgoCD UI の変化、Pod 再起動の有無を表1枚に整理します。
  2. myshop-api の1つのワークロードを選んで「パスワード0」へ移してみます。RDS の1つのデータベースユーザーを rds_iam グループに追加し、本章の Python 例のように IAM トークンで接続するコードを適用します。PgBouncer との連携で発生するトークン失効の問題をどう解くか — プーラーを迂回するか、プーラー自体がトークンを受け取るか — 自分のシナリオに合わせて一段落で決定の根拠を整理します。
  3. EKS audit log を有効化し、CloudWatch Insights で本章の secret アクセスクエリを回してみます。1週間分の結果から正常 / 異常パターンを分類し、異常と疑われる項目 (予想外の ServiceAccount が myshop の Secret にアクセス、深夜時間帯の多量 GetSecretValue など) があるか調べます。発見したパターンを 25章 モニタリング · アラート の PrometheusRule または GuardDuty のルールで自動検知するマニフェストを1枚書いてみます。

一行まとめ: K8s Secret の base64 は暗号化ではなく、etcd encryption-at-rest はクラスタ内部の保護にすぎずマニフェスト段階は別。シークレット運用は保存 · 回転 · 注入 · 監査の4軸であり、道具は sealed-secrets (git の中で完結) / external-secrets (外部ストア同期 + 自動回転) / SOPS (小さなチーム向けでシンプル) の3つに分かれる。envFrom はシンプルだが回転時に Pod 再起動、volumeMount はファイル自動更新。IRSA + RDS IAM auth の「パスワード0」が最も進んだモデルだが、トークン失効 · PgBouncer 連携の限界のため一部のワークロードに適用するオプション。RBAC + 環境別の鍵分離 + automountServiceAccountToken: false がセキュリティの最後の安全線、EKS audit log + CloudTrail + GuardDuty が監査の要です。四半期シークレットガバナンスチェックリストが1ページに収まるのが運用の目標。

次の章 #

本章でシークレットの運用上の論点を扱ったなら、次の章は 時間の論点 です。K8s は四半期ごとにマイナーバージョンが出て、EKS の標準サポート期間が14か月です。1年に最低一度のマイナーアップグレードが運用の必須サイクルであり、そのサイクルを安全に回すマニュアルが次の章の本文です。

30章 アップグレード戦略 では、26章 運用チェックリスト で短く挙げた EKS アップグレードの流れを本格的に扱います。コントロールプレーン → データプレーン → アドオンの順序、deprecated API の検出 (pluto · kubent · apiserver_requested_deprecated_apis メトリクス)、ノード drain の安全装置 (PDB · terminationGracePeriodSeconds)、blast radius の最小化、ロールバックシナリオ、そしてアップグレード前1週 / 当日 / 後1週のチェックリストまでを一連の流れで扱います。

X