첫 번째 컨피덴셜 스페이스 환경 만들기


이 가이드에서 알렉스와 볼라는 서로 숫자를 공개하지 않으면서 최고 급여자를 찾아내려고 합니다. 이들은 자신의 데이터를 기밀로 유지하기 위해 Confidential Space를 사용하기로 결정하고 다음 역할을 각자 수행하기로 협의합니다.

  • 알렉스: 데이터 공동작업자, 워크로드 작성자

  • 볼라: 데이터 공동작업자, 워크로드 운영자

이 구성은 이 가이드에서 최대한 간단하게 설명하기 위해 설계되었습니다. 하지만 워크로드 작성자와 운영자가 데이터 공동작업자와 완전히 독립적일 수 있고, 원하는 만큼 공동작업자를 지정할 수 있습니다.

시작하기 전에

이 가이드에서는 전체 프로세스를 경험할 수 있도록 여러 프로젝트에 액세스할 수 있는 단일 조직의 단일 계정을 사용하는 Confidential Space 시나리오를 보여줍니다. 프로덕션 배포에서 공동작업자, 워크로드 작성자, 워크로드 운영자는 각자 고유한 계정을 가지며, 각자 자신의 프로젝트가 개별 조직에 포함되어 있으므로, 서로 액세스할 수 없고, 각자의 기밀 데이터가 별도로 유지됩니다.

Confidential Space는 많은 Google Cloud서비스와 상호작용을 통해 결과를 생성하지만 다음을 포함하되 이에 국한되지 않습니다.

이 가이드에서는 이 모든 기능을 기본적으로 이해하고 있다고 가정합니다.

필수 API

이 가이드를 완료하려면 지정된 프로젝트에서 다음 API를 사용 설정해야 합니다.

API 이름 API 제목 이 프로젝트에서 사용 설정
cloudkms.googleapis.com Cloud KMS 데이터 공동작업자(알렉스 및 볼라의 프로젝트)
iamcredentials.googleapis.com IAM Service Account Credentials API

데이터 공동작업자(알렉스 및 볼라의 프로젝트)

artifactregistry.googleapis.com Artifact Registry 워크로드 작성자(알렉스 프로젝트)
compute.googleapis.com Compute Engine 워크로드 연산자(볼라 프로젝트)
confidentialcomputing.googleapis.com 컨피덴셜 컴퓨팅 워크로드 연산자(볼라 프로젝트)

필요한 역할

이 가이드를 완료하는 데 필요한 권한을 얻으려면 관리자에게 프로젝트에 대한 다음 IAM 역할을 부여해 달라고 요청하세요.

  • 데이터 공동작업자(알렉스 및 볼라)에 대한 Cloud KMS 관리자(roles/cloudkms.admin)
  • 데이터 공동작업자(알렉스 및 볼라)에 대한 IAM 워크로드 아이덴티티 풀 관리자(roles/iam.workloadIdentityPoolAdmin)
  • 데이터 공동작업자(알렉스 및 볼라)에 대한 서비스 사용량 관리자(roles/serviceusage.serviceUsageAdmin)
  • 데이터 공동작업자(알렉스 및 볼라) 및 워크로드 운영자(볼라)에 대한 스토리지 관리자(roles/storage.admin)
  • 워크로드 운영자 (볼라)에 대한 서비스 계정 관리자 (roles/iam.serviceAccountAdmin)
  • 워크로드 운영자(볼라)에 대한 컴퓨팅 관리자(roles/compute.admin)
  • 워크로드 운영자(볼라)에 대한 보안 관리자(roles/securityAdmin)
  • 워크로드 작성자(알렉스)에 대한 Artifact Registry 관리자(roles/artifactregistry.admin)

역할 부여에 대한 자세한 내용은 프로젝트, 폴더, 조직에 대한 액세스 관리를 참조하세요.

커스텀 역할이나 다른 사전 정의된 역할을 통해 필요한 권한을 얻을 수도 있습니다.

데이터 공동작업자 리소스 설정

알렉스와 볼라 모두 다음 리소스가 포함된 독립 실행형 프로젝트가 필요합니다.

  • 기밀 데이터 자체

  • 데이터를 암호화하고 기밀로 유지하기 위한 암호화 키

  • 암호화된 데이터를 저장할 Cloud Storage 버킷

  • 워크로드 아이덴티티 풀입니다. 기밀 데이터를 처리하는 워크로드는 풀을 사용하여 비공개 데이터에 액세스하고 이를 복호화합니다.

시작하려면 Google Cloud 콘솔로 이동하세요.

Google Cloud 콘솔로 이동

알렉스의 리소스 설정

