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


이 가이드에서 알렉스와 볼라는 서로 숫자를 공개하지 않으면서 최고 급여자를 찾아내려고 합니다. 이들은 자신의 데이터를 기밀로 유지하기 위해 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

데이터 공동작업자(Alex의 프로젝트)

이 가이드에서는 앨릭스만 이 API를 사용 설정해야 합니다. 그러나 두 당사자 이상이 관여하는 경우 워크로드 서비스 계정을 호스팅하지 않는 모든 데이터 공동작업자의 프로젝트에서 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/iam.serviceAccountAdmin)
  • 데이터 공동작업자(알렉스 및 볼라) 및 워크로드 운영자(볼라)에 대한 스토리지 관리자(roles/storage.admin)
  • 워크로드 운영자(볼라)에 대한 컴퓨팅 관리자(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 service-accounts create ALEX_SERVICE_ACCOUNT_NAME
  8. 데이터 복호화를 위해 방금 만든 암호화 키를 사용할 수 있도록 서비스 계정에 cloudkms.cryptoKeyDecrypter 역할을 부여합니다.

    gcloud kms keys add-iam-policy-binding \
        projects/ALEX_PROJECT_ID/locations/global/keyRings/ALEX_KEYRING_NAME/cryptoKeys/ALEX_KEY_NAME \
        --member=serviceAccount:ALEX_SERVICE_ACCOUNT_NAME@ALEX_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/cloudkms.cryptoKeyDecrypter
  9. 워크로드 아이덴티티 풀을 만든 후 iam.workloadIdentityUser 역할을 사용해서 여기에 서비스 계정을 연결합니다.

    gcloud iam workload-identity-pools create ALEX_POOL_NAME \
        --location=global
    gcloud iam service-accounts add-iam-policy-binding \
        ALEX_SERVICE_ACCOUNT_NAME@ALEX_PROJECT_ID.iam.gserviceaccount.com \
        --member="principalSet://iam.googleapis.com/projects/"$(gcloud projects describe ALEX_PROJECT_ID \
            --format="value(projectNumber)")"/locations/global/workloadIdentityPools/ALEX_POOL_NAME/*" \
        --role=roles/iam.workloadIdentityUser
  10. 입력 데이터에 대해 Cloud Storage 버킷을 하나 만들고 결과를 저장할 버킷을 하나 더 만듭니다.

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

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

    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 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 service-accounts create BOLA_SERVICE_ACCOUNT_NAME
  7. 데이터 복호화를 위해 방금 만든 암호화 키를 사용할 수 있도록 서비스 계정에 cloudkms.cryptoKeyDecrypter 역할을 부여합니다.

    gcloud kms keys add-iam-policy-binding \
        projects/BOLA_PROJECT_ID/locations/global/keyRings/BOLA_KEYRING_NAME/cryptoKeys/BOLA_KEY_NAME \
        --member=serviceAccount:BOLA_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/cloudkms.cryptoKeyDecrypter
  8. 워크로드 아이덴티티 풀을 만든 후 iam.workloadIdentityUser 역할을 사용해서 여기에 서비스 계정을 연결합니다.

    gcloud iam workload-identity-pools create BOLA_POOL_NAME \
        --location=global
    gcloud iam service-accounts add-iam-policy-binding \
        BOLA_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com \
        --member="principalSet://iam.googleapis.com/projects/"$(gcloud projects describe BOLA_PROJECT_ID \
            --format="value(projectNumber)")"/locations/global/workloadIdentityPools/BOLA_POOL_NAME/*" \
        --role=roles/iam.workloadIdentityUser
  9. 입력 데이터에 대해 Cloud Storage 버킷을 하나 만들고 결과를 저장할 버킷을 하나 더 만듭니다.

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

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

    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자를 포함하여 누구나 이 역할을 가질 수 있습니다.

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

  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 (
      kms "cloud.google.com/go/kms/apiv1"
      "cloud.google.com/go/storage"
      "context"
      "fmt"
      "google.golang.org/api/option"
      kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
      "io/ioutil"
      "strconv"
      "strings"
      "time"
    )
    
    type collaborator struct {
      name         string
      wipName      string
      sa           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
    const collaborator1ServiceAccountName string = ""      // The name of Alex's service account that can decrypt their salary
    
    // 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
    const collaborator2ServiceAccountName string = ""      // The name of Bola's service account that can decrypt their salary
    
    // END USER VARIABLES SECTION
    // ==========================
    
    var collaborators = [2]collaborator{
      {
        collaborator1Name,
        "projects/" + collaborator1ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator1PoolName + "/providers/attestation-verifier",
        collaborator1ServiceAccountName + "@" + collaborator1ProjectName + ".iam.gserviceaccount.com",
        "projects/" + collaborator1ProjectName + "/locations/global/keyRings/" + collaborator1KMSKeyringName + "/cryptoKeys/" + collaborator1KMSKeyName,
        collaborator1BucketInputName,
        collaborator1EncryptedSalaryFileName,
        collaborator1BucketOutputName,
        collaborator1BucketOutputFileName,
      },
      {
        collaborator2Name,
        "projects/" + collaborator2ProjectNumber + "/locations/global/workloadIdentityPools/" + collaborator2PoolName + "/providers/attestation-verifier",
        collaborator2ServiceAccountName + "@" + collaborator2ProjectName + ".iam.gserviceaccount.com",
        "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"
            },
            "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken"
            }`
    
    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.sa, 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, trustedServiceAccountEmail, wippro string, encryptedData []byte) ([]byte, error) {
      cc := fmt.Sprintf(credentialConfig, wippro, trustedServiceAccountEmail)
      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 := ioutil.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
    const collaborator1ServiceAccountName string = "ALEX_SERVICE_ACCOUNT_NAME"       // The name of Alex's service account that can decrypt their salary
    
    // 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
    const collaborator2ServiceAccountName string = "BOLA_SERVICE_ACCOUNT_NAME"       // The name of Bola's service account that can decrypt their salary
    

    알렉스와 볼라의 프로젝트 번호도 업데이트해야 합니다. 다음 명령어로 가져올 수 있습니다.

    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 이미지를 감사하고 신뢰성을 확인하도록 해야 합니다.

워크로드 승인

양 당사자가 워크로드를 승인함에 따라 알렉스와 볼라는 Confidential Space 증명 확인자 서비스를 제공자로서 워크로드 아이덴티티 풀에 추가해야 합니다. 이렇게 하면 특정 속성 조건이 충족되었을 때 워크로드에 연결된 서비스 계정이 자신의 풀에 연결된 서비스 계정을 가장하고 데이터에 액세스합니다. 즉, 속성 조건이 증명 정책으로 작동합니다.

이 가이드에 사용된 속성 조건은 다음과 같습니다.

  • 실행 중인 Docker 이미지의 다이제스트

  • 워크로드를 실행하는 서비스 계정의 이메일 주소

악의적인 행위자가 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=assertion.sub" \
        --attribute-condition="assertion.submods.container.image_digest == 'DOCKER_IMAGE_DIGEST' \
    && 'WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts \
    && assertion.swname == 'CONFIDENTIAL_SPACE' \
    && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    
  2. 볼라의 프로젝트로 전환합니다.

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

    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=assertion.sub" \
        --attribute-condition="assertion.submods.container.image_digest == 'DOCKER_IMAGE_DIGEST' \
    && 'WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts \
    && assertion.swname == 'CONFIDENTIAL_SPACE' \
    && 'STABLE' in assertion.submods.confidential_space.support_attributes"
    

워크로드 배포

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

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

  • AMD SEV 또는 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 \
    --min-cpu-platform="AMD Milan" \
    --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

워크로드 디버그

또한 로그 탐색기를 사용하여 리소스가 올바르게 설정되지 않는 문제 또는 공급자의 속성 조건이 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=assertion.sub" \
        --attribute-condition="assertion.submods.container.image_digest == 'DOCKER_IMAGE_DIGEST' \
    && 'WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts \
    && 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=assertion.sub" \
        --attribute-condition="assertion.submods.container.image_digest == 'DOCKER_IMAGE_DIGEST' \
    && 'WORKLOAD_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts \
    && 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 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
    

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

삭제

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

알렉스의 리소스 삭제

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

    gcloud config set project ALEX_PROJECT_ID
    
  2. 알렉스의 데이터를 복호화하는 서비스 계정을 삭제합니다.

    gcloud iam service-accounts delete \
        ALEX_SERVICE_ACCOUNT_NAME@ALEX_PROJECT_ID.iam.gserviceaccount.com
    
  3. 알렉스의 워크로드 아이덴티티 풀을 삭제합니다.

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

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

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

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

볼라의 리소스 삭제

  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 \
        BOLA_SERVICE_ACCOUNT_NAME@BOLA_PROJECT_ID.iam.gserviceaccount.com
    
    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. 선택사항: 볼라의 프로젝트를 종료합니다.