External Secrets Operator(ESO)によるKubernetesシークレット管理:AWS Secrets ManagerおよびHashiCorp Vaultからのシークレット同期

APIキー、データベース認証情報、TLS証明書などの平文データをKubernetesマニフェスト内に直接保存することは、セキュリティインシデントへの最短ルートの1つです。よくある落とし穴として、Base64エンコードされたKubernetes SecretをGitにコミットし、難読化されているから暗号化されていると思い込んでしまうケースが挙げられます。しかし、それは誤りです。Base64は単なるエンコーディング方式に過ぎません。リポジトリの読み取り権限を持つ人であれば誰でも、シェルのワンライナーを使って2秒もかからずに本番環境の認証情報をデコードできてしまいます。

この問題は、現代のインフラにおける明確なジレンマを浮き彫りにしています。GitOpsは宣言的でバージョン管理された状態を要求する一方で、セキュリティコンプライアンスは、きめ細かな監査ログと自動ローテーション機能を備えた堅牢なVaultにシークレットを保管することを求めています。コミット履歴に認証情報を漏洩させることなくこのギャップを埋めるには、信頼性の高い同期レイヤーが必要です。

Kubernetesシークレット管理アプローチの比較

プラットフォームエンジニアがクラスタ内のシークレットを処理する際、主に3つの戦略が検討されます。

1. Sealed Secrets (Bitnami)

Sealed Secretsは非対称暗号を採用しています。クラスタ内のコントローラーに紐づく公開鍵を使用してクライアント側で平文の値を暗号化し、生成されたSealedSecretリソースをGitにコミットします。コントローラーは秘密鍵を使用してペイロードをネイティブなKubernetes Secretに復号します。

2. Secrets Store CSI Driver

Secrets Store CSI Driverは、Container Storage Interface (CSI) プラグイン経由で外部シークレットをPodボリュームに直接マウントします。シークレットは、AWS Secrets Manager、HashiCorp Vault、Azure Key Vault、Google Secret Managerなどの外部バックエンドからPod内部の揮発性メモリへと直接ストリーミングされます。

3. External Secrets Operator (ESO)

External Secrets Operatorは専用のKubernetes Operatorです。外部APIからメタデータを定期的にポーリングし、ターゲット名前空間内に標準のv1/Secretリソースを生成します。

各戦略のメリットとデメリット

それぞれのパターンには固有の運用上のトレードオフが存在します。

Sealed Secrets

  • メリット: Git内で完結し、クラウドのIAM設定が不要。ベンダーロックインなしで動作します。
  • デメリット: シークレットのローテーション時に手動での再暗号化とGitコミットが必要。ディザスタリカバリ時にコントローラー秘密鍵のバックアップが単一障害点となり、エンタープライズ向けシークレットエンジンとの連携機能がありません。

Secrets Store CSI Driver

  • メリット: 認証情報がデフォルトでetcdをバイパスし、Pod内のメモリベースのtmpfsボリュームにペイロードが保持されます。
  • デメリット: Podのライフサイクルと密接に結合しており、単純な環境変数を想定しているアプリケーションでもボリュームマウントが必要になります。値をネイティブなSecretにミラーリングするにはalpha/betaのコントローラーフラグを有効にする必要があり、設定のオーバーヘッドが増加します。

External Secrets Operator (ESO)

  • メリット: 標準のv1/Secretオブジェクトを生成するため、既存のHelmチャートやDeploymentとの完全な互換性を確保。複数のバックエンドへの同時接続が可能で、組み込みのテンプレート機能や柔軟なポーリング間隔設定を備え、Podの起動シーケンスから切り離されて動作します。
  • デメリット: シークレットがetcdに保持されるため、保管時のKMS etcd暗号化が必須。常時稼働するコントローラーの監視・保守が必要です。

推奨アーキテクチャ:ESOとエンタープライズバックエンドの組み合わせ