알렉스를 위한 리소스를 설정하려면 다음 안내를 완료하세요.

  1. Cloud Shell 활성화를 클릭합니다.
  2. Cloud Shell에서 다음 명령어를 입력하여 알렉스를 위한 프로젝트를 만듭니다. ALEX_PROJECT_ID는 선택한 이름으로 바꿉니다.

    gcloud projects create ALEX_PROJECT_ID
  3. 새로 만든 프로젝트로 전환합니다.

    gcloud config set project ALEX_PROJECT_ID
  4. 아직 실행하지 않았으면 데이터 공동작업자 및 워크로드 작성자로서 알렉스에게 필요한 API를 사용 설정합니다.

    gcloud services enable cloudkms.googleapis.com artifactregistry.googleapis.com iamcredentials.googleapis.com
  5. Cloud Key Management Service로 키링과 암호화 키를 만듭니다.

    gcloud kms keyrings create ALEX_KEYRING_NAME \
        --location=global
    gcloud kms keys create ALEX_KEY_NAME \
        --location=global \
        --keyring=ALEX_KEYRING_NAME \
        --purpose=encryption
  6. 새로 만든 암호화 키를 사용하여 데이터를 암호화할 수 있도록 알렉스에게 cloudkms.cryptoKeyEncrypter 역할을 부여합니다.

    gcloud kms keys add-iam-policy-binding \
        projects/ALEX_PROJECT_ID/locations/global/keyRings/ALEX_KEYRING_NAME/cryptoKeys/ALEX_KEY_NAME \
        --member=user:$(gcloud config get-value account) \
        --role=roles/cloudkms.cryptoKeyEncrypter
  7. 알렉스의 워크로드 아이덴티티 풀을 만듭니다.

    gcloud iam workload-identity-pools create ALEX_POOL_NAME \
        --location=global
  8. 입력 데이터에 대해 Cloud Storage 버킷을 하나 만들고 결과를 저장할 버킷을 하나 더 만듭니다.

    gcloud storage buckets create gs://ALEX_INPUT_BUCKET_NAME \
        gs://ALEX_RESULTS_BUCKET_NAME
  9. 알렉스 급여만 숫자로 포함하는 파일을 만듭니다.

    echo 123456 > ALEX_SALARY.txt
  10. 파일을 암호화하고 이를 알렉스의 버킷으로 업로드합니다.

    gcloud kms encrypt \
        --ciphertext-file="ALEX_ENCRYPTED_SALARY_FILE" \
        --plaintext-file="ALEX_SALARY.txt" \
        --key=projects/ALEX_PROJECT_ID/locations/global/keyRings/ALEX_KEYRING_NAME/cryptoKeys/ALEX_KEY_NAME
    gcloud storage cp ALEX_ENCRYPTED_SALARY_FILE gs://ALEX_INPUT_BUCKET_NAME

볼라의 리소스 설정

볼라를 위한 리소스를 설정하려면 다음 안내를 완료하세요.

  1. Cloud Shell에서 다음 명령어를 입력하여 볼라를 위한 프로젝트를 만듭니다. BOLA_PROJECT_ID는 선택한 이름으로 바꿉니다.

    gcloud projects create BOLA_PROJECT_ID
  2. 새로 만든 프로젝트로 전환합니다.

    gcloud config set project BOLA_PROJECT_ID
  3. 아직 실행하지 않았으면 데이터 공동작업자 및 워크로드 운영자로서 볼라에게 필요한 API를 사용 설정합니다.

    gcloud services enable cloudkms.googleapis.com iamcredentials.googleapis.com compute.googleapis.com confidentialcomputing.googleapis.com
  4. Cloud Key Management Service로 키링과 암호화 키를 만듭니다.

    gcloud kms keyrings create BOLA_KEYRING_NAME \
        --location=global
    gcloud kms keys create BOLA_KEY_NAME \
        --location=global \
        --keyring=BOLA_KEYRING_NAME \
        --purpose=encryption
  5. 새로 생성된 암호화 키를 사용하여 데이터를 암호화할 수 있도록 볼라에게 cloudkms.cryptoKeyEncrypter 역할을 부여합니다.

    gcloud kms keys add-iam-policy-binding \
        projects/BOLA_PROJECT_ID/locations/global/keyRings/BOLA_KEYRING_NAME/cryptoKeys/BOLA_KEY_NAME \
        --member=user:$(gcloud config get-value account) \
        --role=roles/cloudkms.cryptoKeyEncrypter
  6. 볼라의 워크로드 아이덴티티 풀을 만듭니다.

    gcloud iam workload-identity-pools create BOLA_POOL_NAME \
        --location=global
  7. 입력 데이터에 대해 Cloud Storage 버킷을 하나 만들고 결과를 저장할 버킷을 하나 더 만듭니다.

    gcloud storage buckets create gs://BOLA_INPUT_BUCKET_NAME \
        gs://BOLA_RESULTS_BUCKET_NAME
  8. 볼라 급여만 숫자로 포함하는 파일을 만듭니다.

    echo 111111 > BOLA_SALARY.txt
  9. 파일을 암호화하고 이를 볼라의 버킷으로 업로드합니다.

    gcloud kms encrypt \
        --ciphertext-file="BOLA_ENCRYPTED_SALARY_FILE" \
        --plaintext-file="BOLA_SALARY.txt" \
        --key=projects/BOLA_PROJECT_ID/locations/global/keyRings/BOLA_KEYRING_NAME/cryptoKeys/BOLA_KEY_NAME
    gcloud storage cp BOLA_ENCRYPTED_SALARY_FILE gs://BOLA_INPUT_BUCKET_NAME

