fbpx

Google Secret Manager と External Secrets Operator を連携して GKE で secret を自動生成 #external-secrets #GCP #GKE #SecretManager

はじめに

KubernetesでSecret manifestを作成する際に、Secretのデータはただ単にbase64でエンコードしているだけなので、Kubernetes APIサーバにアクセスしてSecretリソースを閲覧できる権限があれば、誰でもSecretのデータをbase64でデコードして、データの中身を見ることができてしまいます。そのため、Secretデータなどの秘匿情報を安全に管理する方法を別途ユーザー自身で検討する必要があります。

本Blogでは、そのSecretデータなどの秘匿情報を安全に管理する一つの手段として、Secret管理コンポーネントとなる External Secrets Operator を利用して、Google CloudのSecret Management Systemとなる Secret Manager からSecretを取得し、Google Kubernetes Engine(GKE)で安全に秘匿情報を利用する方法について検証しましたので、ざっくりと紹介します。

External Secrets Operatorとは

概要

公式HPでは以下のように紹介されています。

The External Secrets Operator extends Kubernetes with Custom Resources, which define where secrets live and how to synchronize them. The controller fetches secrets from an external API and creates Kubernetes secrets. If the secret from the external API changes, the controller will reconcile the state in the cluster and update the secrets accordingly.

https://external-secrets.io/latest/introduction/overview/

KubernetesにExternal Secrets Operator(以降、ESO)をデプロイすることで、AWS Secrets ManagerやGoogle Secret Manager、Azure Key VaultのようなSecret Management Systemから秘匿情報を自動取得して、Kubernetes Cluster内で自動的にSecretリソースを作成することができます。Secret Management System側の秘匿情報に変更があった場合は、ESO(controller)がその変更を自動検知して、Cluster内のSecretリソースも自動更新します(reconciliation loop)

ESOのCustom Resorceでは主に、Secret Management Systemにどのようにアクセスするかを定義する SecretStore リソースと、どのようなデータを取得するかを定義する ExternalSecrets リソースの2種類が存在します。また、SecretStore リソースの関連で、cluster-wideな(全てのnamespaceに適用される)SecretStoreとして、ClusterSecretStore リソースも存在します。

Google Secret Manager と External Secrets Operator の連携イメージ

今回のGoogle Cloudにおける検証ベースになりますが、ESOの各Custom ResourceとGoogle Secret Managerの連携イメージは以下のようになります。検証ではcluster-wideなClusterSecretStoreリソースを利用する前提です。最初にESOがデプロイされると、常にESOはExternalSecretリソースをreconciliation loopで自動更新している状態になります。Custom ResourceであるExternalSecretsリソースが作成されると、ExternalSecretリソースで指定したClusterSecretStoreリソースを参照し、ClusterSecretStoreリソースに設定されているアクセス先(Google Cloud)の認証情報や、取得するsecret manager secretの情報をもとに、external API をインスタンス化します(①)。external API がインスタンス化されるとESOはexternal APIからsecret情報を抽出し、ExternalSecretリソースで定義された「作成するsecretのnameやdata.keyなど」の情報もとに、実際にCluster内でsecretを自動生成します(②)。ESOは常にexternal APIと自動生成したsecret valuesが同期しているかどうか、チェックします。

[検証] Google Secret Manager と External Secrets Operator を連携して GKE で secret を自動生成

それでは、前述の連携イメージをもとに、実際にESOをデプロイしてsecretが自動生成されるか確認していきます。

前提

  • Kubernetes Engine API / Secret Manager API 有効化済み
  • Workload Identityが有効化された GKE Cluster を作成済み
  • CloudShellで作業実施

Google Secret Manager に 認証情報(version) を登録

事前準備として、ESOが取得するシークレットをGoogle Secret Managerであらかじめ作成します。

  • Google Cloud コンソール画面のナビゲーションメニューで、[セキュリティ] > [Secret Manager] から、シークレットを新たに作成します。シークレットの名前、値は検証用として以下のように設定しました。
    • 名前:test-secret
    • シークレットの値:test-password

