クライアント ライブラリを使用して 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 を作成します。
  • IAM サービス アカウントを作成して構成し、Secret にアクセスします。
  • GKE Autopilot クラスタ、Kubernetes Namespace、Kubernetes サービス アカウントを作成します。
  • テスト アプリケーションを使用してサービス アカウントへのアクセスを確認します。
  • Secret Manager API を使用して Secret にアクセスするサンプルアプリを実行します。

料金

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

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

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

始める前に

  1. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
  2. Google Cloud CLI をインストールします。
  3. gcloud CLI を初期化するには:

    gcloud init
  4. Google Cloud プロジェクトを作成または選択します

    • Google Cloud プロジェクトを作成します。

      gcloud projects create PROJECT_ID

      PROJECT_ID は、作成する Google Cloud プロジェクトの名前に置き換えます。

    • 作成した Google Cloud プロジェクトを選択します。

      gcloud config set project PROJECT_ID

      PROJECT_ID は、実際の Google Cloud プロジェクト名に置き換えます。

  5. Google Cloud プロジェクトで課金が有効になっていることを確認します

  6. Kubernetes Engine and Secret Manager API を有効にします。

    gcloud services enable container.googleapis.comsecretmanager.googleapis.com
  7. Google Cloud CLI をインストールします。
  8. gcloud CLI を初期化するには:

    gcloud init
  9. Google Cloud プロジェクトを作成または選択します

    • Google Cloud プロジェクトを作成します。

      gcloud projects create PROJECT_ID

      PROJECT_ID は、作成する Google Cloud プロジェクトの名前に置き換えます。

    • 作成した Google Cloud プロジェクトを選択します。

      gcloud config set project PROJECT_ID

      PROJECT_ID は、実際の Google Cloud プロジェクト名に置き換えます。

  10. Google Cloud プロジェクトで課金が有効になっていることを確認します

  11. Kubernetes Engine and Secret Manager API を有効にします。

    gcloud services enable container.googleapis.comsecretmanager.googleapis.com
  12. Google アカウントにロールを付与します。次の IAM ロールごとに次のコマンドを 1 回実行します。 roles/secretmanager.admin, roles/container.clusterAdmin, roles/iam.serviceAccountAdmin

    gcloud projects add-iam-policy-binding PROJECT_ID --member="user:EMAIL_ADDRESS" --role=ROLE
    • PROJECT_ID は、実際のプロジェクト ID に置き換えます。
    • EMAIL_ADDRESS は実際のメールアドレスに置き換えます。
    • 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 を期限切れに設定します。

IAM サービス アカウントを構成する

  1. 読み取り専用アクセスと読み取り / 書き込みアクセス用に、2 つの新しい IAM サービス アカウントを作成します。

    gcloud iam service-accounts create readonly-secrets --display-name="Read secrets"
    gcloud iam service-accounts create readwrite-secrets --display-name="Read write secrets"
    
  2. readonly-secrets IAM サービス アカウントに、シークレットへの読み取り専用アクセス権を付与します。

    gcloud secrets add-iam-policy-binding bq-readonly-key \
        --member=serviceAccount:readonly-secrets@PROJECT_ID.iam.gserviceaccount.com \
        --role='roles/secretmanager.secretAccessor'
    
  3. readwrite-secrets IAM サービス アカウントに、Secret への読み取り / 書き込みアクセス権を付与します。

    gcloud secrets add-iam-policy-binding bq-readonly-key \
        --member=serviceAccount:readwrite-secrets@PROJECT_ID.iam.gserviceaccount.com \
        --role='roles/secretmanager.secretAccessor'
    gcloud secrets add-iam-policy-binding bq-readonly-key \
        --member=serviceAccount:readwrite-secrets@PROJECT_ID.iam.gserviceaccount.com \
        --role='roles/secretmanager.secretVersionAdder'
    

クラスタと 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
    
  4. IAM サービス アカウントを Kubernetes サービス アカウントにバインドします。

    gcloud iam service-accounts add-iam-policy-binding readonly-secrets@PROJECT_ID.iam.gserviceaccount.com \
        --member=serviceAccount:PROJECT_ID.svc.id.goog[readonly-ns/readonly-sa] \
        --role='roles/iam.workloadIdentityUser'
    gcloud iam service-accounts add-iam-policy-binding readwrite-secrets@PROJECT_ID.iam.gserviceaccount.com \
        --member=serviceAccount:PROJECT_ID.svc.id.goog[admin-ns/admin-sa] \
        --role='roles/iam.workloadIdentityUser'
    
  5. バインドされた IAM サービス アカウントの名前で Kubernetes サービス アカウントにアノテーションを付けます。

    kubectl annotate serviceaccount readonly-sa \
        --namespace=readonly-ns \
        iam.gke.io/gcp-service-account=readonly-secrets@PROJECT_ID.iam.gserviceaccount.com
    kubectl annotate serviceaccount admin-sa \
        --namespace=admin-ns \
        iam.gke.io/gcp-service-account=readwrite-secrets@PROJECT_ID.iam.gserviceaccount.com
    

これで、GKE 用 Workload Identity 連携を使用して Pod から Secret にアクセスするように構成されたクラスタが作成されました。

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 サービス アカウントにバインドされている IAM サービス アカウントに付与されるアクセスレベルのみを取得します。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. IAM サービス アカウントを削除します。

    gcloud iam service-accounts delete readonly-secrets@PROJECT_ID.iam.gserviceaccount.com
    gcloud iam service-accounts delete readwrite-secrets@PROJECT_ID.iam.gserviceaccount.com
    
  3. Secret Manager で Secret を削除します。

    gcloud secrets delete bq-readonly-key
    

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

プロジェクトを削除する

    Google Cloud プロジェクトを削除します。

    gcloud projects delete PROJECT_ID

次のステップ