クライアント ライブラリを使用して GKE クラスタの外部に保存されている Secret にアクセスする


このチュートリアルでは、Google Kubernetes Engine(GKE)クラスタで使用されるセンシティブ データを Secret Manager に保存し、GKE 用 Workload Identity 連携と Google Cloud クライアント ライブラリを使用して Pod のデータに安全にアクセスする方法について説明します。このチュートリアルは、クラスタ内のストレージからセンシティブ データを移動するセキュリティ管理者を対象としています。

クラスタ ストレージの外部にセンシティブ データを保存すると、攻撃が発生した場合に不正アクセスされるリスクが軽減されます。GKE 用 Workload Identity 連携を使用してデータにアクセスすると、有効期間の長いサービス アカウント キーの管理に伴うリスクを回避し、クラスタ内の RBAC ルールではなく、Identity and Access Management(IAM)を使用して Secret へのアクセスを制御できます。Secret Manager や HashiCorp Vault などの外部 Secret ストア プロバイダを使用できます。

このチュートリアルでは、GKE Autopilot クラスタを使用します。この操作を GKE Standard で行う場合は、GKE 用 Workload Identity 連携を手動で有効にする必要があります。

GKE 用 Workload Identity 連携を使用すると、静的なサービス アカウント キー ファイルのような安全性の低い手段を使用することなく、GKE ワークロードから Google Cloud APIs にアクセスできます。このチュートリアルでは Secret Manager を例として使用しますが、同じ手順で他の Google Cloud APIs にアクセスすることもできます。詳細については、GKE 用 Workload Identity 連携をご覧ください。

目標

  • Google Cloud Secret Manager で Secret を作成します。
  • GKE Autopilot クラスタ、Kubernetes Namespace、Kubernetes サービス アカウントを作成します。
  • IAM 許可ポリシーを作成して、Secret の Kubernetes サービス アカウントへのアクセスを許可します。
  • テスト アプリケーションを使用してサービス アカウントへのアクセスを確認します。
  • Secret Manager API を使用して Secret にアクセスするサンプルアプリを実行します。

費用

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。

始める前に

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. Install the Google Cloud CLI.
  3. To initialize the gcloud CLI, run the following command:

    gcloud init
  4. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  5. Make sure that billing is enabled for your Google Cloud project.

  6. Enable the Kubernetes Engine and Secret Manager APIs:

    gcloud services enable container.googleapis.com secretmanager.googleapis.com
  7. Install the Google Cloud CLI.
  8. To initialize the gcloud CLI, run the following command:

    gcloud init
  9. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  10. Make sure that billing is enabled for your Google Cloud project.

  11. Enable the Kubernetes Engine and Secret Manager APIs:

    gcloud services enable container.googleapis.com secretmanager.googleapis.com
  12. Grant roles to your user account. Run the following command once for each of the following IAM roles: roles/secretmanager.admin, roles/container.clusterAdmin

    gcloud projects add-iam-policy-binding PROJECT_ID --member="user:USER_IDENTIFIER" --role=ROLE
    • Replace PROJECT_ID with your project ID.
    • Replace USER_IDENTIFIER with the identifier for your user account. For example, user:myemail@example.com.

    • Replace ROLE with each individual role.

環境の準備

このチュートリアルのサンプル ファイルを含む GitHub リポジトリのクローンを作成します。

git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
cd ~/kubernetes-engine-samples/security/wi-secrets

Secret Manager で Secret を作成する

  1. 次の例は、Secret の作成に使用するデータを示しています。

    key=my-api-key
  2. サンプルデータを格納する Secret を作成します。

    gcloud secrets create bq-readonly-key \
        --data-file=manifests/bq-readonly-key \
        --ttl=3600s
    

    このコマンドは次の処理を行います。

    • us-central1 Google Cloud リージョンでサンプルキーを使用して新しい Secret Manager Secret を作成します。
    • コマンドを実行してから 1 時間後に Secret を期限切れに設定します。

クラスタと Kubernetes リソースを作成する

GKE クラスタ、Kubernetes Namespace、Kubernetes サービス アカウントを作成します。2 つの Namespace を作成します(1 つは読み取り専用アクセス用、もう 1 つは Secret への読み取り / 書き込みアクセス用)。また、GKE 用 Workload Identity 連携で使用する Kubernetes サービス アカウントを各 Namespace に作成します。

  1. GKE Autopilot クラスタを作成します。

    gcloud container clusters create-auto secret-cluster \
        --region=us-central1
    

    クラスタのデプロイには 5 分ほどかかります。Autopilot クラスタでは常に GKE 用 Workload Identity 連携が有効になっています。GKE Standard クラスタを使用する場合は、続行する前に GKE 用 Workload Identity 連携を手動で有効にする必要があります。

  2. readonly-ns Namespace と admin-ns Namespace を作成します。

    kubectl create namespace readonly-ns
    kubectl create namespace admin-ns
    
  3. readonly-sa Kubernetes サービス アカウントと admin-sa Kubernetes サービス アカウントを作成します。

    kubectl create serviceaccount readonly-sa --namespace=readonly-ns
    kubectl create serviceaccount admin-sa --namespace=admin-ns
    

