🤫

GKE でセキュアに秘匿情報を管理する: Secret Manager アドオンと External Secrets Operator

2025/02/13に公開

こんにちは!クラウドエースの kazz です。
この記事では、Google Kubernetes Engine (以下、GKE) における秘匿情報を、Secret Manager でセキュアに管理する方法について紹介します。

Kubernetes における秘匿情報

Kubernetes で秘匿情報を扱う場合、一般的には Secret オブジェクトを使用します。
しかし、マニフェストから Secret を作成する際には、Base64 エンコードされた秘匿情報を直接マニフェストに記述する必要があり、そのままバージョン管理するにはセキュリティ面で課題が残ります。

このような課題の解決策として、いくつかの秘匿情報の管理アプローチが提供されています。
本記事では、Secret Manager に秘匿情報を保存し、GKE で利用する 2 つの方法を紹介します。

  • Secret Manager アドオン
  • External Secrets Operator

Secret Manager アドオン

概要

image01
Secret Manager アドオンは、Secret Manager に保存された値を、Pod にマウントされたボリュームとして利用できるようにする機能です。
https://cloud.google.com/secret-manager/docs/secret-manager-managed-csi-component?hl=ja
Secret Manager アドオンを有効にすると、GKE クラスタに Kubernetes Secrets Store CSI ドライバがインストールされます。このドライバと専用のプロバイダを通じて Secret Manager に登録した値を使用することができます。
マニフェストには使用する Secret Manager を SecretProviderClass として設定するだけでよく、ファイルに直接パラメータを記述せずに秘匿情報を取り扱うことができるようになります。

メリット

  • 追加のコントローラーをインストールする必要がない: GKE の機能として利用できるため、専用のリソースは必要なく、運用にかかるコストを下げることができます。

デメリット

  • Secret と同期できない: ボリュームマウントベースにしか対応しておらず、Secret へ同期することはできません。

  • 自動で最新の値と同期することができない: Secret Manager の値を更新した場合、最新の値を取得するには Pod の再起動が必要です。

検証

Secret Manager アドオンを使用して、Secret Manager の値をボリュームとしてマウントしてみます。

Workload Identity

Kubernetes の Service Account(以下、SA) が Secret Manager の値にアクセスできる権限を付与しておきます。

gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--role="roles/secretmanager.secretAccessor" \
--member="principal://iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/YOUR_PROJECT_ID.svc.id.goog/subject/ns/default/sa/secret-sa"

Secret Manager

まずは、Secret Manager に秘匿情報を作成します

gcloud secrets create my-secret \
    --replication-policy="automatic"
echo -n "hoge" | \
    gcloud secrets versions add my-secret --data-file=-

これで、my-secret のバージョン 1 に、hoge という値が保存されました。

GKE クラスタ

次に、GKE クラスタを作成します。
作成する際に、詳細設定 > セキュリティの「Secret Manager を有効にする」にチェックを入れてください。
既存のクラスタを使用する場合は、この設定が有効になっていることを確認してください。
image02

マニフェスト

マウントする Secret を定義します。

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: secret-store
spec:
  provider: gke
  parameters:
  # Secret Manager から取得する値の定義
    secrets: |
      - resourceName: "projects/YOUR_PROJECT_ID/secrets/my-secret/versions/latest"
  # 秘匿情報がマウントされるファイル名
        path: "secret.txt"

Pod が使用する SAを作成します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: secret-sa

秘匿情報をマウントするボリュームを作成します。

apiVersion: v1
kind: Pod
metadata:
  name: secret-addon-test
spec:
  serviceAccountName: secret-sa
  containers:
  - image: busybox
    name: secret-addon-test
    volumeMounts:
      - mountPath: "/var/secrets"
        name: secret-vol
  volumes:
  - name: secret-vol
    csi:
      driver: secrets-store-gke.csi.k8s.io
      readOnly: true
      volumeAttributes:
        secretProviderClass: secret-store

上記で作成したマニフェストを kubectl apply して、Secret Manager の値をマウントできているか確認します。

$ kubectl exec secret-addon-test -it -- cat /var/secrets/secret.txt
hoge

Secret Manager に保存した hoge という値がボリュームとしてマウントされていることを確認できました。

(無理やり)環境変数として利用する方法

Secret Manager アドオンは Secret への同期が現在サポートされておらず、環境変数として簡単に渡すことはできません。
どうしてもコンテナ起動時の環境変数として渡したい場合は、起動スクリプトやエントリポイントで、マウントされたファイルから値を設定することで、プロセス内で環境変数として使用できます。
ただし、コンテナが起動時に実行するコマンドがわからない、アクセスできない場合などは、この方法は機能しません。

apiVersion: v1
kind: Pod
metadata:
  name: secret-addon-test
spec:
  serviceAccountName: secret-sa
  restartPolicy: Never
  containers:
  - name: secret-addon-test
    image: busybox
    command: ['sh', '-c']
    args:
      - |
        export MY_SECRET=$(cat /var/secrets/secret.txt)
        printenv MY_SECRET
    volumeMounts:
      - mountPath: "/var/secrets"
        name: secret-vol
  volumes:
  - name: secret-vol
    csi:
      driver: secrets-store-gke.csi.k8s.io
      readOnly: true
      volumeAttributes:
        secretProviderClass: secret-store
$ kubectl logs secret-addon-test
hoge

External Secrets Operator

概要

image03
※画像は公式サイトより引用

External Secrets Operator は、Secret Manager などの外部の秘匿情報ストアと Secret を同期させるためのツールです。
Secret Manager アドオンと同様に、マニフェストにパラメータを記述せずに秘匿情報を取り扱うことができますが、External Secrets Operator は秘匿情報を Secret に保存します。
そのため、env としてコンテナから参照したり、Kubernetes のオブジェクトとして扱うことができます。

メリット

  • Secret Manager の値を Kubernetes Secret として扱うことができる: ボリュームとしてマウントするだけでなく、secretKeyRef で環境変数として Pod に渡すことができます。
  • 定めた間隔に基づいて値を最新の状態に同期できる: マニフェストの refreshInterval の間隔で、Secret と Secret Manager の値を同期することができます。

デメリット

  • オペレーターのデプロイが必要: 専用のPodが稼働するため、追加の費用や運用作業が必要となります。

検証

External Secrets Operator で Secret Manager に保存した値を Secret に同期してみます。
Secret Manager、GKE クラスタ、SA は Secret Manager アドオンの検証で使用したものを流用します。

External Secrets Operator のインストール

まずは公式ドキュメントを参考に、External Secrets Operator を helm を使ってインストールします。
https://external-secrets.io/v0.4.4/guides-getting-started/#option-1-install-from-chart-repository

リポジトリを追加し、チャートを取得できるようにします。

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

External Secrets Operator をデプロイします。

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

正常にデプロイできたか確認してみます。
以下のように表示されていたら問題ないです。

$ kubectl get all -n external-secrets
NAME                                                    READY   STATUS    RESTARTS   AGE
pod/external-secrets-74fd4f457c-66zjb                   1/1     Running   0          2m22s
pod/external-secrets-cert-controller-55d65c4cc4-nlpzg   1/1     Running   0          3m23s
pod/external-secrets-webhook-7b968d566b-2ntzl           1/1     Running   0          3m23s

NAME                               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/external-secrets-webhook   ClusterIP   34.111.111.111   <none>        443/TCP   3m24s

NAME                                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/external-secrets                   1/1     1            1           3m24s
deployment.apps/external-secrets-cert-controller   1/1     1            1           3m24s
deployment.apps/external-secrets-webhook           1/1     1            1           3m24s

NAME                                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/external-secrets-74fd4f457c                   1         1         1       3m23s
replicaset.apps/external-secrets-cert-controller-55d65c4cc4   1         1         1       3m23s
replicaset.apps/external-secrets-webhook-7b968d566b           1         1         1       3m23s

マニフェスト

ClusterSecretStore リソースと ExternalSecret リソースのマニフェストを作成していきます。

ClusterSecretStore リソースは、外部の秘匿情報ストア(Secret Manager)と連携するための設定を持つリソースです。

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: eso-secretstore
spec:
  provider:
    gcpsm:
      projectID: YOUR_PROJECT_ID
      auth:
        workloadIdentity:
          clusterLocation: asia-northeast1
          clusterName: YOUR_CLUSTER_NAME
          serviceAccountRef:
            name: secret-sa

ExternalSecret リソースは、Secret を外部の秘匿情報ストア(Secret Manager)と同期させるためのリソースです。

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: test-application-externalsecret
  labels:
    app: test-application
spec:
  refreshInterval: 1h
  secretStoreRef:
    kind: SecretStore
    name: eso-secretstore
  # ExternalSecretから作成される Secret の情報
  target:
    name: my-secret
  # Secret Managerから取得する秘匿情報の定義
  data:
  - remoteRef:
      key: my-secret
      version: latest
    secretKey: secret

上記のマニフェストをクラスタに apply して、Secret が期待通りに作成されているか確認します。

 $ kubectl get secret my-secret -o jsonpath="{.data.secret}" | base64 -d
 hoge

Secret Manager に保存した hoge という値が Secret として作成されていることを確認できました。

どっちを使えばいいのか

Secret Manager アドオンを選択するケース

  • 少しでも管理リソースや費用を抑えたい
  • Kubernetes Secret を使いたくない
  • サードパーティ(OSS)はできるだけ避けたい

External Secrets Operator を選択するケース

  • Kubernetes Secret として Secret Manager の秘匿情報を扱いたい

運用のシンプルさを優先するなら Secret Manager アドオン、より柔軟な秘匿情報管理が必要なら External Secrets Operator を選ぶのが良いでしょう。
それぞれのできること、できないことを理解して、環境に適したものを選択してください。

おわりに

もし今後のアップデートで Secret Manager アドオンが Kubernetes Secret と同期できるようになれば、多くのケースでアドオンが利用できるようになります。
Secret Manager アドオンの派生元である本家の CSI ドライバーでは同期できるため、実装されることを期待しておきましょう。

Discussion