워크로드에 대한 서비스 계정 만들기

이 가이드에서는 볼라가 워크로드를 운영하고 실행하지만 제3자를 포함하여 누구나 이 역할을 가질 수 있습니다. 작업 부하를 실행하기 위해 볼라가 만드는 VM 인스턴스에는 증명 토큰을 생성하고, 로그를 쓰고, 알렉스와 볼라의 암호화된 데이터를 읽고, 특정 Cloud Storage 버킷에 결과를 쓸 수 있는 권한이 있는 서비스 계정이 연결되어 있습니다.

서비스 계정을 설정하기 위해 볼라의 프로젝트에서 다음 단계를 수행합니다.

  1. 워크로드 실행을 위해 서비스 계정을 만듭니다.

    gcloud iam service-accounts create WORKLOAD_SERVICE_ACCOUNT_NAME
    
  2. Bola에게 iam.serviceAccountUser 역할을 부여하여 나중에 워크로드 VM에 서비스 계정을 연결할 수 있도록 합니다.

    gcloud iam service-accounts add-iam-policy-binding \
        WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --member=user:$(gcloud config get-value account) \
        --role=roles/iam.serviceAccountUser
    
  3. 서비스 계정에 증명 토큰을 생성할 수 있도록 confidentialcomputing.workloadUser 역할을 부여합니다.

    gcloud projects add-iam-policy-binding BOLA_PROJECT_ID \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/confidentialcomputing.workloadUser
    
  4. 워크로드 진행 상황을 확인할 수 있도록 Cloud Logging에 로그를 기록하기 위해 서비스 계정에 logging.logWriter 역할을 부여합니다.

    gcloud projects add-iam-policy-binding BOLA_PROJECT_ID \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/logging.logWriter
    
  5. 암호화된 데이터가 포함된 알렉스 및 볼라의 버킷에 모두 서비스 계정 읽기 액세스 권한을 부여하고 각 결과 버킷에 쓰기 액세스 권한을 부여합니다.

    gcloud storage buckets add-iam-policy-binding gs://ALEX_INPUT_BUCKET_NAME \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/storage.objectViewer
    
    gcloud storage buckets add-iam-policy-binding gs://BOLA_INPUT_BUCKET_NAME \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/storage.objectViewer
    
    gcloud storage buckets add-iam-policy-binding gs://ALEX_RESULTS_BUCKET_NAME \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/storage.objectAdmin
    
    gcloud storage buckets add-iam-policy-binding gs://BOLA_RESULTS_BUCKET_NAME \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/storage.objectAdmin
    

    여기에서는 액세스 권한을 부여하는 사용자에게 현재 작업 중인 Cloud Storage 버킷을 포함하는 프로젝트에 대한 스토리지 관리자(roles/storage.admin) 역할이 있다고 가정합니다.

워크로드 만들기

이 가이드에서 알렉스가 워크로드에 대한 코드를 제공하고 이를 포함하도록 Docker 이미지를 빌드하지만, 제3자를 포함하여 누구나 이 역할을 가질 수 있습니다.

알렉스가 워크로드에 대해 다음 리소스를 만들어야 합니다.

  • 워크로드를 수행하는 코드

  • 워크로드를 실행하는 서비스 계정이 액세스할 수 있는 Artifact Registry의 Docker 저장소

  • 워크로드 코드를 포함하고 실행하는 Docker 이미지

