In this guide, Alex and Bola want to find out who has the highest salary without revealing numbers to each other. They decide to use Confidential Space to keep their data confidential, and agree to take on the following roles:
Alex: Data collaborator, workload author
Bola: Data collaborator, workload operator
This arrangement is designed to keeps things as straightforward as possible for this guide. However, it's possible for the workload author and operator to be entirely independent from the data collaborators, and you can have as many collaborators as you want.
Before you begin
This guide demonstrates a Confidential Space scenario using a single account in a single organization with access to multiple projects, so you can experience the whole process. In a production deployment, collaborators, workload authors, and workload operators have separate accounts and their own projects contained in discrete organizations, inaccessible to each other and keeping their confidential data separate.
Confidential Space can interact with many of Google Cloud's services to produce its results, including but not limited to:
This guide makes use of and assumes a basic understanding of all of these features.
Required APIs
You must enable the following APIs in the specified projects to be able to complete this guide.
API name | API title | Enable in these projects |
---|---|---|
cloudkms.googleapis.com |
Cloud KMS | Data collaborators (Alex and Bola's projects) |
iamcredentials.googleapis.com |
IAM Service Account Credentials API |
Data collaborators (Alex's project) In this guide, only Alex needs this API enabled. However, if more than two parties are involved, the IAM Service Account Credentials API needs to be enabled on every data collaborator's project that doesn't host the workload service account. |
artifactregistry.googleapis.com |
Artifact Registry | Workload author (Alex's project) |
compute.googleapis.com |
Compute Engine | Workload operator (Bola's project) |
confidentialcomputing.googleapis.com |
Confidential Computing | Workload operator (Bola's project) |
Required roles
To get the permissions that you need to complete this guide, ask your administrator to grant you the following IAM roles on the project:
-
Cloud KMS Admin (
roles/cloudkms.admin
) for the data collaborators (Alex and Bola). -
IAM Workload Identity Pool Admin (
roles/iam.workloadIdentityPoolAdmin
) for the data collaborators (Alex and Bola). -
Service Usage Admin (
roles/serviceusage.serviceUsageAdmin
) for the data collaborators (Alex and Bola). -
Service Account Admin (
roles/iam.serviceAccountAdmin
) for the data collaborators (Alex and Bola). -
Storage Admin (
roles/storage.admin
) for the data collaborators (Alex and Bola) and the workload operator (Bola). -
Compute Admin (
roles/compute.admin
) for the workload operator (Bola). -
Security Admin (
roles/securityAdmin
) for the workload operator (Bola). -
Artifact Registry Administrator (
roles/artifactregistry.admin
) for the workload author (Alex).
For more information about granting roles, see Manage access to projects, folders, and organizations.
You might also be able to get the required permissions through custom roles or other predefined roles.
Set up data collaborator resources
Both Alex and Bola need independent projects that contain the following resources:
The confidential data itself.
An encryption key to encrypt that data and keep it confidential.
A Cloud Storage bucket to store the encrypted data in.
A service account that has access to the encryption key, so it can decrypt the confidential data.
A workload identity pool with that service account connected to it. The workload processing the confidential data uses the pool to impersonate the service account and retrieve the unencrypted data.
To get started, go to the Google Cloud console:
Set up Alex's resources
To set up the resources for Alex, complete the following instructions.
- Click Activate Cloud Shell.
-
In Cloud Shell, enter the following command to create a project for Alex, replacing ALEX_PROJECT_ID with a name of your choice:
gcloud projects create ALEX_PROJECT_ID
-
Switch to the newly created project:
gcloud config set project ALEX_PROJECT_ID
-
If you haven't done so already, enable the APIs Alex requires as a data collaborator and workload author:
gcloud services enable cloudkms.googleapis.com artifactregistry.googleapis.com iamcredentials.googleapis.com
-
Create a key ring and encryption key with 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
-
Grant Alex the
cloudkms.cryptoKeyEncrypter
role so they can use the newly created encryption key to encrypt data: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
-
Create a service account that is used later by the workload to decrypt the data:
gcloud iam service-accounts create ALEX_SERVICE_ACCOUNT_NAME
-
Grant the service account the
cloudkms.cryptoKeyDecrypter
role so it can use the encryption key you just created to decrypt data: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
-
Create a workload identity pool, and then connect the service account to it with a role of
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
-
Create a Cloud Storage bucket for the input data, and another to store the results in:
gcloud storage buckets create gs://ALEX_INPUT_BUCKET_NAME \ gs://ALEX_RESULTS_BUCKET_NAME
-
Create a file that contains only Alex's salary as a number:
echo 123456 > ALEX_SALARY.txt
-
Encrypt the file, and then upload it to Alex's bucket:
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
Set up Bola's resources
To set up the resources for Bola, complete the following instructions.
-
In Cloud Shell, enter the following command to create a project for Bola, replacing BOLA_PROJECT_ID with a name of your choice:
gcloud projects create BOLA_PROJECT_ID
-
Switch to the newly created project:
gcloud config set project BOLA_PROJECT_ID
-
If you haven't done so already, enable the APIs Bola requires as a data collaborator and workload operator:
gcloud services enable cloudkms.googleapis.com compute.googleapis.com confidentialcomputing.googleapis.com
-
Create a key ring and encryption key with 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
-
Grant Bola the
cloudkms.cryptoKeyEncrypter
role so they can use the newly created encryption key to encrypt data: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
-
Create a service account that is used later by the workload to decrypt the data:
gcloud iam service-accounts create BOLA_SERVICE_ACCOUNT_NAME
-
Grant the service account the
cloudkms.cryptoKeyDecrypter
role so it can use the encryption key you just created to decrypt data: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
-
Create a workload identity pool, and then connect the service account to it with a role of
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
-
Create a Cloud Storage bucket for the input data, and another to store the results in:
gcloud storage buckets create gs://BOLA_INPUT_BUCKET_NAME \ gs://BOLA_RESULTS_BUCKET_NAME
-
Create a file that contains only Bola's salary as a number:
echo 111111 > BOLA_SALARY.txt
-
Encrypt the file, and then upload it to Bola's bucket:
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
Create a service account for the workload
In addition to the service accounts both Alex and Bola set up to decrypt their data, another service account is needed to run the workload. Since service accounts are used to both decrypt the confidential data and process it, data visibility is restricted to its owners.
In this guide Bola operates and runs the workload, but anyone can take on these roles, including a third party.
Complete the following steps in Bola's project to set up the service account:
Create a service account to run the workload:
gcloud iam service-accounts create WORKLOAD_SERVICE_ACCOUNT_NAME
Grant Bola the
iam.serviceAccountUser
role to impersonate the service account, which is required so they can create a workload VM later: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
Grant the service account the
confidentialcomputing.workloadUser
role so it can generate an attestation token: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
Grant the service account the
logging.logWriter
role to write logs to Cloud Logging, so you can check the progress of the workload: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
Give the service account read access to both Alex and Bola's buckets that contain their encrypted data, and write access to each of their results buckets:
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_I.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
This assumes the user granting the access has the Storage Admin (
roles/storage.admin
) role for the project that contains the Cloud Storage bucket that's being operated on.
Create the workload
In this guide Alex provides the code for the workload and builds a Docker image to contain it, but anyone can take on these roles, including a third party.
Alex needs to create the following resources for the workload:
The code that performs the workload.
A Docker repository in Artifact Registry, that the service account running the workload has access to.
A Docker image that contains and runs the workload code.
To create and set up the resources, complete the following steps in Alex's project:
Switch to Alex's project:
gcloud config set project ALEX_PROJECT_ID
Create a Docker repository in Artifact Registry:
gcloud artifacts repositories create REPOSITORY_NAME \ --repository-format=docker \ --location=us
Grant the service account that's going to run the workload the Artifact Registry Reader (
roles/artifactregistry.reader
) role so it can read from the repository: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
Click Open editor to open the Cloud Shell Editor, and then create a new file called
salary.go
. Copy the following code into the file, and then save it:// 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 }
Modify the
USER VARIABLES SECTION
in the source code, replacing the emptyconst
values with the relevant resource names as described in the code comments. If you have edited the placeholder variables likeALEX_PROJECT_ID
shown in this guide, the values are included in the following code sample, which you can copy and paste over the existing code:// 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
Make sure to also update the project numbers for both Alex and Bola. You can retrieve them with the following command:
gcloud projects describe PROJECT_ID --format="value(projectNumber)"
Make sure that all parties read and audit the source code.
Click Terminal > New Terminal to open a terminal within Cloud Shell Editor.
Enter the following commands in the terminal to set up the Go environment:
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
Enter the following command to compile the source code to a statically linked binary:
CGO_ENABLED=0 go build -trimpath
Create a file named
Dockerfile
in Cloud Shell Editor containing the following contents:FROM alpine:latest WORKDIR /test COPY salary /test ENTRYPOINT ["/test/salary"] CMD []
Update your Docker credentials to include the
us-docker.pkg.dev
domain name:gcloud auth configure-docker us-docker.pkg.dev
Create a Docker image from
Dockerfile
by entering the following command in the terminal:docker build -t \ us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME:latest .
Push the Docker image to Artifact Registry:
docker push \ us-docker.pkg.dev/ALEX_PROJECT_ID/REPOSITORY_NAME/WORKLOAD_CONTAINER_NAME
From the Docker push response, copy the Docker image's digest (including the
sha256:
prefix) somewhere safe to use later.Make sure all parties audit the Docker image and verify that it's trustworthy before authorizing its use.
Authorize the workload
With the workload approved by both parties, Alex and Bola need to add the Confidential Space attestation verifier service as a provider to their workload identity pools. This lets the service account attached to the workload impersonate the service accounts connected to their pools and access their data, providing certain attribute conditions are met. This means the attribute conditions act as attestation policies.
The attribute conditions used in this guide are as follows:
The digest of the Docker image being run
The email address of the service account that runs the workload
If a malicious actor changes the Docker image, or a different service account is attached to the workload, the workload won't be permitted to access Alex or Bola's data.
To view the available attribute conditions, see Attestation assertions.
To set up the providers for Alex and Bola with the required conditions, complete the following steps:
Enter the following command to create the provider for Alex:
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"
Switch to Bola's project:
gcloud config set project BOLA_PROJECT_ID
Enter the following command to create the provider for Bola:
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"
Deploy the workload
With providers added to both Alex and Bola's workload identity pools and the required resources in place, it's time for the workload operator to run the workload.
To deploy the workload, create a new Confidential VM instance in Bola's project that has the following properties:
A supported configuration for an AMD SEV Confidential VM instance.
An OS based on the Confidential Space image.
Secure Boot enabled.
The Docker image attached that Alex created earlier.
The service account attached that runs the workload.
Enter the following command in Bola's Cloud Shell to deploy the workload:
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"
You can view the progress of the workload in Bola's project by going to Logs explorer.
To find the Confidential Space logs, filter by the following Log fields if they're available:
Resource type: VM Instance
Instance ID: The instance ID of the VM
Log name: confidential-space-launcher
To refresh the log, click Jump to now.
When the workload is finished, the VM instance stops. If you want to change the encrypted salary files and deploy the workload again, you only need to start the existing VM:
gcloud compute instances start WORKLOAD_VM_NAME --zone=us-west1-b
Debug the workload
You can use Logs explorer to troubleshoot issues like resources not being set up correctly, or attribute conditions in providers not matching the claims made by the Confidential Space workload.
To do so, you need to make the following changes:
Update Alex and Bola's workload identity pool providers to remove the
support_attributes
assertion. You need to use the Confidential Space debug image to perform more in-depth troubleshooting, and that image has no support attributes to verify.Create the workload VM using the Confidential Space debug image, and set the VM metadata to redirect
STDOUT
andSTDERR
to Cloud Logging to capture all output from the workload.
To make the changes, complete the following steps:
Switch to Alex's project:
gcloud config set project ALEX_PROJECT_ID
Update Alex's provider to remove the
support_attributes
assertion: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'"
Switch to Bola's project:
gcloud config set project BOLA_PROJECT_ID
Update Bola's provider to remove the
support_attributes
assertion: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'"
Create a new VM with the Confidential Space debug image, and
tee-container-log-redirect
set totrue
in the metadata.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"
Unlike the production image, the debug image keeps the VM running after the workload has finished. This means you can use SSH to connect to your VM to continue debugging.
View the results
After the workload has successfully completed, both Alex and Bola can view the results in their respective results buckets:
Switch to Alex's project:
gcloud config set project ALEX_PROJECT_ID
List all the files in their results bucket:
gcloud storage ls gs://ALEX_RESULTS_BUCKET_NAME
Then read the latest file:
gcloud storage cat gs://ALEX_RESULTS_BUCKET_NAME/ALEX_RESULTS_FILE_NAME
Switch to Bola's project:
gcloud config set project BOLA_PROJECT_ID
For Bola, list all the files in their results bucket:
gcloud storage ls gs://BOLA_RESULTS_BUCKET_NAME
Then read the latest file:
gcloud storage cat gs://BOLA_RESULTS_BUCKET_NAME/BOLA_RESULTS_FILE_NAME
By reading the files, Alex and Bola each discover who earns more without ever revealing their salaries to each other.
Cleanup
To remove the resources created in this guide, complete the following instructions.
Clean up Alex's resources
Switch to Alex's project:
gcloud config set project ALEX_PROJECT_ID
Delete the service account that decrypts Alex's data:
gcloud iam service-accounts delete \ ALEX_SERVICE_ACCOUNT_NAME@ALEX_PROJECT_ID.iam.gserviceaccount.com
Delete Alex's workload identity pool:
gcloud iam workload-identity-pools delete ALEX_POOL_NAME \ --location=global
Delete Alex's Cloud Storage buckets:
gcloud storage rm gs://ALEX_INPUT_BUCKET_NAME \ gs://ALEX_RESULTS_BUCKET_NAME --recursive
Delete Alex's salary files and the Go code:
rm ALEX_SALARY.txt \ ALEX_ENCRYPTED_SALARY_FILE \ salary.go salary \ go.mod go.sum
Optional: Disable or destroy Alex's Cloud Key Management Service key.
Optional: Shut down Alex's project.
Clean up Bola's resources
Switch to Bola's project:
gcloud config set project BOLA_PROJECT_ID
Delete the workload VM:
gcloud compute instances delete WORKLOAD_VM_NAME --zone=us-west1-b
Delete the service account that decrypts Bola's data and the service account that runs the workload:
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
Delete Bola's workload identity pool:
gcloud iam workload-identity-pools delete BOLA_POOL_NAME \ --location=global
Delete Bola's Cloud Storage buckets:
gcloud storage rm gs://BOLA_INPUT_BUCKET_NAME \ gs://BOLA_RESULTS_BUCKET_NAME --recursive
Delete Bola's salary files:
rm BOLA_SALARY.txt \ BOLA_ENCRYPTED_SALARY_FILE
Optional: Disable or destroy Bola's Cloud Key Management Service key.
Optional: Shut down Bola's project.