GKE に External Secrets Operator をインストール

GKE ClusterにESOをインストールしていきます。基本的な流れは公式HPに記載の手順に沿いますが、今回のproviderはGoogle Cloudになるので、SecretStoreの記載方法やその他Google Cloud特有の設定については、こちらのリファレンスを参考に進めます。

  • Cloud Shell を利用して、インストール先のGKE Clusterに接続します。
$ gcloud container clusters get-credentials <cluster_name> --zone <zone_name> --project <project_name>
~~~
Fetching cluster endpoint and auth data.
kubeconfig entry generated for <cluster_name>.
  • インストールはHelm Chartを利用します。Helm Chart repositoryにexternal-secrets repoを追加して、Clusterにインストールします。
$ helm repo add external-secrets https://charts.external-secrets.io
~~~
"external-secrets" has been added to your repositories
$ helm install external-secrets \
   external-secrets/external-secrets \
    -n external-secrets \
    --create-namespace
~~~
NAME: external-secrets
LAST DEPLOYED: Mon Oct 23 08:53:13 2023
NAMESPACE: external-secrets
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
external-secrets has been deployed successfully!

In order to begin using ExternalSecrets, you will need to set up a SecretStore
or ClusterSecretStore resource (for example, by creating a 'vault' SecretStore).

More information on the different types of SecretStores and how to configure them
can be found in our Github: https://github.com/external-secrets/external-secrets

インストールが完了すると、ESOに関連するdeploymentなどがデプロイされます。

$ kubectl get all -n external-secrets
NAME                                                    READY   STATUS    RESTARTS   AGE
pod/external-secrets-969fb45c8-mqvk8                    1/1     Running   0          19h
pod/external-secrets-cert-controller-757f9d86d4-5tlzt   1/1     Running   0          19h
pod/external-secrets-webhook-8468995c6f-lz6m5           1/1     Running   0          19h

NAME                               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/external-secrets-webhook   ClusterIP   10.116.3.210   <none>        443/TCP   20h

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

NAME                                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/external-secrets-969fb45c8                    1         1         1       20h
replicaset.apps/external-secrets-cert-controller-757f9d86d4   1         1         1       20h
replicaset.apps/external-secrets-webhook-8468995c6f           1         1         1       20h

Service Accountの作成/Workload Identityの設定

GKE ClusterにインストールしたESOが、Google Secret Managerで作成したシークレットにアクセスするためのGoogle Service Account(以降、GSA)やKubernetes Service Account(以降、KSA)作成と、Workload Identityの設定を実施します。ESOのGoogle Cloudへの認証は、GSAのアクセスキーを発行して利用する方法もありますが、アクセスキー流出などのリスクもあり、よりセキュアなWorkload Identityを利用する方法を実践していきます。

  • secretを自動生成するKubrenetes namespace を作成します。検証では「team1」というnamespaceを作成します。
$ kubectl create ns team1
namespace/team1 created
  • namespace「team1」に紐づくKSAを作成します。このKSAはWorkload Identityの設定により、最終的にGSA「external-secrets-team1@<project_name>.iam.gserviceaccount.com」に紐づけて、Google Cloudの認証を行う想定です。
$ vi serviceaccount.yaml
~~~
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-secrets-team1
  namespace: team1
  annotations:
    iam.gke.io/gcp-service-account: external-secrets-team1@<project_name>.iam.gserviceaccount.com 
$ kubectl apply -f serviceaccount.yaml 
serviceaccount/external-secrets-team1 created
  • 次に、CloudShellで以下gcloudコマンドを実行してGSAを作成します。
$ gcloud iam service-accounts create external-secrets-team1 \
    --project=<project_name>
~~~
Created service account [external-secrets].
  • 作成したGSAに、Google Secret Managerのシークレットへのアクセスに必要なロール roles/secretmanager.secretAccessor を付与します。
