클라이언트 라이브러리를 사용하여 GKE 클러스터 외부에 저장된 보안 비밀에 액세스


이 튜토리얼에서는 Secret Manager에서 Google Kubernetes Engine(GKE) 클러스터에 사용되는 민감한 정보를 저장하고 GKE 및 Google Cloud 클라이언트 라이브러리용 워크로드 아이덴티티 제휴를 사용하여 포드의 데이터에 보다 안전하게 액세스하는 방법을 보여줍니다. 이 튜토리얼은 클러스터 내 스토리지 외부로 민감한 정보를 이동하려는 보안 관리자를 대상으로 합니다.

민감한 정보를 클러스터 스토리지 외부에 저장하면 공격이 발생하더라도 데이터에 대한 무단 액세스 위험을 줄일 수 있습니다. GKE용 워크로드 아이덴티티 제휴를 사용하여 데이터에 액세스하면 장기 실행 서비스 계정 키 관리와 관련된 위험을 방지하고 클러스터 내 RBAC 규칙 대신 Identity and Access Management(IAM)를 사용하여 보안 비밀에 대한 액세스를 제어할 수 있습니다. Secret Manager 또는 HashiCorp Vault와 같은 외부 보안 비밀 저장소 공급업체를 사용할 수 있습니다.

이 튜토리얼에서는 GKE Autopilot 클러스터를 사용합니다. GKE Standard를 사용하여 단계를 수행하려면 GKE용 워크로드 아이덴티티 제휴를 수동으로 사용 설정해야 합니다.

GKE용 워크로드 아이덴티티 제휴를 사용하면 정적 서비스 계정 키 파일과 같은 보안 수준이 낮은 접근 방식을 사용하지 않고도 GKE 워크로드에서 Google Cloud API에 액세스할 수 있습니다. 이 튜토리얼에서는 예시로 Secret Manager를 사용하지만 동일한 단계를 따라 다른 Google Cloud API에 액세스할 수 있습니다. 자세한 내용은 GKE용 워크로드 아이덴티티 제휴를 참조하세요.

목표

  • Google Cloud Secret Manager에서 보안 비밀을 만듭니다.
  • GKE Autopilot 클러스터, Kubernetes 네임스페이스, Kubernetes 서비스 계정을 만듭니다.
  • 보안 비밀에 Kubernetes 서비스 계정에 대한 액세스 권한을 부여하는 IAM 허용 정책을 만듭니다.
  • 테스트 애플리케이션을 사용하여 서비스 계정 액세스를 확인합니다.
  • Secret Manager API를 사용하여 보안 비밀에 액세스하는 샘플 앱을 실행합니다.

비용

이 문서에서는 비용이 청구될 수 있는 다음과 같은 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_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에서 보안 비밀 만들기

  1. 다음 예시에서는 보안 비밀을 만들기 위해 사용할 데이터를 보여줍니다.

    key=my-api-key
  2. 샘플 데이터를 저장하기 위해 보안 비밀을 만듭니다.

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

    이 명령어는 다음을 수행합니다.

    • us-central1 Google Cloud 리전에서 샘플 키를 사용하여 새로운 Secret Manager 보안 비밀을 만듭니다.
    • 명령어를 실행한지 한 시간 후 만료되도록 보안 비밀을 설정합니다.

클러스터 및 Kubernetes 리소스 만들기

GKE 클러스터, Kubernetes 네임스페이스, Kubernetes 서비스 계정을 만듭니다. 보안 비밀에 대한 읽기 전용 액세스와 읽기-쓰기 액세스가 있는 2개의 네임스페이스를 만듭니다. 또한 각 네임스페이스에서 GKE용 워크로드 아이덴티티 제휴에 사용할 Kubernetes 서비스 계정을 만듭니다.

  1. GKE Autopilot 클러스터를 만듭니다.

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

    클러스터는 배포하는 데 약 5분 정도 걸릴 수 있습니다. Autopilot 클러스터에는 항상 GKE용 워크로드 아이덴티티 제휴가 사용 설정됩니다. 대신 GKE Standard 클러스터를 사용하려면 계속하기 전 GKE용 워크로드 아이덴티티 제휴를 수동으로 사용 설정해야 합니다.

  2. readonly-ns 네임스페이스 및 admin-ns 네임스페이스를 만듭니다.

    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 서비스 계정에 보안 비밀에 대한 읽기-쓰기 액세스를 부여합니다.

    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
    

보안 비밀 액세스 확인

각 네임스페이스에 테스트 포드를 배포하여 읽기 전용 및 읽기-쓰기 액세스를 확인합니다.

  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.
    
    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

    이 포드는 readonly-ns 네임스페이스에서 readonly-sa 서비스 계정을 사용합니다.

  2. 읽기-쓰기 포드 매니페스트를 검토합니다.

    # 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

    이 포드는 admin-ns 네임스페이스에서 admin-sa 서비스 계정을 사용합니다.

  3. 테스트 포드를 배포합니다.

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

    포드 실행이 시작하는 데 몇 분 정도 걸릴 수 있습니다. 진행률을 모니터링하려면 다음 명령어를 실행합니다.

    watch kubectl get pods -n readonly-ns
    

    포드 상태가 RUNNING으로 변경되면 Ctrl+C를 눌러 명령줄로 돌아갑니다.