리소스를 만들고 설정하려면 알렉스의 프로젝트에서 다음 단계를 수행합니다.

  1. 알렉스의 프로젝트로 전환합니다.

    gcloud config set project ALEX_PROJECT_ID
    
  2. Artifact Registry에서 Docker 저장소를 만듭니다.

    gcloud artifacts repositories create REPOSITORY_NAME \
        --repository-format=docker \
        --location=us
    
  3. 저장소에서 읽기를 수행할 수 있도록 워크로드를 실행하려는 서비스 계정에 Artifact Registry 리더(roles/artifactregistry.reader) 역할을 부여합니다.

    gcloud artifacts repositories add-iam-policy-binding REPOSITORY_NAME \
        --location=us \
        --member=serviceAccount:WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/artifactregistry.reader
    
  4. 편집기 열기를 클릭하여 Cloud Shell 편집기를 연 후 salary.go라는 새 파일을 만듭니다. 파일에 다음 코드를 복사하고 저장합니다.

    // READ ME FIRST: Before compiling, customize the details in the USER VARIABLES
    // SECTION starting at line 30.
    
    package main
    
    import (
      "context"
      "fmt"
      "io"
      "strconv"
      "strings"
      "time"
    
      kms "cloud.google.com/go/kms/apiv1"
      kmspb "cloud.google.com/go/kms/apiv1/kmspb"
      "cloud.google.com/go/storage"
      "google.golang.org/api/option"
    )
    
    type collaborator struct {
      name         string
      wipName      string
      keyName      string
      inputBucket  string
      inputFile    string
      outputBucket string
      outputFile   string
    }
    
    // ============================
    // START USER VARIABLES SECTION
    // You need to customize this section, replacing each const's value with your
    // own.
    
    // To get a project number, use the following command, and substitute
    // <PROJECT_ID> for the data collaborator's project ID.
    // gcloud projects describe <PROJECT_ID> --format="value(projectNumber)"
    
    // Alex's values
    const collaborator1Name string = "Alex"                // Alex's name
    const collaborator1EncryptedSalaryFileName string = "" // The name of Alex's encrypted salary file
    const collaborator1BucketInputName string = ""         // The name of the storage bucket that contains Alex's encrypted salary file
    const collaborator1BucketOutputName string = ""        // The name of the storage bucket to store Alex's results in
    const collaborator1BucketOutputFileName string = ""    // The name of Alex's output file that contains the results
    const collaborator1KMSKeyringName string = ""          // Alex's Key Management Service key ring
    const collaborator1KMSKeyName string = ""              // Alex's Key Management Service key
    const collaborator1ProjectName string = ""             // Alex's project ID
    const collaborator1ProjectNumber string = ""           // Alex's project number
    const collaborator1PoolName string = ""                // Alex's workload identity pool name
    
    // Bola's values
    const collaborator2Name string = "Bola"                // Bola's name
    const collaborator2EncryptedSalaryFileName string = "" // The name of Bola's encrypted salary file
    const collaborator2BucketInputName string = ""         // The name of the storage bucket that contains Bola's encrypted salary file
    const collaborator2BucketOutputName string = ""        // The name of the storage bucket to store Bola's results in
    const collaborator2BucketOutputFileName string = ""    // The name of Bola's output file that contains the results
    const collaborator2KMSKeyringName string = ""          // Bola's Key Management Service key ring
    const collaborator2KMSKeyName string = ""              // Bola's Key Management Service key
    const collaborator2ProjectName string = ""             // Bola's project ID
    const collaborator2ProjectNumber string = ""           // Bola's project number
    const collaborator2PoolName string = ""                // Bola's workload identity pool name
    
    // END USER VARIABLES SECTION
    // ==========================
    
    var collaborators = [2]collaborator{
      {
        collaborator1Name,
        "projects/" + collaborator1ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator1PoolName + "/providers/attestation-verifier",
        "projects/" + collaborator1ProjectName + "/locations/global/keyRings/" + collaborator1KMSKeyringName + "/cryptoKeys/" + collaborator1KMSKeyName,
        collaborator1BucketInputName,
        collaborator1EncryptedSalaryFileName,
        collaborator1BucketOutputName,
        collaborator1BucketOutputFileName,
      },
      {
        collaborator2Name,
        "projects/" + collaborator2ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator2PoolName + "/providers/attestation-verifier",
        "projects/" + collaborator2ProjectName + "/locations/global/keyRings/" + collaborator2KMSKeyringName + "/cryptoKeys/" + collaborator2KMSKeyName,
        collaborator2BucketInputName,
        collaborator2EncryptedSalaryFileName,
        collaborator2BucketOutputName,
        collaborator2BucketOutputFileName,
      },
    }
    
    const credentialConfig = `{
            "type": "external_account",
            "audience": "//iam.googleapis.com/%s",
            "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
            "token_url": "https://sts.googleapis.com/v1/token",
            "credential_source": {
              "file": "/run/container_launcher/attestation_verifier_claims_token"
            }
            }`
    
    func main() {
      fmt.Println("workload started")
      ctx := context.Background()
    
      storageClient, err := storage.NewClient(ctx) // using the default credential on the Compute Engine VM
      if err != nil {
        panic(err)
      }
    
      // get and decrypt
      s0, err := getSalary(ctx, storageClient, collaborators[0])
      if err != nil {
        panic(err)
      }
    
      s1, err := getSalary(ctx, storageClient, collaborators[1])
      if err != nil {
        panic(err)
      }
    
      res := ""
      if s0 > s1 {
        res = fmt.Sprintf("%s earns more!\n", collaborators[0].name)
      } else if s1 < s0 {
        res = fmt.Sprintf("%s earns more!\n", collaborators[1].name)
      } else {
        res = "earns same\n"
      }
    
      now := time.Now()
      for _, cw := range collaborators {
        outputWriter := storageClient.Bucket(cw.outputBucket).Object(fmt.Sprintf("%s-%d", cw.outputFile, now.Unix())).NewWriter(ctx)
    
        _, err = outputWriter.Write([]byte(res))
        if err != nil {
          fmt.Printf("Could not write: %v", err)
          panic(err)
        }
        if err = outputWriter.Close(); err != nil {
          fmt.Printf("Could not close: %v", err)
          panic(err)
        }
      }
    }
    
    func getSalary(ctx context.Context, storageClient *storage.Client, cw collaborator) (float64, error) {
      encryptedBytes, err := getFile(ctx, storageClient, cw.inputBucket, cw.inputFile)
      if err != nil {
        return 0.0, err
      }
      decryptedByte, err := decryptByte(ctx, cw.keyName, cw.wipName, encryptedBytes)
      if err != nil {
        return 0.0, err
      }
      decryptedNumber := strings.TrimSpace(string(decryptedByte))
      num, err := strconv.ParseFloat(decryptedNumber, 64)
      if err != nil {
        return 0.0, err
      }
      return num, nil
    }
    
    func decryptByte(ctx context.Context, keyName, wippro string, encryptedData []byte) ([]byte, error) {
      cc := fmt.Sprintf(credentialConfig, wippro)
      kmsClient, err := kms.NewKeyManagementClient(ctx, option.WithCredentialsJSON([]byte(cc)))
      if err != nil {
        return nil, fmt.Errorf("creating a new KMS client with federated credentials: %w", err)
      }
    
      decryptRequest := &kmspb.DecryptRequest{
        Name:       keyName,
        Ciphertext: encryptedData,
      }
      decryptResponse, err := kmsClient.Decrypt(ctx, decryptRequest)
      if err != nil {
        return nil, fmt.Errorf("could not decrypt ciphertext: %w", err)
      }
    
      return decryptResponse.Plaintext, nil
    }
    
    func getFile(ctx context.Context, c *storage.Client, bucketName string, objPath string) ([]byte, error) {
      bucketHandle := c.Bucket(bucketName)
      objectHandle := bucketHandle.Object(objPath)
    
      objectReader, err := objectHandle.NewReader(ctx)
      if err != nil {
        return nil, err
      }
      defer objectReader.Close()
    
      s, err := io.ReadAll(objectReader)
      if err != nil {
        return nil, err
      }
    
      return s, nil
    }
    
  5. 소스 코드의 USER VARIABLES SECTION을 수정하여 빈 const 값을 코드 주석에 설명된 관련 리소스 이름으로 바꿉니다. 이 가이드에 표시된 ALEX_PROJECT_ID와 같은 자리표시자 변수를 수정한 경우 다음 코드 샘플에 값이 포함되어 있으며, 이 코드를 복사하여 기존 코드에 붙여넣을 수 있습니다.

    // Alex's values
    const collaborator1Name string = "Alex"                                            // Alex's name
    const collaborator1EncryptedSalaryFileName string = "ALEX_ENCRYPTED_SALARY_FILE" // The name of Alex's encrypted salary file
    const collaborator1BucketInputName string = "ALEX_INPUT_BUCKET_NAME"             // The name of the storage bucket that contains Alex's encrypted salary file
    const collaborator1BucketOutputName string = "ALEX_RESULTS_BUCKET_NAME"          // The name of the storage bucket to store Alex's results in
    const collaborator1BucketOutputFileName string = "ALEX_RESULTS_FILE_NAME"        // The name of Alex's output file that contains the results
    const collaborator1KMSKeyringName string = "ALEX_KEYRING_NAME"                   // Alex's Key Management Service key ring
    const collaborator1KMSKeyName string = "ALEX_KEY_NAME"                           // Alex's Key Management Service key
    const collaborator1ProjectName string = "ALEX_PROJECT_ID"                        // Alex's project ID
    const collaborator1ProjectNumber string = "ALEX_PROJECT_NUMBER"                  // Alex's project number
    const collaborator1PoolName string = "ALEX_POOL_NAME"                            // Alex's workload identity pool name
    
    // Bola's values
    const collaborator2Name string = "Bola"                                            // Bola's name
    const collaborator2EncryptedSalaryFileName string = "BOLA_ENCRYPTED_SALARY_FILE" // The name of Bola's encrypted salary file
    const collaborator2BucketInputName string = "BOLA_INPUT_BUCKET_NAME"             // The name of the storage bucket that contains Bola's encrypted salary file
    const collaborator2BucketOutputName string = "BOLA_RESULTS_BUCKET_NAME"          // The name of the storage bucket to store Bola's results in
    const collaborator2BucketOutputFileName string = "BOLA_RESULTS_FILE_NAME"        // The name of Bola's output file that contains the results
    const collaborator2KMSKeyringName string = "BOLA_KEYRING_NAME"                   // Bola's Key Management Service key ring
    const collaborator2KMSKeyName string = "BOLA_KEY_NAME"                           // Bola's Key Management Service key
    const collaborator2ProjectName string = "BOLA_PROJECT_ID"                        // Bola's project ID
    const collaborator2ProjectNumber string = "BOLA_PROJECT_NUMBER"                  // Bola's project number
    const collaborator2PoolName string = "BOLA_POOL_NAME"                            // Bola's workload identity pool name
    

    알렉스와 볼라의 프로젝트 번호도 업데이트해야 합니다. 다음 명령어를 사용하여 검색할 수 있습니다.

    gcloud projects describe PROJECT_ID --format="value(projectNumber)"
    
  6. 모든 당사자가 소스 코드를 읽고 감사해야 합니다.

  7. 터미널 > 새 터미널을 클릭하여 Cloud Shell 편집기 내에서 터미널을 엽니다.

  8. 터미널에서 다음 명령어를 입력하여 Go 환경을 설정합니다.

    go mod init salary
    go get cloud.google.com/go/kms/apiv1 cloud.google.com/go/storage google.golang.org/api/option google.golang.org/genproto/googleapis/cloud/kms/v1
    
  9. 다음 명령어를 입력하여 소스 코드를 정적으로 연결된 바이너리로 컴파일합니다.

    CGO_ENABLED=0 go build -trimpath
    
  10. Cloud Shell 편집기에서 다음 콘텐츠가 포함된 Dockerfile이라는 파일을 만듭니다.

    FROM alpine:latest
    WORKDIR /test
    COPY salary /test
    ENTRYPOINT ["/test/salary"]
    CMD []
    
  11. us-docker.pkg.dev 도메인 이름을 포함하도록 Docker 사용자 인증 정보를 업로드합니다.

    gcloud auth configure-docker us-docker.pkg.dev
    
  12. 터미널에서 다음 명령어를 입력하여 Dockerfile로부터 Docker 이미지를 만듭니다.

    docker build -t \
        us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME:latest .
    
  13. Artifact Registry에 Docker 이미지를 푸시합니다.

    docker push \
        us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME
    
  14. Docker 푸시 응답에서 Docker 이미지의 다이제스트 (sha256: 접두사 포함)를 안전한 곳에 복사합니다. 나중에 워크로드를 승인하는 데 사용됩니다.

  15. 사용을 승인하기 전에 모든 당사자가 Docker 이미지를 감사하고 신뢰성을 확인하도록 해야 합니다.