IAM 許可ポリシーを作成する

  1. readonly-sa サービス アカウントに、シークレットへの読み取り専用アクセス権を付与します。

    gcloud secrets add-iam-policy-binding bq-readonly-key \
        --member=principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/readonly-ns/sa/readonly-sa \
        --role='roles/secretmanager.secretAccessor' \
        --condition=None
    

    次のように置き換えます。

    • PROJECT_NUMBER: 数値による Google Cloud プロジェクト番号。
    • PROJECT_ID: Google Cloud プロジェクト ID。
  2. admin-sa サービス アカウントに、Secret への読み取り / 書き込みアクセス権を付与します。

    gcloud secrets add-iam-policy-binding bq-readonly-key \
        --member=principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/admin-ns/sa/admin-sa \
        --role='roles/secretmanager.secretAccessor' \
        --condition=None
    gcloud secrets add-iam-policy-binding bq-readonly-key \
        --member=principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/admin-ns/sa/admin-sa \
        --role='roles/secretmanager.secretVersionAdder' \
        --condition=None
    

Secret へのアクセスを確認する

各 Namespace にテスト Pod をデプロイして、読み取り専用アクセスと読み取り / 書き込みアクセスを確認します。

  1. 読み取り専用 Pod マニフェストを確認します。

    # Copyright 2022 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    apiVersion: v1
    kind: Pod
    metadata:
      name: readonly-test
      namespace: readonly-ns
    spec:
      containers:
      - image: google/cloud-sdk:slim
        name: workload-identity-test
        command: ["sleep","infinity"]
        resources:
          requests:
            cpu: "150m"
            memory: "150Mi"
      serviceAccountName: readonly-sa

    この Pod は、readonly-ns Namespace 内の readonly-sa サービス アカウントを使用します。

  2. 読み取り / 書き込み Pod マニフェストを確認します。

    # Copyright 2022 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    apiVersion: v1
    kind: Pod
    metadata:
      name: admin-test
      namespace: admin-ns
    spec:
      containers:
      - image: google/cloud-sdk:slim
        name: workload-identity-test
        command: ["sleep","infinity"]
        resources:
          requests:
            cpu: "150m"
            memory: "150Mi"
      serviceAccountName: admin-sa

    この Pod は、admin-ns Namespace 内の admin-sa サービス アカウントを使用します。

  3. テスト Pod をデプロイします。

    kubectl apply -f manifests/admin-pod.yaml
    kubectl apply -f manifests/readonly-pod.yaml
    

    Pod の実行が開始されるまでに数分かかることがあります。進捗状況をモニタリングするには、次のコマンドを実行します。

    watch kubectl get pods -n readonly-ns
    

    Pod のステータスが RUNNING に変わったら、Ctrl+C を押してコマンドラインに戻ります。

読み取り専用アクセスをテストする

  1. readonly-test Pod でシェルを開きます。

    kubectl exec -it readonly-test --namespace=readonly-ns -- /bin/bash
    
  2. Secret を読み取ります。

    gcloud secrets versions access 1 --secret=bq-readonly-key
    

    出力は key=my-api-key です。

  3. 新しいデータを Secret に書き込みます。

    printf "my-second-api-key" | gcloud secrets versions add bq-readonly-key --data-file=-
    

    出力は次のようになります。

    ERROR: (gcloud.secrets.versions.add) PERMISSION_DENIED: Permission 'secretmanager.versions.add' denied for resource 'projects/PROJECT_ID/secrets/bq-readonly-key' (or it may not exist).
    

    読み取り専用サービス アカウントを使用する Pod は、Secret の読み取りのみ可能であり、新しいデータを書き込むことはできません。

  4. Pod を終了します。

    exit
    

読み取り / 書き込みアクセスをテストする

  1. admin-test Pod でシェルを開きます。

    kubectl exec -it admin-test --namespace=admin-ns -- /bin/bash
    
  2. Secret を読み取ります。

    gcloud secrets versions access 1 --secret=bq-readonly-key
    

    出力は key=my-api-key です。

  3. 新しいデータを Secret に書き込みます。

    printf "my-second-api-key" | gcloud secrets versions add bq-readonly-key --data-file=-
    

    出力は次のようになります。

    Created version [2] of the secret [bq-readonly-key].
    
  4. 新しい Secret バージョンを確認します。

    gcloud secrets versions access 2 --secret=bq-readonly-key
    

    出力は my-second-api-key です。

  5. Pod を終了します。

    exit
    

Pod には、Pod マニフェストで使用される Kubernetes サービス アカウントに付与されるアクセスレベルのみを取得します。admin-ns Namespace の admin-sa Kubernetes アカウントを使用する Pod は Secret の新しいバージョンを書き込むことができますが、readonly-sa Kubernetes サービス アカウントを使用する readonly-ns Namespace の Pod は Secret を読み取ることのみができます。

コードから Secret にアクセスする

