Polaris on Kubernetes
This page explains how Polaris runs in a Kubernetes cluster and how that differs from the VM deployments. Read Deploying Polaris first for the choices common to every platform.
To install and run Polaris in a cluster, use the how-to guides. To look up a values key, an object the chart creates or a permission, use the Kubernetes reference.
Polaris ships for Kubernetes as a Helm chart, charts/polaris in the Polaris deployment tooling. The chart needs Kubernetes 1.30 or newer.
Where it runs today
| Cluster | Status |
|---|---|
| GKE Standard, AMD SEV-SNP confidential nodes on Container-Optimized OS | Supported. The chart's values/gke-sev-snp.yaml and its built-in baseline were measured on this setup |
| GKE confidential nodes on Ubuntu | The proxy and Sentinel run there, but the chart ships no baseline for Ubuntu nodes, so you bring your own |
| GKE with Intel TDX nodes | Not yet measured. values/gke-tdx.yaml exists and is unmeasured |
| AKS, EKS, OKE | Not yet measured. Their values files are carried over from the VM deployments and name the fields still to be settled |
| GKE Autopilot | Not supported. The Sentinel needs host PID, host paths and capabilities that Autopilot forbids |
The rest of this page describes GKE with SEV-SNP confidential nodes.
The shape of a deployment
On a VM deployment each workload has a confidential VM of its own. In a cluster, the confidential VM is the node: a GKE node pool with confidential nodes, where every node is an SEV-SNP VM. The chart puts the Polaris pieces on that pool.
- The Policy Manager runs as a Deployment with one replica. It sits behind its own copy of the Polaris proxy, which serves it over attested TLS on port 443.
- The Polaris proxy runs as a sidecar in each protected pod. It is a native sidecar, an init container with
restartPolicy: Always, so it starts before your container and outlives it. It terminates TLS on 443 and forwards to your container over the pod's loopback. - The Sentinel runs as a DaemonSet, one per Polaris node. It watches the node's processes and kernel and hands that evidence to the proxies on its node.
- A mutating webhook, when you turn it on, adds the same proxy sidecar to pods you deploy with your own manifests.
With the GKE values file, the chart also installs Google's confidential computing device plugin, which gives the proxy and the Sentinel the SEV-SNP and TPM devices, and a small DaemonSet that loads the proxy's AppArmor profile onto each Polaris node.
The chart schedules these pieces on nodes that carry the label key polaris.fr0ntierx.com/pool, whatever its value.
Two ways to protect a pod
- A
workloadsentry. The chart renders a Deployment, a Service on 443 and a ServiceAccount for your container, with the proxy already in the pod. Use this when Polaris owns the workload. - The webhook. Your own manifests or chart create the pod in a namespace you have labelled, and the webhook adds the proxy when the pod is created. Use this when the workload has its own release.
Both put the same sidecar in the pod, rendered from one template, so the two paths cannot drift apart. A pod the webhook protects does not get the hardening a workloads entry gets, such as the read-only root filesystem and dropped capabilities, so set those in your own manifests.
What the hardware proves
GKE confidential nodes run on AMD SEV-SNP. The proxy in each pod reads an SEV-SNP report straight from the node through the kernel's configfs interface, a path the chart calls gcp-sev-direct. The Policy Manager checks it against AMD's key chain, with no Google attestation service in the path. This is the direct hardware attestation path from the overview.
Google's own confidential computing token is not available from inside a pod on GKE with Workload Identity, so the cloud-attested path does not apply here.
The report's launch measurement covers the node's firmware and its number of vCPUs. Everything after boot, the node's operating system, Kubernetes and your containers, is covered by Polaris's other layers. See Attestation & Trust Model.
GKE's node images measure nothing in user space with IMA, so the chart turns off the Sentinel's IMA reader on GKE and the boot-measurement check with it. Image integrity still compares container digests, and the runtime and kernel layers come from the Sentinel's eBPF programs.
How the sidecars recognise the Policy Manager
Each proxy checks that the Policy Manager runs in an SEV-SNP guest before it trusts any verdict. That check shows a genuine confidential VM, not your Policy Manager.
You can pin the Policy Manager's launch measurement in proxy.policyManagerPin, and a sidecar with a pin refuses a Policy Manager with any other measurement. The measurement covers the node's firmware and its number of vCPUs, while the operating system, Kubernetes and the Policy Manager's own software are loaded later and fall outside it. A matching measurement therefore means "an SEV-SNP guest of this shape, on this Google firmware", which is true of every SEV-SNP node of that machine type in every project. The pin narrows which guests a sidecar accepts. It does not show that the guest runs your Policy Manager.
Without a pin, each sidecar trusts the first SEV-SNP Policy Manager that answers, and learns again whenever its pod restarts. Nobody knows the measurement before the Policy Manager first runs, so you install, read the measurement with scripts/pm-snp-pin.sh, and upgrade with the pin. See Pin the Policy Manager's measurement.
Because the measurement covers the node's firmware and vCPU count, every node the Policy Manager can land on has to give the same value. Keep one machine type in the pool the Policy Manager runs in.
How your key is gated
Your data is encrypted under a Cloud KMS key in your project. Each protected workload has a Kubernetes service account bound through Workload Identity to a Google service account, and that Google service account is the identity the Policy Manager grants to.
While a workload passes, the Policy Manager keeps two roles for its Google service account on your key: roles/cloudkms.cryptoKeyEncrypterDecrypter and roles/cloudkms.publicKeyViewer. When a layer you enforce fails, it removes them. The grant lasts 120 seconds and is renewed on each passing report, so a workload that goes quiet loses the key once the grant lapses.
The grant is an IAM binding on the Google service account, and every pod running under that account shares it. So a failure in one pod removes the key from every pod on that account, and the Policy Manager locks the account until the pod that failed passes again or you clear the lock. See Diagnose a withdrawal of access.
The Policy Manager's own Google service account needs permission to read and change the IAM policy on your key, and nothing more on it.
How a workload uses the key
Unlike on AWS, the proxy serves no encrypt or decrypt routes on GKE. Your workload calls Cloud KMS itself, as its Google service account, with the credentials GKE's metadata server hands the pod under Workload Identity. The call works while the Policy Manager holds the binding and fails once it is removed. See Use the gated key from your workload.
This means your workload container can get credentials for its Google service account, and anything that can act as that account can use the key while the binding stands. The node's own service account never appears in a pod, which is a GKE default.
What your clients see when a check fails
The proxy holds workload traffic when a layer you enforce fails. Its readiness probe follows that state, so Kubernetes takes the pod out of its Service until the next passing report, and a new pod joins its Service only after its first passing verdict.
When the Policy Manager stops answering, each proxy keeps serving until five reports in a row fail, about two and a half minutes at the default 30-second interval, and then closes.
What differs from the VM deployments
- Nothing revokes while the Policy Manager is down. The grant lifetime and the dead man's switch both run inside the Policy Manager, and there is no separate retirer as on AWS. A binding it made stays on your key until it is back. The proxies do close their gates after five failed reports.
- The Policy Manager restarts more often. Node upgrades, drains and image changes move it. Its state sits on a persistent volume so it survives, and the Deployment uses
Recreate, so the Policy Manager is down while the volume moves. After a restart it holds no grants, and each workload gets its key back on its next passing report. - The node is shared with the cluster. The kubelet, containerd, the CNI and GKE's own agents run on every node, and the chart's baseline lists them in the Sentinel's host profile. A DaemonSet you add later shows up as unknown behavior until Polaris learns it.
- Cluster administrators are trusted. Anyone who can run
kubectl exec,kubectl debugorkubectl port-forwardinto a protected pod, or read its Secrets, can reach what the pod holds. Polaris detects exec and debug containers as unknown runtime behavior. It does not see Secret reads through the API server. The chart ships no RBAC that limits these, so keep them away from protected namespaces with your own roles. - Ports are closed by NetworkPolicy. Pod addresses route across the cluster, so the chart adds ingress policies that admit only 443 to protected pods and to the Policy Manager, and checks before it installs that the cluster enforces them. GKE Dataplane V2 does.
- Namespaces run at Pod Security
privileged. The proxy needs the configfs and securityfs host paths to attest, which thebaselinelevel forbids. - One node image per pool. The Policy Manager holds one baseline, and the chart's baseline fits Container-Optimized OS nodes only.
- No IMA on GKE's node images. See What the hardware proves.
- One protected pod per node. The Policy Manager names a node by its GCE instance, so a second protected pod on the same node collides with the first. The chart does not enforce this, so keep
replicasand your node count in step.
Learning, then enforcing
The chart's GKE baseline starts every layer at alert, which the Policy Manager treats as advise: it reports findings and leaves your key alone. Polaris then learns the healthy workload. Learning watches for at least 600 seconds. An idle workload teaches an idle profile, so the chart can drive traffic to your workloads while they learn, with soak.enabled.
The Policy Manager refuses to move a layer to enforce until it has been clean under advise for a day, so daily jobs have run at least once. You move layers through the Policy Manager's API, as in Move a layer to enforce.
The Policy Manager keeps what it learned and which layers enforce on its volume, and both survive a restart. Any change to the baseline the chart renders wins over that record: the learned allowlists go, the layers return to the baseline's settings, and learning starts again. The chart's baseline names your workloads' image digests, identities and ports and your cluster's address ranges, so changing any of those, or upgrading to a chart whose baseline differs, starts learning again. See Upgrade Polaris.