何百ものマイクロサービスを管理する本番クラスタにおいて、ESOは運用チームと開発チームの間に最もクリーンな境界線を提供します。セキュリティチームはAWS Secrets ManagerやVaultでIAMポリシーとライフサイクルルールを集中的に管理し、アプリケーション開発者は標準ワークロードのすぐ隣に簡潔なExternalSecretマニフェストを記述するだけで済みます。

SecretStoreやExternalSecretのYAML定義を作成する際は、事前にキー構造を検証しておくと工数を削減できます。JSONペイロードをYAMLに変換したり構造化ディクショナリをテストしたりする必要がある場合は、ToolCraft YAML to JSON Converterをお試しください。

ToolCraftはすべての変換処理をブラウザのクライアント側のみで実行し、ペイロードを外部送信しないため、非本番環境のスキーマ検証にも安全に利用できます。既存のクラスタシークレットを迅速に検査したり、ローカルでBase64文字列を確認したりするには、Base64 Encoder/Decoderも同様に高速かつ安全です。

ステップ・バイ・ステップ実装ガイド

ステップ1:External Secrets Operatorのインストール

クラスタにESOのHelmチャートをデプロイします。

helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets \
  external-secrets/external-secrets \
  -n external-secrets \
  --create-namespace \
  --set installCRDs=true

コントローラー、Webhook、cert-controllerの各Podが正常に起動していることを確認します。

kubectl get pods -n external-secrets

ステップ2:SecretStoreによるAWS Secrets Managerの接続

IAM Roles for Service Accounts (IRSA) を使用することで、ハードコードされた認証情報なしでAWS EKS上での安全なトークン交換が可能になります。対象のIAMロールにsecretsmanager:GetSecretValueおよびsecretsmanager:DescribeSecret権限を付与し、ServiceAccountにバインドした上で、SecretStoreを宣言します。

cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets-backend
  namespace: default
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-east-1
      auth:
        jwt:
          serviceAccountRef:
            name: eso-aws-sa
EOF

ステップ3:SecretStoreによるHashiCorp Vaultの接続

HashiCorp Vaultの場合、ネイティブなKubernetes認証方式によりPodのServiceAccountが直接Vaultポリシーにマッピングされます。これにより、静的なルートトークンを管理する手間が不要になります。

cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: default
spec:
  provider:
    vault:
      server: "https://vault.internal.corp:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "app-role"
          serviceAccountRef:
            name: default
EOF

ステップ4:ExternalSecretリソースの宣言

次に、値を取得してKubernetes Secretを生成するためのExternalSecretマニフェストを定義します。上流のVaultにJSONディクショナリが保存されている場合は、マッピングを記述する前にToolCraft JSON Formatterを使用してキーのフォーマットや確認を行ってください。

cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: payment-service-secret
  namespace: default
spec:
  refreshInterval: "1h"
  secretStoreRef:
    name: aws-secrets-backend
    kind: SecretStore
  target:
    name: payment-api-secret
    creationPolicy: Owner
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: production/payment-service
        property: password
    - secretKey: API_KEY
      remoteRef:
        key: production/payment-service
        property: api_key
EOF

ステップ5:同期の確認とPodへのバインド

リソースの同期ステータスを確認します。

kubectl get externalsecret payment-service-secret
kubectl describe externalsecret payment-service-secret

出力内にStatus: SecretSyncedが表示されていることを確認します。続いて、ESOによってネイティブSecretが生成されたことを確認します。

kubectl get secret payment-api-secret -o yaml

同期されたキーを通常の環境変数としてアプリケーションにバインドできるようになりました。

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: payment-service
  template:
    metadata:
      labels:
        app: payment-service
    spec:
      containers:
        - name: app
          image: nginx:alpine
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: payment-api-secret
                  key: DB_PASSWORD
EOF

上流で認証情報がローテーションされると、ESOは設定されたrefreshIntervalに従って更新された値を取得します。この構成にStakater Reloaderのようなコントローラーを組み合わせることで、下流のシークレットが変更された瞬間にPodのローリングアップデートを自動的にトリガーできます。

Share: