클라이언트 라이브러리를 사용하여 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에서 보안 비밀을 만듭니다.
  • 보안 비밀에 액세스하기 위해 IAM 서비스 계정을 만들고 구성합니다.
  • GKE Autopilot 클러스터, Kubernetes 네임스페이스, Kubernetes ServiceAccounts를 만듭니다.
  • 테스트 애플리케이션을 사용하여 서비스 계정 액세스를 확인합니다.
  • Secret Manager API를 사용하여 보안 비밀에 액세스하는 샘플 앱을 실행합니다.

비용

이 문서에서는 비용이 청구될 수 있는 다음과 같은 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.com secretmanager.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.com secretmanager.googleapis.com
  12. Google 계정에 역할을 부여합니다. 다음 각 IAM 역할에 대해 다음 명령어를 한 번씩 실행합니다. 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에서 보안 비밀 만들기

  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 보안 비밀을 만듭니다.
    • 명령어를 실행한지 한 시간 후 만료되도록 보안 비밀을 설정합니다.

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

    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 네임스페이스, 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
    
  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용 워크로드 아이덴티티 제휴를 사용하여 포드에서 보안 비밀에 액세스하도록 클러스터가 구성되었습니다.

보안 비밀 액세스 확인

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

  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 서비스 계정에 바인딩된 IAM 서비스 계정에 부여한 액세스 수준만 받습니다. 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. 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에서 보안 비밀을 삭제합니다.

    gcloud secrets delete bq-readonly-key
    

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

프로젝트 삭제

    Google Cloud 프로젝트를 삭제합니다.

    gcloud projects delete PROJECT_ID

다음 단계