自己紹介
竹下
2023年8月21日からインターンに参加している早稲田大学基幹理工学研究科 M1 竹下です。
SRE関連の技術と,自身が研究しているセキュリティ分野との関係性を学びたいと思い、インターンに参加しました。
中林
2023年8月21日からインターンに参加している秋田県立大学大学院 システム科学技術研究科 博士前期1年 中林です。
Kubernetes とコンテナセキュリティの部分に興味があり、インターンに参加しました。
引野
2023年8月28日からインターンに参加している新潟大学大学院 医歯学総合研究科 医科学専攻1年 引野です。
データ分析の環境構築をきっかけにコンテナ技術に興味を持ち、インターンに参加しました。
1. はじめに
はじめまして、スリーシェイクの Sreake 事業部インターン生の竹下、中林、引野です。
Sreake 事業部は SRE 技術に強みを持つエンジニアによるコンサルテーションサービスを提供する事業部であり、SRE 技術の調査、研究を行う目的で 2023年 8月 21日から 2週間の短期インターンに参加しました。本記事では、Kubernetes で秘密情報を扱うためのいくつかのツールについて調査を行い、比較を行ったことをまとめます。
2. kubernetes における秘密情報について
秘密情報とは、パスワードやトークン、キーなどの少量の機密データを含むオブジェクトのことです。Kubernetes における秘密情報を管理する手法は大まかに分けて3種類あります。本記事では、Kubernetes における秘密情報を管理する手法として3つのツールを検証し、それらの仕組みを説明した上で、検証結果とメリット・デメリットについてまとめ、比較しました。
Ⅰ. ユーザーが秘密情報を直接エンコード/デコードする
⇒本記事ではⅠの手法の一つとして Secrets Operations (以下、SOPS )を検証しました。
Ⅱ. 秘密情報をファイルとして直接マウントする
⇒本記事ではⅢの手法の一つとして Secrets Store CSI Driver (以下、SSCD)を検証しました。
Ⅲ. 外部の秘密情報と Kubernetes Secret を紐付ける
⇒本記事ではⅡの手法の一つとして External Secrets Operator(以下、ESO)を検証しました。
3. バージョン情報
動作検証に使用したツールは以下のとおりです。
- kubectl:v1.27.4
- helm:v3.9.3
- Kubernetes:v1.27.3-gke.100
- getsops/sops:Commit ID 35039c363ab5adb5cff1ef99ce8305eec5b5b7d8
- Secrets Store CSI Driver:v1.3.4
- GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp:v1.3.0
- external-secrets/external-secrets:v0.9.3
- Google Cloud Secret Manager
- Google Cloud KMS
4-1. SOPS の仕組み
SOPS とは
SOPS とは、Mozilla(Firefox などの開発元)が公開している暗号化ツールで、エンベロープ暗号化を利用してファイルを暗号化することができます。また、暗号化したファイルを復号することも可能で、SOPS を用いると、秘密情報を含むファイルをその他の情報を含むファイルと一元管理することができます。
エンベロープ暗号化とは、平文データをデータキーで暗号化し、そのデータキーをさらに別のキーで暗号化する方法です。SOPS ではデータキーを利用する KMS のマスターキーで暗号化します。また、暗号化したデータキーは SOPS によって暗号化したファイルに保存されます。
SOPS の新規ファイル作成手順
①SOPS がランダムなデータキーを生成
②KMS のマスターキーでデータキーを暗号化
③暗号化されたデータキーを安全にファイルに保存
SOPS の秘密情報の暗号化手順
①ファイルから暗号化されたデータキーを抽出
②KMS を使ってデータキーを復号
③暗号化する key-value ごとに Initialization Vector(IV) を生成
④IVとデータキーを用いて value を暗号化
⑤IVと暗号化された value をファイルに保存
⑥ファイルの整合性の確認のため、すべての key-value をベースに計算された MAC を暗号化してファイルに保存
SOPSの秘密情報の複号手順
①ファイルから暗号化されたデータキーを抽出
②KMS を使ってデータキーを復号
③ファイルに入っている全ての key-value をベースに MAC を計算し暗号化して、ファイルに保存されている MAC と比較することで整合性を検証
④ファイルから IV と暗号化された value を抽出
⑤IV とデータキーを用いて value を復号
SOPSでは、複数のマスターキーを用いることが可能で、一つのマスターキーにアクセスできなくなってしまっても、別のマスターキーにアクセスできれば、復号が可能になります。
この機能は次のようなケースで利便性が向上します。
Ⅰ. チームごとにマスターキーを生成し、複数チームにファイルを共有できます。
Ⅱ. 複数のマスターキーを用意することで、バックアップとして使えます。
4-2. SOPS を Google Cloud で検証するまでの手順
①SOPS のインストール
セルフビルドを行う際は、Goのインストールが必要です。
$ mkdir -p $GOPATH/src/github.com/getsops/sops/
$ git clone <https://github.com/getsops/sops.git> $GOPATH/src/github.com/getsops/sops/
$ cd $GOPATH/src/github.com/getsops/sops/
$ make install
②Google Cloud KMS を利用するために、アプリケーションの認証情報を使用して認証
$ gcloud auth login
$ gcloud auth application-default login
③KMS リソースID を gcloud を使用して作成
$ gcloud kms keyrings create sops --location global
$ gcloud kms keys create sops-key --location global --keyring sops --purpose encryption
$ gcloud kms keys list --location global --keyring sops
④ファイルの暗号化
$ sops --encrypt --gcp-kms projects/sreake-intern/locations/global/keyRings/sops/cryptoKeys/sops-key test.yaml > test.enc.yaml
test.yaml は暗号化されるファイルなので、事前に作成しておきます。
また、key: value で要素を追加します。
今回は、user として masamu、pass として three-shake を追加しました。
sample
暗号化した結果
⑤ファイルの複号化
$ sops --decrypt test.enc.yaml
4-3. SOPS のメリット・デメリット
メリット
- Kubernetes クラスタ上で特に設定をする必要がなくなる
- KMS が鍵を管理するため、鍵を手に入れるために認証が必要になり、不正なユーザに盗まれるリスクが低くなる
- Integrity Check が可能で改ざんされていないことが保証される。
- 秘密情報そのものを Git で管理できるため、機能追加など他の部分の変更に合わせた秘密情報の変更が追いやすくなる
- SOPS の構築が不要であるため、マニフェストファイルを書く必要がある
- key-value の暗号化では、value の部分だけを暗号化する手法なので、暗号化されたファイルを見たとき、秘密情報が洩れることなく、レビューがしやすくなる
デメリット
- 暗号化された yaml ファイルをそのまま Git 管理することへの抵抗心を持つ可能性がある
- マスターキーが漏れると全ての秘密情報が復号されてしまう
- ファイルが漏れたときに yaml ファイルの value はわからないが、構造はわかるため内容推測の材料になる
- 秘密情報を利用する際、復号した状態をローカルに保存するので、平文状態が残るリスクがある
4-4. SOPSと他ツールの連携
SOPS は CLI ツールであるため、ネイティブな Kubernetes 環境で積極的に使うことはあまり有用ではなく、追加のツールを必要とする場合がほとんどです。
そのため、Helm で SOPS を利用する Helm-Secrets について紹介します。
①Helm-Secrets
Helm-Secret は暗号化された Helm のファイルをその場で復号するための Helm のプラグインです。
- 暗号化には SOPS を利用します
- シークレットを AWS SecretManager、Azure KeyVault、HashiCorp Vault などのクラウドネイティブシークレットマネージャーに格納します。
- デプロイツールや Argo CD のような GitOps オペレーターで使用できます。
- マニフェストファイルの管理には Helm を利用することが多いため、SOPS を Helm から使えるようにすることは利便性の向上に繋がります。
4-5. Helm-Secrets の利用手順
Helm-Secrets を利用するには、アプリケーションを Kubernetes クラスタにデプロイするのに必要なリソースを含んだパッケージである Helm Chart の他に平分で秘密情報を記述している plainSecrets.yaml
と暗号化・復号に関する鍵の情報を記載する .sops.yaml
ファイルを用意する必要があります。
plainSecrets.yaml の sample
# パスワードを "testpass" として
# echo "testpass" | base64 | xargs -I{} echo -e "test:\\n pass: {}\\n" > plainSecrets.yaml # base64 エンコードしたファイルを作成する場合はこっち
echo "testpass" | xargs -I{} echo -e "test:\\n pass: {}\\n" > plainSecrets.yaml
# 中身を確認
cat plainSecrets.yaml
=>
test:
pass: testpass
.sops.yaml では暗号化・復号に関する鍵の情報を記載するが、SOPS 利用時に作成した KMS の鍵をそのまま利用することができます。
Helm-Secrets の利便性が向上する機能
ディレクトリ内のすべての復号されたファイルを再帰的に削除します。
$ helm secrets clean examples/sops/
removed examples/sops/secrets.yaml.dec
この機能を使うと、復号された平文状態のファイルが全て削除されるので、平文状態が残るリスクが低減されます。
secrets.yaml ファイルを復号し、エディターで開きます。そして、ファイルが変更された場合、エディターの終了後に再び暗号化されます。
$ helm secrets edit examples/sops/secrets.yaml
こちらについても、復号された状態がローカルに残らないので、平文状態が残るリスクが低減されます。
プロジェクトごと、グローバル、または任意のツリーレベルごとに異なる PGP または KMS キーを実行できる。これにより、同じ git リポジトリを使用して、異なる CI / CD インスタンス上のツリーを分離できます。
charts/
├── .sops.yaml
└── projectX
├── .sops.yaml
├── stages
│ ├── dev
│ │ ├── secrets.yaml
│ │ └── env.yaml
│ └── test
│ ├── secrets.yaml
│ └── env.yaml
├── secrets.yaml
└── values.yaml
4-6. Helm-Secrets のメリット・デメリット
メリット
- ファイルの復号をデプロイ時に自動で行うため、SOPS 単体で使うよりも秘密情報の扱いに関するヒューマンエラーが起きにくいです。
- 利便性が SOPS 単体で利用するよりも高いです。
- Helm のテンプレート機能を使えるため、秘密ファイルを別のファイルに保存するなど、秘密情報とそれ以外を切り分けて管理することができます。
デメリット
- デメリットは基本的にはSOPSと同じですが、前述のとおり、ディレクトリ内のすべての復号されたファイルを再帰的に削除する機能や、エディターの終了時にファイルが暗号化される機能を用いれば、復号された状態がローカルに残らないので、平文状態が残るリスクが低減され、ヒューマンエラーのリスクも低減できると考えられます。
5-1. SSCD の仕組み
Secrets Store CSI Driver (SSCD) とは
Secrets Store CSI Driver は,、各ノード上で作動するkubeletとの通信を円滑に勧めてくれるデーモンセット(DaemonSets)のことです。
外部のシークレットプロバイダー(GCP Provider)と通信し、Secrets Store から秘密情報を Pod にマウントする機能も果たします。
SSCD は3つのコンテナで構成されており、以下で各コンテナについて簡単に説明します。
SSCD を構成する3つのコンテナ
node-driver-register
CSI Driver を kubelet に登録し、CSI 呼び出しを発行する UNIX ドメインソケットを認識します。
secrets-store
Pod の作成または削除時に、ボリュームのマウントまたはアンマウントを担当します。
liveness-probe
CSI Driver が正常に機能しているかを監視し、問題があれば Kubernetes に報告します。この機能により、Pod を再起動して問題の解決を試みることができます。
5-2. SSCD 構築手順
全体の流れ
- Workload Identity が有効な GKE クラスターを用意
- Secrets Store CSI Driver をインストール
- 外部のシークレットプロバイダーをインストール
- 秘密情報を作成
- 確認
1. Workload Identity が有効な GKE クラスターを用意
公式ドキュメントを参照してください。
2. Secrets Store CSI Driver をインストール
まず、https://github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp.git をクローンします。
git clone https://github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp.git
※ SSCD をインストールするために、Helm というツールを使います。
Helm のインストールはこちら
Helm のリポジトリを追加し、SSCDをインストール
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --namespace kube-system
インストールできたか確認
kubectl --namespace=kube-system get pods -l "app=secrets-store-csi-driver"
実行後、以下のような表示が出ればOK
NAME READY STATUS RESTARTS AGE
csi-secrets-store-secrets-store-csi-driver-flq5t 3/3 Running 0 31s
csi-secrets-store-secrets-store-csi-driver-fwbhf 3/3 Running 0 31s
参考:https://secrets-store-csi-driver.sigs.k8s.io/getting-started/installation
3. 外部のシークレットプロバイダーをインストール
今回は、GCP Provider を利用します。
helm upgrade --install secrets-store-csi-driver-provider-gcp charts/secrets-store-csi-driver-provider-gcp
インストールできたか確認
kubectl --namespace=kube-system get pods -l "app=secrets-store-csi-driver-provider-gcp"
実行後、以下のような表示が出ればOK
NAME READY STATUS RESTARTS AGE
csi-secrets-store-provider-gcp-ntwwc 1/1 Running 0 5m43s
csi-secrets-store-provider-gcp-qp2tc 1/1 Running 0 5m43s
4. Workload Identity サービスアカウントの用意
$ export PROJECT_ID=test-project # test-project には任意のプロジェクト名を入れてください
$ gcloud config set project test-project
# Workload Identity 用のサービスアカウントを作成
$ gcloud iam service-accounts create test-workload # test-workload には任意のアカウント名を入れてください
# default/mypod が新しいサービスアカウントとして機能するように設定
$ gcloud iam service-accounts add-iam-policy-binding \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:test-project.svc.id.goog[default/mypodserviceaccount]" \
test-workload@test-project.iam.gserviceaccount.com
5. 秘密情報の作成
$ echo "foo" > secret.data
$ gcloud secrets create test-secret --replication-policy=automatic --data-file=secret.data
# test-secret には任意のシークレット名を入れてください
# secret.data ファイルを削除
$ rm secret.data
# 新しく作ったサービスアカウントにシークレットへのアクセス権を付与
$ gcloud secrets add-iam-policy-binding test-secret \
--member=serviceAccount:test-workload@test-project.iam.gserviceaccount.com \
--role=roles/secretmanager.secretAccessor
6. Podにマウントできているか確認
$ ./scripts/example.sh
# https://github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp.git
# 上記リポジトリ上のスクリプトを使用
$ kubectl exec -it mypod /bin/bash
root@mypod:/ cat /var/secrets/good1.txt
foo
root@mypod:/ cat /var/secrets/good2.txt
foo
※ example.sh について補足
example/script.sh
では 2個のテンプレートファイル(examples/app-secrets.yaml.tmpl, examples/mypod.yaml.tmpl
)の PROJECT_ID を PROJECT_ID
環境変数で置換し、デプロイするまでを自動化したシェルスクリプトです。
examples/app-secrets.yaml.tmpl
は SecretProviderClass
リソースの構成を記述したマニュフェストのテンプレートです。SecretProviderClass
リソースはドライバーの構成やプロバイダー毎のパラメータ、秘密情報ファイルの PATH などを指定するカスタムリソースです。app-secrets.yaml.tmp
内では Google Cloud の Secret Manager にある秘密情報を good1.txt
と good2.txt
というファイルとしてマウントするという設定を記述しています。
examples/mypod.yaml.tmpl
は Workload Identity 用の KSA と CSI ボリュームをマウントするように指定した Pod を構成するマニュフェストファイルです。
つまり、example/script.sh
を実行すると秘密情報が good1.txt
と good2.txt
を/var/secrets
以下にマウントした Pod が立ち上がります。
この内容は cat
コマンドで該当ファイルの内容を出力すると確認できます。
5-3. SSCD のメリット・デメリット
メリット
- CSI Driver でマウントされるファイルシステムが tmpfs であり、Pod を削除するとノード内の秘密情報も削除されるため、ノードに秘密情報が残ることがない
- 秘密情報が直接ファイルシステムにマウントされるため、Pod内でファイルとして扱うことが出来る
デメリット
- 秘密情報がアップデートされたときに、マウントしているPodで最新の秘密情報を取得するには、デプロイし直さないといけない
- 秘密情報の自動更新はアルファ版の機能となっている
6-1. ESO の仕組み
ESO とは
External Secrets Operator(ESO) とは公式ドキュメントには以下のように記載されています。
External Secrets Operator is a Kubernetes operator that integrates external secret management systems like AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, IBM Cloud Secrets Manager, CyberArk Conjur and many more. The operator reads information from external APIs and automatically injects the values into a Kubernetes Secret.
つまり、ESO は HachiCorp Vault などの外部秘密情報管理システム(ESMS)に保存した秘密情報を Kubernetes 側から参照できるようにした秘密情報管理ツールの一つです。
ESO のアーキテクチャは以下の図のようになります。ESO は Kubernetes 内に配置され、ESMS と Kubernetes の Secret オブジェクトの同期を行うために仲介を行います。秘密情報自体は ESMS に保存しておき、ESO が ESMS から秘密情報を取得し、 Secret オブジェクトに自動的に値をストアします。そのため、秘密情報の管理自体は ESMS 側に一任することになります。
ESO はこの機能を 3つのコンポーネントと 主に 4つの CRD で実現しています。
図の作成には以下のアイコンセットを利用しました。
- https://iconduck.com/icons/27592/kubernetes
- https://github.com/kubernetes/community/tree/master/icons
- https://github.com/external-secrets/external-secrets/blob/main/assets/eso-logo-large.png
Kubernetes の Secret リソース
各コンポーネント、カスタムリソースの説明の前に、Kubernetes の Secret リソースについて簡単に説明します。
Kubernetes には秘密情報を含むデータを保存するための Secret というリソースがあります。このリソースを使用することで、マニュフェストファイルに直接秘密情報を記載する必要がなくなり、秘密情報の漏洩のリスクが低減することが期待できます。
Secret リソースには秘密情報の漏洩のリスクを低減するための工夫があります。Secret は etcd に格納されており、ノード上の Pod から参照される場合にノード上に送信されるようになっています。このとき、 Secret はストレージ内には保存されず、 tmpfs としてノードの RAM 上に保存されます。そのため、Secret を参照している Pod がノード上から削除されるとノード上の Secret の内容も自動的に削除され、ノード上に必要のない Secret が残り続けることを防ぐことができます。
一方で Secret の内容はデフォルトでは Base64 エンコードされているのみで特に暗号化の処理を行っていないため、etcd に不正アクセスされた場合には情報漏洩となる可能性があるというデメリットも存在します。
ESO のコンポーネント
ESO は Core Controller、 Webhook、 Cert Controller という 3つのコンポーネントから構成されています。
特に Core Controller は ESO で最も重要なコンポーネントです。Core Controller では複数のカスタムコントローラが動いており、対応するカスタムリソースの内容をもとに秘密情報の取得、 Kubernetes の Secret オブジェクトへの値の自動挿入、更新などを行います。
Webhook は ExternalSecret、SecretStore リソースの検証などを行う Webhook です。検証が具体的にどのような処理を指しているかまでは分かりませんでした。
3つ目の Cert Contoroller は Webhook が TLS 通信を行うために必要な証明書の管理を行うコントローラです。
以上 3つのコンポーネントが Kubernetes の Pod として動作することで ESO の機能を実現しています。
ESO のカスタムリソース
ESO には主に 4つの CRD (SecretStore、 ClusterSecretStore、 ExternalSecret、 ClusterExternalSecret)があり、 ESO のユーザは CRD に基づいたカスタムリソースを作成し、組み合わせることで、秘密情報の取得が可能になります。
SecretStore/ClusterSecretStore
SecretStore/ClusterSecretStore リソースは ESO が ESMS から秘密情報を取得するために必要なプロジェクト名、鍵名などを指定します。このリソースには対応する ESMS のプロバイダーに合わせた記述が必要になります。
ESMS として Google Cloud Secret Manager を用いる場合は認証部分に Workload Identity を使用できるため、アクセストークンを別途用意し、マニュフェストファイルに記述するなどの手順がなく、比較的容易に設定できます。
SecretStore と ClusterSecretStore は基本的な役割は同じですが、スコープに違いがあります。SecretStore リソースは namespace スコープであり、同じ namespace からのみ参照できます。一方でClusterSecretStore リソースはクラスタスコープであり、全ての namespace から参照できます。
ExternalSecret
ExternalSecret リソースにはどのデータを取得し、どのようにデータを Secret オブジェクトにストアするかを記述します(spec.target
)。また、どの SecretStore オブジェクトを使うかも指定する必要があります(spec.secretStoreRef
)。
ExternalSecret リソースをデプロイすると Core Controller の中の ExternalSecret のコントローラが ExternalSecret オブジェクトの記載内容から ESMS にアクセスし秘密情報を取得した後 Secret オブジェクトに値をストアします。
また、ESMS の秘密情報の変更に対応するために、ExternalSecret リソースでは ESMS に更新を見に行く間隔(spec.refreshInterval
)を設定することができ、コントローラはその内容を ESMS から fetch し、Secret の値を最新の内容に同期することが出来ます。
ClusterExternalSecret
ClusterExternalSecret リソースはクラスタスコープのリソースであり、特定の namespace に ExternalSecret を配置したい場合などに使用します。
ESO の秘密情報の取得手順
ESO のアーキテクチャ図にカスタムリソースを追加し、秘密情報の取得手順を図解したものを以下に示します。
図の ExternalSecret リソースと Secret の間及び ExternalSecret リソースと ClusterSecretStore リソースの間は、ExternalSecret リソースで秘密情報をストアする Secret リソースの名前や ESMS の情報が記述された ClusterSecretStore を指定する必要がありますが、実際の処理を行うのは ESO の Core Controller であるため、間接的な繋がりを表すために点線で結んでいます。
ESO は Core Controller の Reconciliation Loop の中で次の手順で秘密情報を同期します。
- ExternalSecret の内容を読み取る
- ExternalSecret で参照している SecretStore の情報を読み取る
- SecretStore の内容を元に ESMS との通信を確立し、秘密情報を取得する
- ExternalSecret でターゲットとして指定した Secret オブジェクトが存在しない場合は新規作成し、 その Secret オブジェクトに取得した秘密情報をストアする
さらに詳しい手順はコントローラの Reconciliation Loop の実装から辿ることが出来ます。
6-2. ESO の構築
今回は ClusterSecretStore、 ExternalSecret リソースを用いて先程の図のような ESO の構築を行い、 Google Cloud Secret Manager に保存された秘密情報を取得できることを確認します。構築には以下の 4つの手順を行う必要があります。
- Workload Identity の有効化(Google Cloud Secret Manager との認証で使用)
- Secret Manager で秘密情報を作成
- ESO のインストール
- ESO カスタムリソースの作成
- ClusterSecretStore リソースを作成
- ExternalSecret リソースを作成
パラメータ一覧
ESO の構築に用いるパラメータ名とその役割を以下のように定義します。各パラメータの値は環境によって異なる値を指定する必要がありますが、値の例も記載するため構築の手順での参考にしてください。
変数名 | 値の例 | 説明 |
---|---|---|
PROJECT_ID | example-project | Google Cloud のプロジェクト ID |
CLUSTER_NAME | example-cluster | GKE クラスタの名前 |
COMPUTE_REGION | asia-northeast1-a | GKE クラスタのリージョン or ゾーン |
NODEPOOL_NAME | default-pool | ノードプールの名前 デフォルトは default-pool |
NAMESPACE | external-secrets | KSA を作成する namespace ,ESO をインストールする namespace |
GSA_ACCOUNT_NAME | example-gsa | Google Cloud Service Account の名前 |
KSA_ACCOUNT_NAME | example-ksa | Kubernetes Service Account の名前 |
KEY_NAME | example-eso | Google Cloud Secret Manager の鍵名 |
SECRET_NAME | test-secret-info | 秘密情報をストアする Secret リソースの名前 |
1. Workload Identity の有効化
ESMS に Google Cloud の Secret Manager、 Kubernetes クラスタとして Google Cloud の GKE クラスタを利用している場合、Pod に対して Google Cloud のサービスを利用する権限を付与することが出来る Workload Identity という機能を利用できます。Workload Identity は Kubernetes Service Account (KSA) と Google Cloud の Service Account (GSA) を紐付けることで、KSA から GSA の権限を借用し、 Google Cloud のサービスを利用できるという仕組みになっています。
Workload Identity を有効化するとアクセストークンなどの資格情報を明示的に指定する必要がなくなるため高い利便性があり、アクセストークンが漏れることもなくなるためセキュリティ面でもより強固になることも期待できます。今回構築する ESO では Google Cloud Secret Manager から秘密情報を取得することが目的のため、Workload Identity を有効化します。
有効化の手順はこの記事では詳しく解説しませんが、Google Cloud 公式ドキュメントの「 Workload Identity を使用する」の手順を参考にしました。注意点として、上記の手順で作成する KSA の namespace は ESO をインストールする namespace と同じである必要があります。
上記手順で Workload Identity が正しく設定されているかは、Google Cloud の metadata-server にリクエストを送ることで確認できます。まず、以下のマニュフェストファイルを作成し、デプロイすることで Kubernetes 上に Pod を作成します。(kubectl apply -f wi-test.yaml
)
apiVersion: v1
kind: Pod
metadata:
name: workload-identity-test
namespace: NAMESPACE
spec:
containers:
- image: google/cloud-sdk:slim
name: workload-identity-test
command: ["sleep","infinity"]
serviceAccountName: KSA_ACCOUNT_NAME
nodeSelector:
iam.gke.io/gke-metadata-server-enabled: "true"
次に、作成した Pod 上に入ります。
# 以下でGSAのアカウントが帰ってくれば成功
$ kubectl exec -it workload-identity-test \\
--namespace NAMESPACE \\
-- /bin/bash
Pod 上のシェルで以下の curl コマンドを実行します。
この時指定している 169.254.169.254 は Google Cloud の Compute Engine がアクセスできる metadata-server が配置されているリンクローカルアドレスです。Compute Engine の VM が metadata-server にリクエストを行った場合は Google Cloud のサービスを使用するためのアクセストークンや GSA のアドレスなどを取得する事ができます。
一方で、Workload Identity を有効にした場合は kube-system の namespace に gke-metadata-server という Pod が作成され、iptables のルールが書き換えられることによって metadata-server への リクエストが gke-metadata-server にフォワーディングされるようになります。gke-metadata-server は KSA の annotations の情報を元に KSA と GSA の紐づけを処理し、 GSA の権限で Google Cloud の機能を使うためのアクセストークンなどを取得することができます。
以下で実行しているコマンドのアクセス先として指定しているエンドポイントは GSA のメールアドレスを返すエンドポイントであり、Workload Identity の設定が正しくなされている場合には KSA と紐付けられている GSA のメールアドレスがレスポンスとして返ってくることを確認できます。
$ curl -H "Metadata-Flavor: Google" <http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email>
=>
KSA_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com
2. Secret Manager で秘密情報を作成
Secret Manager で秘密情報を作成します。秘密情報の形式は文字列で記載することも出来ますが、辞書形式で記述することで、アプリケーションから使いやすくなるため、今回の検証では以下のようなユーザ名やパスワードなどを辞書形式で記述したものを使用します。
// 今回使用した秘密情報の例
{
"user":"default-user",
"pass":"super-weak-passphrase"
}
以下のコマンドで Secret Manager に秘密情報を作成することが出来ます。グラフィカルに行いたい場合は、Web の Secret Manager の管理画面から作成することも出来ます。
$ cat << EOF > secret.data
{
"user":"default-user",
"pass":"super-weak-passphrase"
}
EOF
$ gcloud secrets create KEY_NAME --replication-policy=automatic \\
--data-file=secret.data
いずれかの方法により秘密情報作成後、 Web の Secret Manager の管理画面から設定した値を閲覧することで適切に設定できていることを確認します。
先ほど作成した GSA から Secret Manager の秘密情報を参照できるように権限の設定を行います。作成した GSA に対して以下のコマンドで roles/secretmanager.secretAccessor
ロールを付与します。これにより、GSA から作成した秘密情報の閲覧ができるようになります。
$ gcloud secrets add-iam-policy-binding KEY_NAME \\
--member="serviceAccount:GSA_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com" \\
--role="roles/secretmanager.secretAccessor"
3. ESO のインストール
ここからは Secret Manager から秘密情報を取得する ESO を構築します。
まず、ESO のインストールを行います。ESO のインストール方法は公式ドキュメントに様々な方法が提示されています.今回は Helm に Chart レポジトリを追加する方法でインストールします。
ESO をインストールする namespace は任意に指定できますが、公式ドキュメントに沿って external-secrets や es とすると後の設定が簡単になります。ここでは namespace を NAMESPACE=external-secret としています。
# Helm にチャートレポジトリの情報を追加する
$ helm repo add external-secrets <https://charts.external-secrets.io>
# installサブコマンドでKubernetes上にオブジェクトを作成する
$ helm install external-secrets \\
external-secrets/external-secrets \\
-n NAMESPACE \\
--create-namespace \\
ESO がインストールされると kubectl get pods -A
などのコマンドを実行し、NAMESPACE という名前の namespace (ここでは external-secrets )に Pod が作成されていることが確認できます。
それぞれ,external-secrets-[1-9a-z]{10}-[1-9a-z]{5} が Core Controller の Pod、 external-secrets-cert-controller-[1-9a-z]{10}-[1-9a-z]{5} が Cert Controller、 external-secrets-webhook-[1-9a-z]{10}-[1-9a-z]{5} が Webhook のコンポーネントです。
$ kubectl get pods -A
=>
NAMESPACE NAME READY STATUS RESTARTS AGE
demo nginx-77b4fdf86c-l4k9j 1/1 Running 0 38m
external-secrets external-secrets-57ddc5b469-fqwcx 1/1 Running 0 23s
external-secrets external-secrets-cert-controller-6dc74f5db7-rb4md 0/1 Running 0 23s
external-secrets external-secrets-webhook-57ffc57cb8-lfb9c 0/1 Running 0 23s
gmp-system alertmanager-0 2/2 Running 0 4h19m
gmp-system collector-4vvxm 2/2 Running 0 4h17m
4. ESO のカスタムリソース作成
ESO の構築には最低限 2つのカスタムリソースを作成する必要があります。
- SecretStore or ClusterSecretStore
- ExternalSecret
カスタムリソースの内容を適切に書き換えることでプラットフォームに合わせた ESO を構築することも出来ます。
ClusterSecretStore リソースの作成
ClusterSecretStore は ESMS へのアクセス方法を指定するカスタムリソースであり、この情報を元に ESO が ESMS から秘密情報を取得します。SecretStore リソースではなく、ClusterSecretStore リソースを使用するのは、 namespace を考慮する必要がなくなり、構築が容易なためです。
ClusterSecretStore のマニュフェストファイルの例を以下に示します。今回秘密情報を保管してある Google Cloud の Secret Manager にアクセスするためにはこれまで設定してきた内容をもとにして以下のパラメータを適切な値に変更する必要があります。
spec.provider.gcpsm.projectID
: GSA が存在する Google Cloud のプロジェクト IDspec.provider.gcpsm.auth.clusterLocation
:GKE のリージョン or ゾーンspec.provider.gcpsm.auth.clusterName
:GKE クラスタの名前spec.provider.gcpsm.auth.clusterProjectID
:GKE クラスタが所属している Google Cloud のプロジェクト IDspec.provider.gcpsm.auth.serviceAccountRef.name
:GSA と紐づけた KSA の名前spec.provider.gcpsm.auth.serviceAccountRef.namespace
:KSA の namespace
Google Cloud Secret Manager 以外の ESMS を使用する場合は spec.provider 以下の設定を ESO 公式ドキュメントの Provider の項目を参考に ESMS に合わせて変更する必要があります。
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: gcp-store
spec:
provider:
gcpsm:
projectID: PROJECT_ID
auth:
workloadIdentity:
clusterLocation: COMPUTE_REGION
clusterName: CLUSTER_NAME
clusterProjectID: PROJECT_ID
serviceAccountRef:
name: KSA_ACCOUNT_NAME
namespace: NAMESPACE
作成したマニュフェストファイルはkubectl apply -f ClusterSecretStore.yaml
でデプロイします。
ExternalSecret リソースの作成
ExternalSecret は ClusterSecretStore で指定した ESMS からどのようなデータを取得し、どのように Secret オブジェクトとして保存するかを定義するカスタムリソースです。ExternalSecret のスコープをクラスタスコープにした ClusterExternalSecret カスタムリソースも存在します。
ここでは、ESMS から全ての秘密情報を取得し、Secret オブジェクトにストアするシンプルな ExternalSecret の設定例を示します。
spec.refreshInterval
:ESMS への fetch 頻度、0にすると更新を行わないspec.target.name
:取得したデータをストアする Secret オブジェクトの名前、ExternalSecret と同じ namespace で作成されますspec.target.creationPolicy
:Secret オブジェクトの内容を操作するためのポリシー(Owner
、Orphan
、Merge
、None
)spec.dataFrom.extract.key
:Secret Manager の秘密情報の名前
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: example
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: gcp-store
target:
name: SECRET_NAME
creationPolicy: Owner
dataFrom:
- extract:
key: KEY_NAME
作成したマニュフェストファイルはkubectl apply -f ExternalSecret.yaml
でデプロイします。
Secret の値確認
最後にESO が取得した秘密情報が Secret オブジェクトに保存されていることを確認します。まず、コマンドラインで Secret オブジェクトの値を確認し、次に Pod から Secret オブジェクトの値を参照できることを確認します。
コマンドラインから秘密情報を参照する
カスタムリソースが正常にデプロイされている場合、 ExternalSecret リソースで宣言した target.name
で Secret オブジェクトが作成されており、以下のコマンドで確認できます。(以下の出力はKEY_NAME=test-secret-info の場合)
$ kubectl get secret -n default
=>
NAME TYPE DATA AGE
test-secret-info Opaque 2 20h
Secret オブジェクトには Google Cloud Secret Manager から同期された秘密情報がストアされているため、これを取り出して確認します。
Secret オブジェクトの情報は Base64 エンコードされて保存されているため、秘密情報を平文として取り出すには Secret オブジェクトの秘密情報を取り出した後に Base64 デコードをする必要があります。Linux には Base64 エンコード/デコードを行う base64 コマンドがあるため、これを使用します。
同期が適切に行われている場合は以下のコマンドを実行すると Secret Manager にて設定した値が取得できていることが確認できます。
# Secret 取得の例
$ kubectl get secret -n default SECRET_NAME -o jsonpath='{.data.pass}' | base64 -d | xargs -I{} echo "pass:{}"
=>
pass:super-weak-passphrase
Podから秘密情報を参照する
ESO において ESMS の秘密情報は Secret オブジェクトに保存されるため、 Secret の内容を Pod に環境変数として読み込ませることなどが出来ます。これにより、秘密情報を必要とするサービスをデプロイする際に、 Pod が作成される瞬間に Secret オブジェクトから自動で秘密情報を挿入することが可能になります。
ここを参考に以下のような spec.containers.env
に Secret オブジェクトから秘密情報を取得するように宣言した Pod を構成するマニュフェストファイルを作成し、デプロイします。
apiVersion: v1
kind: Pod
metadata:
name: secret-keyref-pod
spec:
containers:
- name: password-container
image: k8s.gcr.io/busybox
command: [ "/bin/sh", "-c", "echo user:$USER password:$PASSWORD" ]
env:
- name: USER
valueFrom:
secretKeyRef:
name: SECRET_NAME
key: user
- name: PASSWORD
valueFrom:
secretKeyRef:
name: SECRET_NAME
key: pass
restartPolicy: Never
この Pod 自体は USER, PASSWORD 環境変数を標準出力に表示して終了するだけの Pod です。この Pod のログは以下のコマンドを実行することで確認することが出来ます。ログを見ると Secret オブジェクトから秘密情報を参照できていることが確認できます。
$ kubectl logs -n default secret-keyref-pod
=>
user:default-user password:super-weak-passphrase
6-3. ESO のメリット・デメリット
メリット
- Secret Manager が管理を行うので自分で秘密情報を暗号化復号をする必要がない
- 秘密情報とマニュフェストファイルの管理が別になるため、秘密情報漏洩のリスクが軽減する
- 構成に柔軟性があり、同じクラスタ内での細かいアクセスコントロールがし易い
- Secret はノードのディスクには保存せず tmpfs に保存するため、ノード内に Secret を使用する Pod がない場合にノードから Secret が消される
デメリット
- 秘密情報とマニュフェストファイルの管理が別になるため、管理が複雑になる
- SOPS などのファイル暗号化ツールに比べて導入の難易度が高い
- 同期の間隔次第では最新の秘密情報にアクセスできない時間が存在する
7. 3つのツールの比較
3つのツールについて 6つの指標から比較を行った表を示します。評価は動作検証と仕組みに関する調査からの主観的なものとなっていますのでご了承ください。
8. まとめとインターンの感想
本インターンを通して、SOPS、SSCD、ESO の3パターンの方法で秘密情報の管理について学び、それぞれの方法の比較を行いました。
実際の業務に携わった経験がないため、メリット・デメリットを考えることは非常に困難でしたが、様々なシーンに応じて使い分けるイメージができてよかったです。
各方法にメリット・デメリットがみられたため、唯一解が出ないことがもどかしくもあり、面白さもあったと思います。
竹下
コンテナや、Kubernetes に関して初めてのことばかりで、メンターの方々に助けをいただきながら調査・検証を進めていきました。
今回は、自分の研究分野と関わりのあるセキュリティの部分での調査・検証を行ったので、非常に興味深い内容がたくさんあり楽しいインターンでした。
実際に Google Cloud 上でクラスターを作り、シェル上で公式のドキュメントを見ながら試行錯誤して検証するのがとても新鮮で、実際に検証が成功したときは嬉しい気持ちになりました。このような貴重な体験を、これからの自分の研究や仕事で活かしていけたらなと思います。
最後に、株式会社スリーシェイクの皆様やメンターの方々には、ツールを調査・検証する上で、講義やミーティングなどでの情報共有、指導などを行って頂きました。深く感謝申し上げます。また、一緒に調査・検証を行ってくれたチーム02の皆様、同じ期間でとても興味深い発表をして下さったチーム01の皆様と楽しくインターンの時間を共有することができてよかったです。ありがとうございました。
中林
Kubernetes という技術に触れることが初めてで手探りな状態でスタートしました。そのため、2週間という期間はとても短いと感じましたが、限られた期間の中で全力で技術研究に取り組むという濃密な体験をすることができ、その中で様々な学びを得ることが出来ました。
特に技術研究のテーマである秘密情報の管理は Kubernetes に関わらず難しい問題ですが、Kubernetes の場合は様々な選択肢があり、権限の柔軟性も素晴らしいと感じました。また、調査した 3つのツールは宣言的にリソースを記述し、コントローラが自動で管理する Kubernetes の強みを生かし、シームレスに Kubernetes エコシステムに組み込む事ができるため、調査していてよく出来ていると感心することが多々あり、興味の尽きない 2週間となりました。
また、 Kubernetes の知識が以外にもインターン生との繋がりやメンターの方々からのインプット、アウトプットに関するアドバイスなど様々なものを得ることが出来ました。貴重な経験を大切にし自分の糧としていきたい思います。
最後に株式会社スリーシェイクの皆様には、技術研究をするにあたり必要なツールの提供と適切なご指導を頂きました。感謝申し上げます。
引野
1週間遅れで本インターンに参加となり、ハイペースで知識をインプットせざるを得ない状況になりましたが、このような状況のおかげで Docker と Kubernetes の用語や概念に短時間で慣れることができました。
初日は他のメンバーが作ってくれた資料に沿ってコマンドを打ったものの、予備知識がほとんどないこともあって何が何だかよくわかりませんでした。しかしながら、2日目以降のインプットする段階の時に、「あのコマンドはこういうことをしていたのか!」という発見があり、スムーズにインプットすることができたと思います。
私のメインの研究分野は医療ビッグデータを用いたデータ解析であるため、一見今回のインターンと何の関係もないように見えますが、解析結果の再現性を担保したり、解析環境の構築を容易にしたりするといった観点からIaCの考え方、コンテナの利用など非常に参考になりました。
右も左もわからない状態の自分を手厚くサポートしてくれたメンターの方々および竹下さん、中林さんには感謝してもしつくせません。本当にありがとうございました。
9. 参考文献
- AWS KMS の概念 – AWS Key Management Service,
- SOPS: Secrets OPerationS! | MoT Lab (GO Inc. Engineering Blog),
- jkroepke/helm-secrets: A helm plugin that help manage secrets with Git workflow and store them anywhere,
- helm-secretsを使ってみた – Qiita,
- Google Secret Manager Provider for Secret Store CSI Driver,
- Secrets Store CSI Driver,
- secrets-store-csi-driver,
- Using Helm plugin manager,
- Secret | Kubernetes,
- Introduction – External Secrets Operator,
- Introduction to External Secret Operator – DEVOPS DONE RIGHT,
- Managing Kubernetes Secrets with the External Secrets Operator,
- Workload Identity を使用する | Google Kubernetes Engine (GKE) | Google Cloud,
- Workload Identity | Google Kubernetes Engine (GKE) | Google Cloud,
- GKE Workload Identity について詳しく調べてみた(DeepDive) | MIXI DEVELOPERS,