External Secrets OperatorによるKubernetesシークレット管理の自動化:AWSとVaultの同期

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Kubernetesネイティブなシークレットの問題点

KubernetesネイティブのSecretsという名称は、少々誤解を招くかもしれません。これらは実際には暗号化されておらず、単にBase64エンコードされた文字列に過ぎないからです。もしこれらをGitリポジトリにコミットしてしまえば、読み取り権限を持つ人なら誰でも echo 'encoded-string' | base64 --decode を実行するだけで、わずか3秒で本番環境のデータベースを掌握できてしまいます。ジュニアデベロッパーが誤ってStripeのAPIキーを含む.yamlファイルをパブリックリポジトリにプッシュしてしまい、手痛い教訓を得るチームを私は何度も見てきました。

ほとんどの組織は、最終的にAWS Secrets ManagerやHashiCorp Vaultのような専用ツールへと移行します。しかし、それらの値をPodに取り込もうとすると摩擦が生じます。カスタムのPythonスクリプトを書いたり、重いinit-containersを使用したりすることもできますが、これらの手法はメンテナンスの負債を大きく増やします。外部VaultとKubernetesの架け橋をマスターすることは、安全でモダンなCI/CDパイプラインを構築する上で避けて通れないスキルです。

External Secrets Operator (ESO) は、このギャップを完璧に埋めてくれます。これは、プロバイダーからデータを取得し、アプリケーションが利用できるようにKubernetesネイティブのシークレットとして注入するバックグラウンドコントローラーとして機能します。

ESOのコアコンセプトを理解する

ESOは、主に2つのカスタムリソースを使用して重労働を処理します。

  • SecretStore / ClusterSecretStore: これらは接続文字列の役割を果たします。AWS、Vault、またはGCPとどのように通信するかを定義します。単一のネームスペースで隔離する場合はSecretStoreを、クラスター全体で1つの接続を共有する場合はClusterSecretStoreを使用します。
  • ExternalSecret: これは何を取得するかを定義するマニフェストです。prod/db/passwordのような特定のキーを特定し、それをローカルのKubernetesシークレットにマッピングするようオペレーターに指示します。

マニフェストのデバッグ中、ネストされたデータ構造が正しいか確認するために、YAMLとJSONの形式を頻繁に切り替えることがあります。このような時、YAML ↔ JSON 変換ツールを手元に置いておくと便利です。クライアント側で動作するツールを使用することで、変換プロセス中に機密性の高いアーキテクチャパターンがサードパーティのサーバーに漏洩するのを防ぐことができます。

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

インストールにはHelmを使用するのが標準的です。バージョン管理が容易になり、将来的に移行が必要になった際のクリーンアッププロセスも簡単になります。

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

helm install external-secrets \
  external-secrets/external-secrets \
  -n external-secrets \
  --create-namespace

コントローラーPodのステータスを確認して、デプロイを検証します:

kubectl get pods -n external-secrets

ステップ2:AWS Secrets Managerへの接続

静的なAWSアクセスキーの使用は、何としても避けてください。代わりに、IAM Roles for Service Accounts (IRSA) を使用して、クラスターにシークレットの読み取り権限を付与します。対象のリソースに対して secretsmanager:GetSecretValue を具体的に許可するIAMポリシーが必要になります。

IAMロールをKubernetesのServiceAccountに紐付けたら、以下のようにClusterSecretStoreを適用します:

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secretsmgr
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-east-1
      auth:
        jwt:
          serviceAccountRef:
            name: eso-service-account
            namespace: external-secrets

ステップ3:HashiCorp Vaultへの接続

Vaultの設定には、通常もう少し多くの構成が必要です。トークンやAppRoleも使用できますが、クラスター内部のトラフィックにはKubernetes Auth Methodが最もクリーンです。以下は、Kubernetesネイティブな認証マウントの標準的な構成です:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: my-app
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "my-role"
          serviceAccountRef:
            name: "my-app-sa"

初期設定時には認証エラーがよく発生します。オペレーターが同期に失敗した場合、私は JWTデコーダー を使用して、サービスアカウントトークンの有効期限やクレームを確認します。これにより、問題がトークンの期限切れなのか、ロール名の不一致なのかを素早く特定できます。

ステップ4:最初のシークレットを同期する

実際にやってみましょう。AWSに /production/api-key という名前のシークレットがあるとします。これを my-app ネームスペースで api-credentials という名前のシークレットとして利用できるようにします。

以下の ExternalSecret マニフェストを適用します:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: api-key-sync
  namespace: my-app
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmgr
    kind: ClusterSecretStore
  target:
    name: api-credentials
    creationPolicy: Owner
  data:
  - secretKey: API_TOKEN
    remoteRef:
      key: /production/api-key
      property: api_token_value

これでESOが値をフェッチし、標準的なKubernetesシークレットを生成します。アプリケーションのPodは、これを環境変数やファイルボリュームとしてマウントできます。レガシーなシークレットを移行する場合は、Base64デコーダー を使用して、新しく同期された値を古い手動設定の値と比較してください。これにより、予期しないフォーマット変更によるアプリケーションのクラッシュを防ぐことができます。

現場で役立つ実践的なヒント

本番クラスターで数百のシークレットを管理してきた経験から、いくつか重要な教訓を学びました:

  1. APIコストに注意: AWS Secrets Managerの料金は、シークレット1つにつき月額0.40ドルに加え、APIコール10,000回ごとに0.05ドルかかります。refreshIntervalを10秒に設定してはいけません。ほとんどのユースケースでは1時間間隔で十分であり、コストを抑えることができます。
  2. パスの標準化: /env/namespace/app/key のような階層構造を使用してください。これにより、あるチームが誤って別のチームの認証情報を閲覧することを防ぐ、きめ細かなIAMやVaultのポリシーを作成できます。
  3. ステータスの監視: 同期が失敗した場合、kubectl describe externalsecret <name> が最強の味方になります。問題が403 Forbiddenエラーなのか、ネットワークタイムアウトなのかを示す明確なイベントログを確認できます。
  4. 最小権限の原則: ESOにVaultへのクラスター全体の管理者権限を絶対に与えないでください。特定のパスへの読み取り専用アクセス権のみを付与し、万が一侵害された場合の影響範囲を最小限に抑えます。

まとめ

手動の kubectl create secret コマンドを卒業することは、セキュリティと運用効率の両面で大きな勝利です。External Secrets Operatorを導入することで、GitOpsマニフェストをクリーンに保ちながら、堅牢なVaultで信頼できる唯一の情報源(Single Source of Truth)を維持できます。これにより、ローテーションが自動化され、ヒューマンエラーが減り、機密データがソース管理にプレーンテキストで触れることがなくなります。

初期設定には1時間ほどかかりますが、トラブルシューティングで節約できる時間とセキュリティ上の安心感は、それ以上の価値があります。複数の環境でシークレットが自動的に更新されるのを目にすれば、もう手動でのエンコード作業には戻れなくなるでしょう。

Share: