GKE でセキュアに秘匿情報を管理する: Secret Manager アドオンと External Secrets Operator
こんにちは!クラウドエースの kazz です。
この記事では、Google Kubernetes Engine (以下、GKE) における秘匿情報を、Secret Manager でセキュアに管理する方法について紹介します。
Kubernetes における秘匿情報
Kubernetes で秘匿情報を扱う場合、一般的には Secret オブジェクトを使用します。
しかし、マニフェストから Secret を作成する際には、Base64 エンコードされた秘匿情報を直接マニフェストに記述する必要があり、そのままバージョン管理するにはセキュリティ面で課題が残ります。
このような課題の解決策として、いくつかの秘匿情報の管理アプローチが提供されています。
本記事では、Secret Manager に秘匿情報を保存し、GKE で利用する 2 つの方法を紹介します。
- Secret Manager アドオン
- External Secrets Operator
Secret Manager アドオン
概要
Secret Manager アドオンは、Secret Manager に保存された値を、Pod にマウントされたボリュームとして利用できるようにする機能です。
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 を有効にする」にチェックを入れてください。
既存のクラスタを使用する場合は、この設定が有効になっていることを確認してください。
マニフェスト
マウントする 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
概要
※画像は公式サイトより引用
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 を使ってインストールします。
リポジトリを追加し、チャートを取得できるようにします。
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