$ gcloud projects add-iam-policy-binding <project_name> \
    --member "serviceAccount:external-secrets-team1@<project_name>.iam.gserviceaccount.com" \
    --role "roles/secretmanager.secretAccessor"
  • また、GCP アクセストークンの生成に必要なロール roles/iam.serviceAccountTokenCreator を付与します。
$ gcloud projects add-iam-policy-binding <project_name> \
    --member "serviceAccount:external-secrets-team1@<project_name>.iam.gserviceaccount.com" \
    --role "roles/iam.serviceAccountTokenCreator"
  • 権限 iam.serviceAccounts.getIamPolicy だけを含むカスタムロール getIamPolicy を作成して、GSAに付与します。 権限 iam.serviceAccounts.getIamPolicy はgcloudコマンドでWorkloadIdentity設定時に必要となる権限です。
$ gcloud iam roles create getIamPolicy --project=<project_name> \
    --title=getIamPolicy \
    --permissions="iam.serviceAccounts.getIamPolicy" --stage=GA
$ gcloud projects add-iam-policy-binding <project_name> \
    --member "serviceAccount:external-secrets-team1@<project_name>.iam.gserviceaccount.com" \
    --role "projects/<project_name>/roles/getIamPolicy"
  • 作成したGSAとKSAのIAM ポリシーバインディングを実施します。
$ gcloud iam service-accounts add-iam-policy-binding external-secrets-team1@<project_name>.iam.gserviceaccount.com \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:<project_name>.svc.id.goog[team1/external-secrets-team1]" \
    --project <project_name>
  • KSAに、作成したGSAのメールアドレスでアノテーションを付与します。
$ kubectl annotate serviceaccount external-secrets \
    --namespace external-secrets \
    iam.gke.io/gcp-service-account=external-secrets-team1@<project_name>.iam.gserviceaccount.com

External Secrets / Cluster Secret Store リソースデプロイ

ESOを動作されるための準備が整ったので、ExternalSecretリソースとClusterSecretStoreリソースを作成して、実際にsecretが自動生成されるか確認します。

  • 以下のClusterSecretStoreを用意して、デプロイします。
$ vi clustersecretstore.yaml
~~~
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: test-secretstore
spec:
  provider:
    gcpsm:
      projectID: <project_name>
      auth:
        workloadIdentity:
          clusterLocation: asia-northeast1-b
          clusterName: <cluster_name>
          clusterProjectID: <project_name>
          serviceAccountRef:
            name: external-secrets-team1
            namespace: team1
$ kubectl apply -f clustersecretstore.yaml 
~~~
clustersecretstore.external-secrets.io/test-secretstore created
  • 以下のExternalSecretsを用意して、デプロイします。
$ vi externalsecret.yaml
~~~
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: test-externalsecret
  namespace: team1
spec:
  refreshInterval: 1h
  secretStoreRef:
    kind: ClusterSecretStore
    name: test-secretstore
  target:
    name: test-secret
    creationPolicy: Owner
  data:
  - secretKey: password
    remoteRef:
      key: test-secret
      version: latest
$ kubectl apply -f externalsecret.yaml 
externalsecret.external-secrets.io/test-externalsecret created
  • 問題無くリソースがデプロイされると、「team1」namespaceにsecretが自動生成されているのが確認できます。
$ kubectl get secret -n team1
NAME          TYPE     DATA   AGE
test-secret   Opaque   1      96m
  • secret の value は base64 でエンコードされているので、以下のようにデコードして実際のvalueを確認することができます。
$ kubectl get secret test-secret -n team1 -o jsonpath='{.data.password}' | base64 -d
test-password

まとめ

簡単ですが Google Secret Manager と External Secrets Operator を連携して GKE で secret を自動生成する方法について紹介しました。実際のESO利用イメージとしては、namespace毎にチームを分け、ExternalSecretsリソースやSecretStoreリソースを各チームで管理させる(他のチームのリソースを触れないようにする)適切なscopingを実施したいケースが多いと思いますので、機会があればそちらも検証/紹介したいと思います。

新規CTA