읽기 전용 액세스 테스트

  1. readonly-test 포드에서 셸을 엽니다.

    kubectl exec -it readonly-test --namespace=readonly-ns -- /bin/bash
    
  2. 보안 비밀을 읽어봅니다.

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

    출력은 key=my-api-key입니다.

  3. 보안 비밀에 새 데이터를 기록해봅니다.

    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).
    

    읽기 전용 서비스 계정을 사용하는 포드는 보안 비밀을 읽을 수만 있고 새 데이터를 쓸 수 없습니다.

  4. 포드를 종료합니다.

    exit
    

읽기-쓰기 액세스 테스트

  1. admin-test 포드에서 셸을 엽니다.

    kubectl exec -it admin-test --namespace=admin-ns -- /bin/bash
    
  2. 보안 비밀을 읽어봅니다.

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

    출력은 key=my-api-key입니다.

  3. 보안 비밀에 새 데이터를 기록해봅니다.

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

    출력은 다음과 비슷합니다.

    Created version [2] of the secret [bq-readonly-key].
    
  4. 새 보안 비밀 버전을 읽습니다.

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

    출력은 my-second-api-key입니다.

  5. 포드를 종료합니다.

    exit
    

포드는 사용자가 포드 매니페스트에서 사용된 Kubernetes 서비스 계정에 부여한 액세스 수준만 받습니다. admin-ns 네임스페이스에서 admin-sa Kubernetes 계정을 사용하는 모든 포드는 새 버전의 보안 비밀을 작성할 수 있지만 readonly-sa Kubernetes 서비스 계정을 사용하는 readonly-ns 네임스페이스의 포드는 보안 비밀을 읽을 수만 있습니다.

코드에서 보안 비밀 액세스

이 섹션에서는 다음과 같은 작업을 수행하게 됩니다.

  1. 클라이언트 라이브러리를 사용하여 Secret Manager에서 보안 비밀을 읽는 샘플 애플리케이션을 배포합니다.

  2. 애플리케이션이 보안 비밀에 액세스할 수 있는지 확인합니다.

가능한 한 모든 경우에 Secret Manager API를 사용하여 애플리케이션 코드에서 Secret Manager 보안 비밀에 액세스합니다.

  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를 호출하여 보안 비밀 읽기를 시도합니다.

  2. 샘플 애플리케이션 포드 매니페스트를 검토합니다.

    # 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-ns 네임스페이스에서 readonly-sa 서비스 계정을 사용하는 포드를 만듭니다.
    • Google 이미지 레지스트리에서 샘플 애플리케이션을 가져옵니다. 이 애플리케이션은 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
    

    포드 작동을 시작하는 데 몇 분 정도 걸릴 수 있습니다. 포드에 클러스터에서 새 노드가 필요하면 GKE가 포드를 프로비저닝하는 동안 CrashLoopBackOff 유형 이벤트가 표시될 수 있습니다. 노드가 성공적으로 프로비저닝되면 충돌이 중지됩니다.

  5. 보안 비밀 액세스를 확인합니다.

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

    출력은 my-second-api-key입니다. 출력이 비어 있으면 포드가 아직 실행 중이 아닐 수 있습니다. 잠시 후 다시 시도해 보세요.

대체 방법

민감한 정보를 포드에 마운트해야 하는 경우 GKE용 Secret Manager 부가기능(미리보기)을 사용합니다. 이 부가기능은 GKE 클러스터에서 Kubernetes Secret Store CSI 드라이버의 Google Cloud Secret Manager 제공업체를 배포하고 관리합니다. 자세한 내용은 GKE에서 Secret Manager 부가기능 사용을 참조하세요.

보안 비밀을 마운트된 볼륨으로 제공할 때는 다음과 같은 위험이 있습니다.

  1. 마운트된 볼륨은 디렉터리 순회 공격에 취약합니다.
  2. 디버그 엔드포인트 열기와 같은 잘못된 구성으로 인해 환경 변수가 손상될 수 있습니다.

가능한 한 모든 경우에 Secret Manager API를 통해 보안 비밀에 프로그래매틱 방식으로 액세스하는 것이 좋습니다. 자세한 내용은 이 튜토리얼의 샘플 애플리케이션 도는 Secret Manager 클라이언트 라이브러리를 참조하세요.

삭제

이 튜토리얼에서 사용된 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 리소스가 포함된 프로젝트를 삭제하거나 프로젝트를 유지하고 개별 리소스를 삭제하세요.

개별 리소스 삭제

  1. 다음과 같이 클러스터를 삭제합니다.

    gcloud container clusters delete secret-cluster \
        --region=us-central1
    
  2. 선택사항: Secret Manager에서 보안 비밀을 삭제합니다.

    gcloud secrets delete bq-readonly-key
    

    이 단계를 수행하지 않으면 만들기 중 --ttl 플래그를 설정하여 보안 비밀이 자동으로 만료됩니다.

프로젝트 삭제

    Delete a Google Cloud project:

    gcloud projects delete PROJECT_ID

다음 단계