워크로드 승인

양 당사자가 워크로드를 승인함에 따라 알렉스와 볼라는 Google Cloud 증명을 제공자로서 워크로드 아이덴티티 풀에 추가해야 합니다. 공급자는 사용할 증명 서비스와 워크로드가 Alex 또는 Bola의 데이터에 대해 작동하도록 허용되기 위해 일치해야 하는 속성을 지정합니다. 악의적인 행위자가 Docker 이미지를 변경하거나 다른 측정된 속성을 변경하면 워크로드의 액세스가 거부됩니다.

이 가이드에서는 속성 매핑을 사용하여 이미지 다이제스트를 기반으로 워크로드에 직접 리소스 액세스 권한을 제공합니다. 하지만 다른 상황에서는 서비스 계정 가장을 사용하여 리소스에 액세스하는 것이 좋습니다. 자세한 내용은 외부 워크로드 액세스를 참고하세요.

필요한 조건을 사용해서 알렉스와 볼라에 대해 공급자를 설정하려면 다음 단계를 완료합니다.

  1. 다음 명령어를 입력하여 알렉스에 대한 공급자를 만듭니다.

    gcloud iam workload-identity-pools providers create-oidc attestation-verifier \
        --location=global \
        --workload-identity-pool=ALEX_POOL_NAME \
        --issuer-uri="https://confidentialcomputing.googleapis.com/" \
        --allowed-audiences="https://sts.googleapis.com" \
        --attribute-mapping="google.subject=\"gcpcs::\"+assertion.submods.container.image_digest+\"::\"+assertion.submods.gce.project_number+\"::\"+assertion.submods.gce.instance_id,attribute.image_digest=assertion.submods.container.image_digest" \
        --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE' \
            && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    
  2. 알렉스 공급업체에서 정의한 제휴 ID에 cloudkms.cryptoKeyDecrypter 역할을 부여하여 지정된 다이제스트가 있는 워크로드 컨테이너만 KMS 키를 복호화할 수 있도록 image_digest 속성을 지정합니다.

    gcloud kms keys add-iam-policy-binding \
        projects/ALEX_PROJECT_ID/locations/global/keyRings/ALEX_KEYRING_NAME/cryptoKeys/ALEX_KEY_NAME \
        --member="principalSet://iam.googleapis.com/projects/ALEX_PROJECT_NUMBER/locations/global/workloadIdentityPools/ALEX_POOL_NAME/attribute.image_digest/WORKLOAD_CONTAINER_IMAGE_DIGEST" \
        --role=roles/cloudkms.cryptoKeyDecrypter
    
  3. 볼라의 프로젝트로 전환합니다.

    gcloud config set project BOLA_PROJECT_ID
    
  4. 다음 명령어를 입력하여 볼라에 대해 공급자를 만듭니다.

    gcloud iam workload-identity-pools providers create-oidc attestation-verifier \
        --location=global \
        --workload-identity-pool=BOLA_POOL_NAME \
        --issuer-uri="https://confidentialcomputing.googleapis.com/" \
        --allowed-audiences="https://sts.googleapis.com" \
        --attribute-mapping="google.subject=\"gcpcs::\"+assertion.submods.container.image_digest+\"::\"+assertion.submods.gce.project_number+\"::\"+assertion.submods.gce.instance_id,attribute.image_digest=assertion.submods.container.image_digest" \
        --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE' \
            && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    
  5. 볼라의 공급업체에서 정의한 제휴 ID에 cloudkms.cryptoKeyDecrypter 역할을 부여하여 지정된 다이제스트가 있는 워크로드 컨테이너만 KMS 키를 복호화할 수 있도록 image_digest 속성을 지정합니다.

    gcloud kms keys add-iam-policy-binding \
        projects/BOLA_PROJECT_ID/locations/global/keyRings/BOLA_KEYRING_NAME/cryptoKeys/BOLA_KEY_NAME \
        --member="principalSet://iam.googleapis.com/projects/BOLA_PROJECT_NUMBER/locations/global/workloadIdentityPools/BOLA_POOL_NAME/attribute.image_digest/WORKLOAD_CONTAINER_IMAGE_DIGEST" \
        --role=roles/cloudkms.cryptoKeyDecrypter
    

