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


このチュートリアルでは、Google Kubernetes Engine(GKE)クラスタで使用される機密データを Secret Manager に保存する方法について説明します。Workload Identity Federation for GKE とGoogle Cloud クライアント ライブラリを使用して、Pod からデータに安全にアクセスする方法についても説明します。

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

このページは、クラスタ内のストレージから機密データを移動するセキュリティ スペシャリストを対象としています。 Google Cloud のコンテンツで使用されている一般的なロールとタスクの例の詳細については、一般的な GKE Enterprise ユーザーロールとタスクをご覧ください。

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

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

目標

  • 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

次のステップ