このセクションでは、次の操作を行います。

  1. クライアント ライブラリを使用して、Secret Manager で Secret を読み取るサンプル アプリケーションをデプロイします。

  2. アプリケーションが Secret にアクセスできることを確認します。

可能な限り、Secret Manager API を使用して、アプリケーション コードから Secret Manager の Secret にアクセスする必要があります。

  1. サンプル アプリケーションのソースコードを確認します。

    // Copyright 2022 Google LLC
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    //
    //     http://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.
    
    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"os"
    
    	secretmanager "cloud.google.com/go/secretmanager/apiv1"
    	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
    )
    
    func main() {
    
            // Get environment variables from Pod spec.
            projectID := os.Getenv("PROJECT_ID")
            secretId := os.Getenv("SECRET_ID")
            secretVersion := os.Getenv("SECRET_VERSION")
    
            // Create the Secret Manager client.
            ctx := context.Background()
            client, err := secretmanager.NewClient(ctx)
            if err != nil {
                    log.Fatalf("failed to setup client: %v", err)
            }
            defer client.Close()
    
            // Create the request to access the secret.
            accessSecretReq := &secretmanagerpb.AccessSecretVersionRequest{
                    Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", projectID, secretId, secretVersion),
            }
    
            secret, err := client.AccessSecretVersion(ctx, accessSecretReq)
            if err != nil {
                    log.Fatalf("failed to access secret: %v", err)
            }
    
            // Print the secret payload.
            //
            // WARNING: Do not print the secret in a production environment - this
            // snippet is showing how to access the secret material.
            log.Printf("Welcome to the key store, here's your key:\nKey: %s", secret.Payload.Data)
    }
    

    このアプリケーションは、Secret Manager API を呼び出して Secret を取得します。

  2. サンプル アプリケーションの Pod マニフェストを確認します。

    # Copyright 2022 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    apiVersion: v1
    kind: Pod
    metadata:
      name: readonly-secret-test
      namespace: readonly-ns
    spec:
      containers:
      - image: us-docker.pkg.dev/google-samples/containers/gke/wi-secret-store:latest
        name: secret-app
        env:
          - name: PROJECT_ID
            value: "YOUR_PROJECT_ID"
          - name: SECRET_ID
            value: "bq-readonly-key"
          - name: SECRET_VERSION
            value: "latest"
        resources:
          requests:
            cpu: "125m"
            memory: "64Mi"
      serviceAccountName: readonly-sa

    このマニフェストの内容は次のとおりです。

    • readonly-sa サービス アカウントを使用する readonly-ns Namespace に Pod を作成します。
    • Google イメージ レジストリからサンプル アプリケーションを pull します。このアプリケーションは、Google Cloud クライアント ライブラリを使用して Secret Manager API を呼び出します。アプリケーション コードは、リポジトリ内の /main.go で確認できます。
    • 使用するサンプル アプリケーションの環境変数を設定します。
  3. サンプル アプリケーションの環境変数を置き換えます。

    sed -i "s/YOUR_PROJECT_ID/PROJECT_ID/g" "manifests/secret-app.yaml"
    
  4. サンプルアプリをデプロイします。

    kubectl apply -f manifests/secret-app.yaml
    

    Pod が機能し始めるまでに数分かかることがあります。Pod がクラスタに新しいノードを必要とする場合、GKE がノードのプロビジョニングしている間に CrashLoopBackOff タイプのイベントが発生することがあります。ノードが正常にプロビジョニングされると、クラッシュは停止します。

  5. Secret へのアクセスを確認します。

    kubectl logs readonly-secret-test -n readonly-ns
    

    出力は my-second-api-key です。出力が空白の場合、Pod がまだ実行されていない可能性があります。数分待ってから、もう一度お試しください。

その他の方法

機密データを Pod にマウントする必要がある場合は、GKE 用の Secret Manager アドオン(プレビュー)を使用します。このアドオンは、GKE クラスタで Kubernetes Secret Store CSI ドライバの Google Cloud Secret Manager プロバイダをデプロイして管理します。手順については、GKE で Secret Manager アドオンを使用するをご覧ください。

マウントされたボリュームとして Secret を提供すると、次のようなリスクがあります。

  1. マウントされたボリュームは、ディレクトリ トラバーサル攻撃を受けやすくなります。
  2. デバッグ エンドポイントを開くなどの構成ミスによって、環境変数が不正使用される可能性があります。

可能な限り、Secret Manager API を使用してプログラムで Secret にアクセスすることをおすすめします。手順については、このチュートリアルのサンプル アプリケーションを使用するか、Secret Manager クライアント ライブラリをご覧ください。

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。

リソースを個別に削除する

  1. クラスタを削除します。

    gcloud container clusters delete secret-cluster \
        --region=us-central1
    
  2. Secret Manager で Secret を削除します。

    gcloud secrets delete bq-readonly-key
    

    この手順を行わないと、作成時に --ttl フラグを設定したため、Secret は自動的に期限切れになります。

プロジェクトを削除する

    Delete a Google Cloud project:

    gcloud projects delete PROJECT_ID

次のステップ