워크로드 배포

알렉스와 볼라의 워크로드 아이덴티티 풀에 공급자가 추가되었고 필요한 리소스가 배치되었으면 워크로드 운영자가 워크로드를 실행할 시간입니다.

워크로드를 배포하기 위해 볼라의 프로젝트에서 다음 속성이 포함된 새 컨피덴셜 VM 인스턴스를 만듭니다.

  • AMD SEV, Intel TDX 또는 NVIDIA 컨피덴셜 컴퓨팅이 적용된 Intel TDX (미리보기)를 사용하는 컨피덴셜 VM 인스턴스의 지원되는 구성

  • Confidential Space 이미지 기반의 OS

  • 사용 설정된 보안 부팅

  • 알렉스가 이전에 만든 연결된 Docker 이미지

  • 워크로드를 실행하는 연결된 서비스 계정

볼라의 Cloud Shell에서 다음 명령어를 입력하여 워크로드를 배포합니다.

gcloud compute instances create WORKLOAD_VM_NAME \
    --confidential-compute-type=SEV \
    --shielded-secure-boot \
    --scopes=cloud-platform \
    --zone=us-west1-b \
    --maintenance-policy=MIGRATE \
    --image-project=confidential-space-images \
    --image-family=confidential-space \
    --service-account=WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
    --metadata="^~^tee-image-reference=us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME:latest"

