Kubernetes Secrets 管理
Kubernetes Secret の本質的な問題
Kubernetes の Secret はデフォルトで Base64 エンコードされているだけで暗号化されていない。
etcd に平文で保存されており、etcd への直接アクセスや kubectl get secret -o yaml で内容が読める。
# Secret の中身は誰でも(RBAC で許可されていれば)読める
kubectl get secret db-credentials -n production -o jsonpath='{.data.password}' | base64 -d
# → mypassword123 ← 平文で出力される
これは「暗号化」ではなく「難読化」に過ぎない。3つのレイヤーで対策が必要。
Layer 1: etcd の暗号化(at-rest encryption)← Kubernetes 側
Layer 2: 外部シークレット管理(Vault / GCP Secret Manager)← より安全
Layer 3: RBAC で Secret へのアクセスを最小化 ← 必須
Layer 1: etcd の暗号化(at-rest encryption)
Secret を etcd に書き込む際に暗号化する。
セルフホスト K8s では自分で設定が必要。GKE ではデフォルトで有効。
EncryptionConfiguration の設定
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc: # AES-CBC で暗号化
keys:
- name: key1
secret: <base64エンコードした32バイトのランダムキー>
- identity: {} # 暗号化なし(フォールバック。新規作成には使われない)
# 32バイトのランダムキーを生成
head -c 32 /dev/urandom | base64
# API Server の起動オプションに追加
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml
# 既存の Secret を再暗号化(設定後に実行)
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
GKE での Application-layer Secrets Encryption
# Terraform: GKE で Cloud KMS を使った Application-layer 暗号化
resource "google_container_cluster" "main" {
database_encryption {
state = "ENCRYPTED"
key_name = google_kms_crypto_key.k8s_secret_key.id
}
}
resource "google_kms_key_ring" "k8s" {
name = "k8s-secrets"
location = "asia-northeast1"
}
resource "google_kms_crypto_key" "k8s_secret_key" {
name = "k8s-secret-encryption-key"
key_ring = google_kms_key_ring.k8s.id
purpose = "ENCRYPT_DECRYPT"
rotation_period = "7776000s" # 90日でキーローテーション
}
Layer 2: 外部シークレット管理
etcd 暗号化より強力な方法。シークレットを Kubernetes の外に置き、必要なときだけ取得する。
External Secrets Operator(ESO)
GCP Secret Manager / AWS Secrets Manager / HashiCorp Vault などと Kubernetes を橋渡しするオペレーター。
シークレットの実体は外部に置き、ESO が定期的に同期して Kubernetes Secret を自動生成する。
外部シークレット管理サービス(GCP Secret Manager等)
↕ ESO が定期同期
Kubernetes Secret(自動生成・自動更新)
↕ マウント
Pod
# SecretStore: 外部サービスへの接続設定
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: gcp-secret-store
namespace: production
spec:
provider:
gcpsm: # GCP Secret Manager を使用
projectID: my-project-id
auth:
workloadIdentity:
clusterLocation: asia-northeast1
clusterName: production-cluster
serviceAccountRef:
name: external-secrets-sa # Workload Identity を使用
---
# ExternalSecret: 同期するシークレットの定義
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h # 1時間ごとに最新値に同期
secretStoreRef:
name: gcp-secret-store
kind: SecretStore
target:
name: db-credentials # 生成する Kubernetes Secret 名
creationPolicy: Owner # ESO が管理(削除時は Secret も削除)
data:
- secretKey: password # Kubernetes Secret のキー名
remoteRef:
key: production/db-password # GCP Secret Manager のシークレット名
version: latest
- secretKey: username
remoteRef:
key: production/db-username
# ESO のインストール(Helm)
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace
HashiCorp Vault
エンタープライズ向けの高機能シークレット管理プラットフォーム。
動的シークレット(使い捨ての認証情報を都度発行)が最大の特徴。
【静的シークレット(従来)】
DB パスワード = "fixed_password_123"
→ 漏洩したら攻撃者が永続的にアクセスできる
【動的シークレット(Vault)】
Pod 起動時: Vault が DB に一時ユーザーを作成
→ 認証情報を Pod に発行(有効期限: 1時間)
→ Pod 終了時: Vault が DB の一時ユーザーを自動削除
→ 漏洩しても有効期限が切れれば無効
# Vault Agent Sidecar による自動インジェクション
apiVersion: v1
kind: Pod
metadata:
name: app
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "order-service"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/order-service"
# /vault/secrets/db にシークレットが書き込まれる
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "database/creds/order-service" -}}
DATABASE_URL=postgres://{{ .Data.username }}:{{ .Data.password }}@db:5432/orders
{{- end }}
spec:
serviceAccountName: order-service-sa
containers:
- name: app
image: my-app:latest
env:
- name: DB_CREDENTIALS_FILE
value: /vault/secrets/db
Layer 3: RBAC による Secret アクセスの最小化
どの保存方法を使っても、Secret へのアクセス権限を絞ることは必須。
# 特定の Secret のみ get できる Role(list は禁止)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: db-secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials"] # 特定の Secret のみ
verbs: ["get"] # list は与えない
list を与えると Namespace 内の全 Secret 名が見える。
get だけでも resourceNames で対象を絞ることが重要。
Secret の Pod への渡し方
環境変数(非推奨)
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
環境変数は kubectl describe pod や子プロセスに継承されるため漏洩しやすい。
また、アプリがクラッシュした際のスタックトレースに含まれることがある。
ボリュームマウント(推奨)
volumes:
- name: db-secret
secret:
secretName: db-credentials
defaultMode: 0400 # 所有者のみ読み取り可能
containers:
- name: app
volumeMounts:
- name: db-secret
mountPath: /etc/secrets
readOnly: true
# /etc/secrets/password にファイルとしてマウントされる
ファイルとして扱うことで、アプリが必要なタイミングだけ読み込める。
ESO の refreshInterval と組み合わせれば、外部で更新されたシークレットが自動で反映される。
Secret のライフサイクル管理
ローテーション
手動ローテーションのリスク:
忘れる・タイミングがずれる・ダウンタイムが発生する
自動ローテーションの実装:
① GCP Secret Manager / Vault でローテーションを設定
② ESO の refreshInterval で K8s Secret を自動更新
③ アプリがファイルの変更を監視してホットリロード(またはコンテナ再起動)
シークレットの削除管理
# ExternalSecret に ownerReference を設定すると
# ExternalSecret を削除したとき K8s Secret も自動削除される
spec:
target:
creationPolicy: Owner # ESO が Owner = 連動して削除される
deletionPolicy: Delete # ExternalSecret 削除時に Secret も削除
よくあるアンチパターン
❌ Dockerfile や docker-compose.yml にシークレットをハードコード
❌ ConfigMap にパスワードを保存(Secret ではなく ConfigMap を使う誤り)
❌ Secret を Git にコミット(.gitignore で除外するだけでは不十分。履歴に残る)
❌ default ServiceAccount に secrets の get 権限を付与
❌ 環境変数でシークレットを渡す(子プロセス・ログに漏洩リスク)
❌ refreshInterval を長く設定しすぎて漏洩時のローテーションが遅くなる
チェックリスト
□ etcd の at-rest encryption を有効化している(GKE: Cloud KMS 連携)
□ シークレットは GCP Secret Manager / Vault など外部に保管している
□ ESO / Vault Agent でシークレットを自動同期している
□ シークレットは環境変数ではなくボリュームマウントで渡している
□ RBAC で secrets への get を resourceNames で絞っている
□ secrets への list 権限を不必要に付与していない
□ シークレットのローテーションを自動化している
□ シークレットが Git リポジトリにコミットされていないか定期スキャンしている
□ truffleHog / detect-secrets でヒストリーを含めてスキャンしている
参考文献
- Kubernetes 公式ドキュメント『Secrets』(kubernetes.io)— Secret の仕様・設定方法・セキュリティの注意点
- External Secrets Operator 公式ドキュメント(external-secrets.io)— SecretStore / ExternalSecret の設定リファレンス
- HashiCorp Vault 公式ドキュメント(developer.hashicorp.com/vault)— 動的シークレット・Vault Agent の設定方法
- CIS Kubernetes Benchmark v1.8 Section 5.4(Secrets Management)— シークレット管理のチェックリスト
- Google Cloud『Using Secret Manager with GKE』(公式ドキュメント)— ESO と Workload Identity を使った GKE 向け設定ガイド
- 1. ⎈Kubernetes セキュリティ概要・脅威モデル
- 2. 🎫Kubernetes RBAC・ServiceAccount
- 3. 🛡️Pod Security・SecurityContext
- 4. 🔌Kubernetes NetworkPolicy
- 5. 🗝️Kubernetes Secrets 管理
- 6. 🔗Kubernetes サプライチェーンセキュリティ
- 7. 👁️Kubernetes ランタイムセキュリティ(Falco・eBPF)
出典: Kubernetes Documentation(kubernetes.io/docs/concepts/configuration/secret)/ HashiCorp Vault Documentation(developer.hashicorp.com/vault)/ External Secrets Operator Documentation(external-secrets.io)/ CIS Kubernetes Benchmark v1.8