GCP reference
Facts to look up while deploying and running Polaris on Google Cloud. For why GCP works the way it does, see Polaris on Google Cloud.
What Polaris offers on GCP today
| Capability | On GCP |
|---|---|
AMD SEV-SNP, with the report checked directly against AMD's keys (sev-snp-direct) | Available, and the default |
AMD SEV-SNP with an SVSM and its vTPM (sev-snp-svsm) | Available. The workload VM is created through Compute Engine's alpha API |
Intel TDX (tdx) | Available |
| NVIDIA H100 confidential GPU, with Intel TDX | Only with --local-build. The released images include no GPU-enabled proxy, so deploy refuses it otherwise |
AMD SEV-SNP checked through Google's Confidential Computing verifier (sev-snp) | Not available. deploy switches it to sev-snp-direct |
| Attestation of image, runtime and kernel, with key access withdrawn when a layer you enforce fails | Available |
| Status routes and incident records readable through the endpoint | Available |
| Health telemetry exported from the Policy Manager over OTLP | Available, off by default |
| A route for the workload to use its attestation-gated key | Not yet available |
Learned allowlists and promote settings kept across a Policy Manager restart | Available, on the Policy Manager's state disk |
Learned allowlists and promote settings kept across a deploy | Not yet available. Each deploy writes the baseline under a new name, which starts learning again |
| Pinning the Policy Manager's measurement before the workload VM boots | Available, with cloud.gcp.pm_measurement_hex |
| Ending key access when the Policy Manager stops, without the Policy Manager | Not yet available |
| Naming your own key ring, key or KMS location | Not yet available |
| More than one workload VM | Not yet available |
Regions, zones and machine types
Both VMs run in one zone, cloud.gcp.zone, inside cloud.gcp.region. The defaults are us-central1 and us-central1-a. The tooling does not check the region or zone against the TEE you choose, so pick a zone that offers the machine type with confidential computing. Compute Engine refuses the VM at launch otherwise.
TEE (tee.cpu) | Workload VM default | CPU platform the module requests |
|---|---|---|
sev-snp-direct | n2d-standard-4 | AMD Milan or newer |
sev-snp-svsm | n2d-standard-4 | AMD Milan or newer |
tdx | c3-standard-4 | Intel Sapphire Rapids |
tdx with tee.gpu: nvidia-h100 | a3-highgpu-1g, the only machine type the module accepts | Fixed by the machine type |
The Policy Manager always runs on AMD SEV-SNP, on n2d-standard-2 by default, whatever TEE the workload VM uses.
| VM | Config key | Default |
|---|---|---|
| Workload VM | compute.cvm_machine_type | n2d-standard-4, c3-standard-4 for tdx, or a3-highgpu-1g with a GPU |
| Policy Manager VM | compute.pm_machine_type | n2d-standard-2 |
For a GPU deployment, deploy fills in a3-highgpu-1g when compute.cvm_machine_type is empty. That is the only size the module takes with a GPU, so any other size you set is refused.
The GPU VM always runs as a Spot VM, with no option to change it, and it does not restart on its own. When Compute Engine preempts it, its access to your key ends once its reports stop, and the next deploy creates it again.
What a deployment creates
Everything below is created in your project and removed by destroy, except where noted. <name> is the deployment name, metadata.name. In hardware attestation mode there is no Policy Manager, so its VM, state disk, service account, role, address and firewall rules are not created.
Network
- A VPC network,
<name>-net, with one subnet,<name>-subnet,10.0.0.0/24in the region, with Private Google Access on. - A reserved external address,
<name>-cvm-ip, for the workload VM. It stays the same when the workload VM is replaced. - A reserved internal address,
<name>-pm-ip,10.0.0.253, the last usable address in the subnet, for the Policy Manager. - Firewall rules, applied by network tag:
<name>-allow-https: HTTPS on port 443 to the workload VM from any address.<name>-allow-iap-ssh: SSH on port 22 to both VMs from35.235.240.0/20, the range IAP connects from.<name>-pm-allow-evidenceand<name>-pm-allow-operator: ports 443 and 8443 on the Policy Manager, from the workload VM only.<name>-deny-all-egress: denies all outbound traffic from both VMs, at the lowest priority, so only the allow rules below get out.<name>-allow-egress-gapisand<name>-allow-egress-https: port 443 to199.36.153.4/30and199.36.153.8/30, Google's restricted and private API addresses.<name>-allow-egress-pki-http: port 80 to199.36.153.8/30.<name>-allow-egress-int: port 443 within the subnet.<name>-allow-egress-pm-op: port 8443 from the workload VM to the Policy Manager.<name>-allow-egress-custom: port 443 to the ranges innetwork.allowed_egress_cidrs, when set.<name>-allow-egress-nras: port 443 to NVIDIA's attestation service, on GPU deployments only.<name>-allow-egress-otel: from the Policy Manager to your telemetry collector, when you set one with--otel-endpointand--otel-egress-cidrs.
- A Cloud Router,
<name>-router, and Cloud NAT,<name>-nat, for the subnet. The Policy Manager has no external address and goes out through it, within the same firewall rules. - Three private Cloud DNS zones,
<name>-googleapis,<name>-pkg-devand<name>-gcr-io, visible only to the deployment's network. They pointgoogleapis.com,pkg.devandgcr.ioat199.36.153.8to199.36.153.11, so the VMs reach Google APIs and Artifact Registry without a path to the internet.
Compute
- Two confidential VMs in the zone,
<name>-cvmand<name>-pm, each with a 50 GBpd-ssdboot disk that is deleted with it. Both set host maintenance to terminate rather than migrate. - A state disk for the Policy Manager,
<name>-pm-state,pd-balanced, 10 GB, attached to the Policy Manager VM. It is a disk of its own, so it outlives the VM, and a replacement Policy Manager attaches the same disk. It holds the Policy Manager's issuer keys, its record of the workload VMs, the locks a failure put on the key, what it learned and thepromotesettings.destroydeletes it. - Shielded VM with Secure Boot on both. The Policy Manager and the
sev-snp-directandtdxworkload VMs also have the Shielded vTPM and integrity monitoring. Thesev-snp-svsmworkload VM has the SVSM's vTPM instead, since GCP refuses the two together. - For
sev-snp-svsm, the workload VM is created bydeploythrough Compute Engine's alpha API rather than by Terraform, because the SVSM setting exists only there.deployreads the VM back and deletes it if it did not get the SVSM vTPM.deploydeletes and recreates this VM on every run, anddestroydeletes it before running Terraform.
Identity
- Two service accounts,
<name>-cvmand<name>-pm, cut to 30 characters. Their roles are in The service accounts and roles. - A custom role,
<name>PMIAMManager<suffix>, with the hyphens taken out of the name and six random hex characters added, bound to the Policy Manager's service account on the deployment's key. - A Workload Identity Federation pool,
<name>-attest-pool, and an OIDC provider in it,<name>-cc-prov, for the issuerhttps://confidentialcomputing.googleapis.com, limited to tokens that name your project. Both IDs are cut to 32 characters. Both are disabled, nothing is bound to them, and the key gate does not use them. See The service accounts and roles. - When
sandbox.operator_principalis set, an operator service account,<name>-operator, for a third party. See the same section.
Keys
- A Cloud KMS key ring,
<name>-keyring, and key,<name>-key, in the deployment's region. The key is for asymmetric decryption,RSA_DECRYPT_OAEP_4096_SHA256, with HSM protection. The Policy Manager grants and removes access to it as the verdict changes.
Created, and kept by destroy
- The key ring and key.
destroytakes them out of Terraform's state before it runs, so they remain, and a later deployment under the same name takes them back and gates the same key. If you delete the key's versions yourself, Cloud KMS keeps them restorable for 30 days. - The IAM bindings the Policy Manager added to the key for the workload VM's service account. See Tear down a deployment.
- The Artifact Registry repository
polaris.docker_repo,polaris-dockerby default, in the deployment's region.deploycreates it withgcloudif it is missing and copies the Polaris images into it. Later deployments with the same repository name reuse it.
The service accounts and roles
| Service account | Role | On |
|---|---|---|
<name>-cvm, the workload VM | roles/artifactregistry.reader | The project |
roles/confidentialcomputing.workloadUser | The project | |
roles/cloudkms.cryptoKeyEncrypterDecrypter and roles/cloudkms.publicKeyViewer | The deployment's key, added and removed by the Policy Manager | |
<name>-pm, the Policy Manager | roles/artifactregistry.reader | The project |
roles/confidentialcomputing.workloadUser | The project | |
The custom role, with cloudkms.cryptoKeys.getIamPolicy and cloudkms.cryptoKeys.setIamPolicy | The deployment's key | |
<name>-operator, only with sandbox.operator_principal | roles/compute.viewer, roles/iap.tunnelResourceAccessor and roles/artifactregistry.writer | The project |
roles/compute.osLogin | The Policy Manager VM only |
Both VMs run with the cloud-platform access scope. What each can do is set by the roles above.
Older releases also gave roles/iam.workloadIdentityUser on <name>-cvm to every identity from the deployment's federation pool. The tooling no longer creates that binding, and deploy removes one an older release left on the account, from fresh state too, before it plans. If it cannot read the account's policy it says so. To check by hand: gcloud iam service-accounts get-iam-policy <name>-cvm@<project>.iam.gserviceaccount.com.
With sandbox.operator_principal set, that principal gets roles/iam.serviceAccountTokenCreator on <name>-operator and nothing else, and the Policy Manager VM turns on OS Login so that the operator account can SSH to it. The operator account has no role on the key and none that changes IAM. It is meant for letting a third party read a sandbox, and destroy removes it.
Where a deployment connects
| From | To | For |
|---|---|---|
The machine that runs deploy | Google Cloud APIs in your project | Creating and checking the deployment |
The machine that runs deploy | ghcr.io/fr0ntierx | Copying the Polaris images into your Artifact Registry, pinned by digest |
The machine that runs deploy | Both VMs on port 22, through IAP | Health checks, capturing the Policy Manager's measurement, status and promote |
| Workload VM | Google APIs and Artifact Registry, through the private API addresses | Pulling images and Google API calls |
| Workload VM | The Compute Engine metadata server | Its identity and service account credentials. Only the proxy's container and the host reach it, and the workload's container does not |
| Workload VM | The Policy Manager on ports 443 and 8443 | Evidence reports, and the tunnel Polaris Desktop uses |
| Workload VM | The ranges in network.allowed_egress_cidrs, on port 443 | Your workload's outbound traffic |
| Policy Manager | The same private API addresses and the ranges in network.allowed_egress_cidrs | Pulling images, Cloud KMS, and anything you allow |
| Policy Manager | Your OTLP collector, when set | Health telemetry |
| Both VMs, GPU deployments only | NVIDIA's attestation service, 34.120.45.54/32 by default | Checking the GPU's evidence |
The network rules apply to the Policy Manager as well, so although it goes out through Cloud NAT, it reaches no more of the internet than the workload VM does. That includes AMD's Key Distribution Service, so the Policy Manager does not check AMD's or Intel's certificate revocation lists on GCP.
Name lookups go to the metadata server, which firewall rules do not filter, so both VMs can resolve any name.
Telemetry leaves the Policy Manager only when you set --otel-endpoint, and it carries health data such as verdict counts and latencies, never evidence or secrets. Nothing exports from the workload VM.
IAM permissions
The identity that deploys
deploy enables no Google Cloud APIs. Enable the APIs for Compute Engine, Cloud DNS, Cloud KMS, IAM, Cloud Resource Manager, Artifact Registry and Identity-Aware Proxy in the project first, since the deployment creates or calls resources in each.
Before it creates anything in workload or full mode, deploy checks that your active gcloud account holds two roles as direct bindings on the project, and stops if either is missing:
roles/iam.workloadIdentityPoolAdminroles/iam.roleAdmin
The check looks for these exact roles bound to your own account, so a role that contains their permissions, or a binding through a group, does not satisfy it.
Beyond those two, the identity that runs deploy and destroy has to be able to do the following:
| Service | What the tooling does | Used for |
|---|---|---|
| Compute Engine | Create, read and delete networks, subnets, addresses, firewall rules, routers, Cloud NAT and instances. Read images in the image project. Insert and read instances through the alpha API for sev-snp-svsm | The network and both VMs |
| Cloud DNS | Create and delete private managed zones and their record sets | Routing Google API traffic to the private addresses |
| Cloud KMS | Create and read a key ring and key, and set an IAM binding on the key | The deployment's key, and the Policy Manager's role on it |
| IAM | Create and delete service accounts, a custom role and a federation pool and provider, set an IAM binding on a service account, and undelete a pool, provider or role left soft-deleted by an earlier deployment of the same name | The deployment's identities |
| Resource Manager | Read the project's IAM policy and add project-level role bindings | The prerequisite check, and the roles each service account gets |
| Artifact Registry | Create and read a Docker repository, and push and read images | Copying the Polaris images, and your workload's when deploy builds it |
| Identity-Aware Proxy | Open a TCP tunnel to port 22 on both VMs, and SSH to them with gcloud compute ssh and gcloud compute scp | The steps after Terraform, status and promote |
The VMs run as the two service accounts the deployment creates, so the deploying identity also has to be allowed to attach them to instances.
An identity that can create service accounts, grant them project roles and attach them to VMs it creates can give those VMs any permission in the project. Treat the deploying identity as an administrator of the project it deploys into, and give each deployment a project of its own.
deploy also runs gcloud config set project with cloud.gcp.project_id, which changes the default project of your gcloud configuration.
What the Policy Manager gets on the key
The Policy Manager's service account holds the custom role on the deployment's key and nothing else on it. The role lets it read and write the key's IAM policy.
When the workload VM passes, the Policy Manager adds the workload VM's service account to the key's policy under two roles:
roles/cloudkms.cryptoKeyEncrypterDecrypterroles/cloudkms.publicKeyViewer
It removes the service account from both when a layer you enforce fails, when the workload VM stops reporting, or when the grant reaches its lifetime without a passing report. The role is not limited by an IAM condition, so nothing in Google Cloud limits which roles or members the Policy Manager's service account writes into the key's policy.
Deployment config keys
The keys specific to GCP, under cloud.gcp:
| Key | Required | Default | Meaning |
|---|---|---|---|
project_id | Yes | The project to deploy into | |
region | No | us-central1 | The region for the subnet, the addresses, the Artifact Registry repository and the key ring |
zone | No | us-central1-a | The zone for both VMs |
pm_measurement_hex | No | unset | The launch measurement the Policy Manager's SEV-SNP report must carry, 96 hex characters. Set, it is written into the workload VM's configuration before the VM boots, and the proxy refuses any other Policy Manager. Unset, the proxy trusts the first Policy Manager that answers until deploy installs the value it captured. On GCP the measurement covers Google's firmware and the vCPU shape, not the Policy Manager's software, so it describes the VM's shape and is not an identity, and a firmware rollout moves it. deploy prints the value after a deployment |
pm_required_vmpl | No | 0 | The privilege level, 0 to 3, the Policy Manager's report must carry. Read only when pm_measurement_hex is set |
The keys that choose the TEE:
| Key | Default | Meaning on GCP |
|---|---|---|
tee.cpu | sev-snp-direct, sev-snp-svsm or tdx. sev-snp is accepted and switched to sev-snp-direct | |
tee.gpu | none | none or nvidia-h100. nvidia-h100 needs tee.cpu: tdx and --local-build, and runs on a3-highgpu-1g, which deploy picks when the machine type is empty |
tee.svsm_measurement | unset | For sev-snp-svsm only. The SHA-384 of the SVSM firmware, 96 hex characters. Set, the platform check accepts only that firmware. A wrong value fails the platform check and withdraws access to your key, and so does a firmware update by Google until you change the value |
Keys shared with the other clouds that apply on GCP:
| Key | Default | Meaning on GCP |
|---|---|---|
metadata.name | The deployment name, used in every resource name. A lowercase letter, then 1 to 24 lowercase letters, digits or hyphens | |
compute.cvm_machine_type | n2d-standard-4, c3-standard-4 for tdx, a3-highgpu-1g with a GPU | Workload VM machine type. See Regions, zones and machine types |
compute.pm_machine_type | n2d-standard-2 | Policy Manager machine type. It must be an AMD SEV-SNP machine type |
compute.cvm_image_family | polaris-cvm, and polaris-cvm-gpu on GPU deployments | The image family both VMs boot from. deploy warns when it is anything else |
compute.cvm_image_project | the deployment's project | The project that holds the image family |
network.allowed_egress_cidrs | empty | Ranges both VMs may reach over HTTPS on port 443 |
features.attestation_mode | full | hardware, workload or full. See Deploying Polaris |
features.ebpf | false | Runs the Sentinel agent, which collects runtime and kernel evidence. It runs only in full mode, so set it for full attestation |
features.ima | false | Has the Sentinel read the kernel's measurements of every executable. Takes effect only in full mode with features.ebpf: true |
workload.image | A prebuilt workload image. One in Artifact Registry in the deployment's own project needs nothing else | |
workload.dir | In place of workload.image, a folder with a Dockerfile, relative to the top folder of the Polaris tooling. deploy builds it for linux/amd64 and pushes it to Artifact Registry in your project | |
workload.port | 3000 | The port your workload listens on, over plain HTTP |
workload.env | empty | Environment variables for the workload, as KEY: value pairs. They become part of the workload VM's instance metadata, so keep secrets out |
workload.resources.cpus, workload.resources.memory_mb | no limit | Hard limits for the workload container |
workload.security.read_only | false | Runs the workload with a read-only filesystem |
workload.security.capabilities | empty | Extra Linux capabilities the workload needs, as a list |
polaris.docker_repo | polaris-docker | The Artifact Registry repository the Polaris images are copied into. Deployments in one project and region that share the name share the repository |
polaris.proxy_image, polaris.pm_image, polaris.sentinel_image | unset | Prebuilt Polaris images to deploy in place of the released ones. The proxy and Policy Manager images must already be in the deployment's repository |
license.file | A license file for a direct contract. See Licensing | |
attestation.report_interval | 30 | Seconds between evidence reports, from 5 to 300 |
attestation.deadman_interval | 120 | Seconds of silence from the workload VM before the Policy Manager withdraws access. At least 30, and more than report_interval |
attestation.grant_ttl | 3600 | Seconds a grant lasts without a passing report. At least 60 |
attestation.autolearn_window | 10m | How long Polaris must watch the workload before learning can finish, such as 30m or 2h. Under 12 hours. 0 means no window |
attestation.promotion_window | 1d | How long a layer must be clean under advise before it may enforce. 0 turns the check off, and is the default when sandbox.ttl is set |
sandbox.ttl | unset | Has Compute Engine delete both VMs this long after they start, such as 4h. For trial deployments. The rest of the deployment stays until destroy |
sandbox.operator_principal | unset | A user:, group: or serviceAccount: principal that may act as the read-only operator account |
sandbox.impersonate | unset | Run gcloud and Terraform as that operator account. It can run status, and the tooling refuses deploy and destroy with it |
attestation.trust_domain | polaris.<name> | The name the Policy Manager writes into every certificate it issues. It is stored on the Policy Manager's state disk, so set it before the first deploy, if at all, and never change it afterwards |
attestation.deployment_id | derived from the trust domain | A UUID naming the deployment in the Policy Manager's record of its workload VMs. The Policy Manager refuses to start when its state disk holds a record under another ID, so never change it, or the trust domain it is derived from, once the deployment exists |
These keys are accepted and have no effect on GCP:
kms.gcp.key_ringandkms.gcp.key_id. The key ring and key are always<name>-keyringand<name>-key. These values only reach akmsblock in the baseline file, which the Policy Manager does not use to find the key.kms.gcp.location. The key ring is always incloud.gcp.region.compute.cvm_count. A deployment has one workload VM.network.workload_ingress_cidrs. Port 443 on the workload VM is open to any address.
Commands
Run every command from the top folder of the tooling. deploy and destroy take --config <file>. status and promote take --config <file>, or --project <id> and --name <name> in its place.
| Command | What it does |
|---|---|
deploy | Creates the deployment, or updates it. Copies the Polaris images, writes the baseline, runs Terraform, checks that the workload container and the Policy Manager are up, then reads the Policy Manager's measurement. With cloud.gcp.pm_measurement_hex set it checks the value against the pin and stops on a mismatch, and without it installs the value on the workload VM and prints the config to pin it. Both VMs are replaced when the image family has a newer image. --dry-run stops at the Terraform plan. --force-replace replaces both VMs even when nothing changed. --promotion-window <duration> and --autolearn-window <duration> set the two windows. --preflight runs only the checks and stops |
status | Lists the two VMs and the endpoint, and through IAP reads each layer's status, the license state and how far learning has got from the Policy Manager |
watch | Shows each layer's status and how far learning has got, through the deployment's endpoint, with no Google Cloud access. Takes --endpoint https://<address> and --token <token>, and works with the read-only token |
promote | Moves enforcement layers between advise, enforce and off, in place. --layers takes a comma-separated list of l2, l3, l31, ima_boot, l3_syscalls, l3_binaries and l3_network, and defaults to l31. --mode defaults to enforce. It refuses to enforce a layer whose allowlist is still empty, and only warns for l3_binaries, unless you pass --force. The Policy Manager also refuses enforce for a layer that has not been clean under advise for the promotion window. promote reads the settings back to confirm them |
set-workload | Points a running deployment at a new workload image with a token alone. Takes --endpoint, --token and --image, and pins the image by digest before sending it. The Policy Manager replaces the image allowlist and starts learning the workload again, and the workload VM pulls and restarts the container within about 30 seconds. The image must be pullable from the deployment's registry, and --mirror-registry copies it there first |
destroy | Removes everything in What a deployment creates except the items it keeps |
The GCP deployment leaves ima_boot unset, so it follows l2.
The Policy Manager keeps the settings promote makes, and everything it learned, on its state disk, so they survive a restart of its container or VM. They last only while the baseline file stays the same. Each deploy writes the baseline under a new name that carries the time of the run, so each deploy returns every layer to the settings in the baseline file and starts learning again.
up, up-polaris and down create and remove a time-limited sev-snp-svsm demonstration sandbox. They are not meant for your own workload.
Routes on the endpoint
These are the same routes as on AWS, since the proxy serves them on every cloud.
| Route | Token | Returns |
|---|---|---|
/polaris-container/health | none | {"status":"ok"} while the proxy runs |
/polaris-container/ready | none | Whether the proxy is serving workload traffic, and the state of its gate |
/polaris-container/attestation | none | The hardware report and its certificates, for a client to check |
/polaris-container/tls-info | none | The hash of the proxy's TLS public key, which the hardware report is bound to |
/polaris-container/attestation-status | none | The latest verdict, with a status for each layer and when it was checked |
/polaris-container/attestation-status | read-only | The same, with the reason for a failure and the alerts |
/management/status | read-only | The Policy Manager's status for each workload VM, including its cvm_id |
/management/profiles/learn, /management/ima/learn and /management/kernel/learn | read-only | Each learner's progress |
/management/incidents/<cvm_id> and /management/incidents/<cvm_id>/report | read-only | The VM's recent incidents, as JSON or as a plain-text report |
/management/checks/<cvm_id> | read-only | The latest checks, one by one |
/management/history/<cvm_id> | read-only | Recent verdicts |
/management/kms/locks | read-only | The service accounts a failure has locked out of the key |
/management/kms/locks/<service account>, with DELETE | management | Lifts a lock |
/management/workload | workload-swap | The workload image the deployment should run, read with GET and changed with POST, as set-workload does |
The proxy passes the management token on to every /management/ route, and the read-only token only on GET.
GCP has no key routes on the proxy's internal listener yet, so the workload has no supported way to use the gated key.
Files the tooling keeps
deploy keeps these files for each deployment in terraform/modules/gcp/ in the tooling, so run every command from the copy of the tooling that holds them. The state and the .tfvars file carry the deployment's tokens in plain text, so keep the folder encrypted and access-controlled.
| File | Holds | Removed by destroy |
|---|---|---|
terraform-<name>.tfstate | The Terraform state | No |
terraform-<name>.tfvars | The deployment's resolved configuration, with the tokens and the baseline | No |
terraform-<name>.token | The management token, used by status and promote | Yes, though any later command writes a new, unrelated one |
terraform-<name>.readonly.token | The read-only token, for watch and the management routes | Yes, though any later command writes a new, unrelated one |
terraform-<name>.workload.token | The workload-swap token, for set-workload | No |
pm-snp-baseline-<name>.yaml | An audit copy of the Policy Manager's captured measurement, written when cloud.gcp.pm_measurement_hex is unset | No |
pm-identity-<name>.json | The record Polaris Desktop checks the Policy Manager against | No |
The three tokens also sit in both VMs' boot configuration, which is instance metadata. Anyone who can view the instances in the project can read them there.