로그 탐색기로 이동하여 볼라의 프로젝트에서 워크로드 진행 상황을 확인할 수 있습니다.

로그 탐색기로 이동

Confidential Space 로그를 찾으려면 사용 가능한 경우 다음 로그 필드를 기준으로 필터링합니다.

  • 리소스 유형: VM 인스턴스

  • 인스턴스 ID: VM의 인스턴스 ID

  • 로그 이름: confidential-space-launcher

로그를 새로고침하려면 현재 시점으로 이동을 클릭합니다.

워크로드가 완료되면 VM 인스턴스가 중지됩니다. 암호화된 급여 파일을 변경하고 워크로드를 다시 배포하려면 기존 VM만 시작하면 됩니다.

gcloud compute instances start WORKLOAD_VM_NAME --zone=us-west1-b

결과 보기

워크로드가 성공적으로 완료되면 알렉스와 볼라가 모두 자신의 결과 버킷에서 결과를 볼 수 있습니다.

  1. 알렉스의 프로젝트로 전환합니다.

    gcloud config set project ALEX_PROJECT_ID
    
  2. 결과 버킷의 모든 파일을 나열합니다.

    gcloud storage ls gs://ALEX_RESULTS_BUCKET_NAME
    

    그런 후 최신 파일을 읽습니다.

    gcloud storage cat gs://ALEX_RESULTS_BUCKET_NAME/ALEX_RESULTS_FILE_NAME
    
  3. 볼라의 프로젝트로 전환합니다.

    gcloud config set project BOLA_PROJECT_ID
    
  4. 볼라의 경우 해당 결과 버킷의 모든 파일을 나열합니다.

    gcloud storage ls gs://BOLA_RESULTS_BUCKET_NAME
    

    그런 후 최신 파일을 읽습니다.

    gcloud storage cat gs://BOLA_RESULTS_BUCKET_NAME/BOLA_RESULTS_FILE_NAME
    

이 파일을 읽으면 알렉스와 볼라 모두 자신의 급여를 상대방에게 공개하지 않으면서도 급여가 더 높은 사람이 누구인지 확인할 수 있습니다.

워크로드 디버그

또한 로그 탐색기를 사용하여 리소스가 올바르게 설정되지 않는 문제 또는 공급자의 속성 조건이 Confidential Space 워크로드로 수행된 클레임과 일치하지 않는 문제를 해결할 수 있습니다.

이렇게 하려면 다음 변경사항을 적용해야 합니다.

  • 알렉스 및 볼라의 워크로드 아이덴티티 풀 공급업체를 업데이트하여 support_attributes 어설션을 삭제합니다. 더 심층적인 문제 해결을 수행하려면 Confidential Space 디버그 이미지를 사용해야 하며, 해당 이미지에는 확인할 지원 속성이 없습니다.

  • Confidential Space 디버그 이미지를 사용하여 워크로드 VM을 만들고 STDOUTSTDERR을 Cloud Logging으로 리디렉션하여 워크로드의 모든 출력을 캡처하도록 VM 메타데이터를 설정합니다.

변경하려면 다음 단계를 완료하세요.

  1. 알렉스의 프로젝트로 전환합니다.

    gcloud config set project ALEX_PROJECT_ID
    
  2. 알렉스의 제공업체를 업데이트하여 support_attributes 어설션을 삭제합니다.

    gcloud iam workload-identity-pools providers update-oidc attestation-verifier \
        --location=global \
        --workload-identity-pool=ALEX_POOL_NAME \
        --issuer-uri="https://confidentialcomputing.googleapis.com/" \
        --allowed-audiences="https://sts.googleapis.com" \
        --attribute-mapping="google.subject=\"gcpcs::\"+assertion.submods.container.image_digest+\"::\"+assertion.submods.gce.project_number+\"::\"+assertion.submods.gce.instance_id,attribute.image_digest=assertion.submods.container.image_digest" \
        --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE'"
    
  3. 볼라의 프로젝트로 전환합니다.

    gcloud config set project BOLA_PROJECT_ID
    
  4. 볼라의 제공업체를 업데이트하여 support_attributes 어설션을 삭제합니다.

    gcloud iam workload-identity-pools providers update-oidc attestation-verifier \
        --location=global \
        --workload-identity-pool=BOLA_POOL_NAME \
        --issuer-uri="https://confidentialcomputing.googleapis.com/" \
        --allowed-audiences="https://sts.googleapis.com" \
        --attribute-mapping="google.subject=\"gcpcs::\"+assertion.submods.container.image_digest+\"::\"+assertion.submods.gce.project_number+\"::\"+assertion.submods.gce.instance_id,attribute.image_digest=assertion.submods.container.image_digest" \
        --attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE'"
    
  5. Confidential Space 디버그 이미지로 새 VM을 만들고 메타데이터에서 tee-container-log-redirecttrue로 설정합니다.

    gcloud compute instances create WORKLOAD_VM_2_NAME \
        --confidential-compute-type=SEV \
        --shielded-secure-boot \
        --scopes=cloud-platform \
        --zone=us-west1-b \
        --maintenance-policy=MIGRATE \
        --min-cpu-platform="AMD Milan" \
        --image-project=confidential-space-images \
        --image-family=confidential-space-debug \
        --service-account=WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --metadata="^~^tee-image-reference=us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME:latest~tee-container-log-redirect=true"
    

프로덕션 이미지와 달리 디버그 이미지는 워크로드가 완료된 후에도 VM을 계속 실행합니다. 따라서 SSH를 사용하여 VM에 연결하여 디버깅을 계속할 수 있습니다.

삭제

이 가이드에서 만든 리소스를 삭제하려면 다음 안내를 따르세요.

알렉스의 리소스 삭제

  1. 알렉스의 프로젝트로 전환합니다.

    gcloud config set project ALEX_PROJECT_ID
    
  2. 알렉스의 워크로드 아이덴티티 풀을 삭제합니다.

    gcloud iam workload-identity-pools delete ALEX_POOL_NAME \
        --location=global
    
  3. 알렉스의 Cloud Storage 버킷을 삭제합니다.

    gcloud storage rm gs://ALEX_INPUT_BUCKET_NAME \
        gs://ALEX_RESULTS_BUCKET_NAME --recursive
    
  4. 알렉스의 급여 파일과 Go 코드를 삭제합니다.

    rm ALEX_SALARY.txt \
        ALEX_ENCRYPTED_SALARY_FILE \
        salary.go salary \
        go.mod go.sum
    
  5. 선택사항: 알렉스의 Cloud Key Management Service 키를 사용 중지하거나 폐기합니다.

  6. 선택사항: 알렉스의 프로젝트를 종료합니다.

볼라의 리소스 삭제

  1. 볼라의 프로젝트로 전환합니다.

    gcloud config set project BOLA_PROJECT_ID
    
  2. 워크로드 VM을 삭제합니다.

    gcloud compute instances delete WORKLOAD_VM_NAME --zone=us-west1-b
    
  3. 워크로드를 실행하는 서비스 계정을 삭제합니다.

    gcloud iam service-accounts delete \
        WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com
    
  4. 볼라의 워크로드 아이덴티티 풀을 삭제합니다.

    gcloud iam workload-identity-pools delete BOLA_POOL_NAME \
        --location=global
    
  5. 볼라의 Cloud Storage 버킷을 삭제합니다.

    gcloud storage rm gs://BOLA_INPUT_BUCKET_NAME \
        gs://BOLA_RESULTS_BUCKET_NAME --recursive
    
  6. 볼라의 급여 파일을 삭제합니다.

    rm BOLA_SALARY.txt \
        BOLA_ENCRYPTED_SALARY_FILE
    
  7. 선택사항: 볼라의 Cloud Key Management Service 키를 사용 중지하거나 폐기합니다.

  8. 선택사항: 볼라의 프로젝트를 종료합니다.