Kubernetes reference
Facts to look up while installing and running Polaris in a cluster. For why it works the way it does, see Polaris on Kubernetes.
Object names on this page assume the release is called polaris and installed into polaris-system. With another release name, polaris becomes <release>, or <release>-polaris when the release name does not contain polaris.
What Polaris offers on Kubernetes today
| Capability | In a cluster |
|---|---|
| GKE Standard with AMD SEV-SNP confidential nodes on Container-Optimized OS | Available |
| A baseline shipped with the chart for those nodes | Available, as gke-cos |
| Attestation of image, runtime and kernel, with key access withdrawn when a layer you enforce fails | Available |
| Protecting pods from your own manifests through the webhook | Available |
| Pinning the Policy Manager's launch measurement | Available |
| Wrapping the Policy Manager's issuer keys with Cloud KMS | Available on GCP |
| Ending key access while the Policy Manager is down | Not available. Bindings stay until it is back |
Removing the Policy Manager's bindings on your key at helm uninstall | Available on GKE, for workloads entries. Pods the webhook protects are not covered |
| Encrypt and decrypt routes on the proxy for the workload | Not available on GKE. The workload calls Cloud KMS itself |
| IMA measurements of what runs on the node | Not available on GKE's node images |
| A baseline for GKE Ubuntu nodes | Not available. Bring your own |
| GKE with Intel TDX, AKS, EKS, OKE | Not yet measured |
| GKE Autopilot | Not supported |
| A load balancer or Ingress in front of workloads or the Policy Manager | Not created by the chart |
| Enforcing one protected pod per node | Not enforced by the chart |
RBAC that keeps exec and port-forward out of protected namespaces | Not shipped. Write your own |
| Egress NetworkPolicies | Not shipped |
Pod Security baseline for protected namespaces | Not available. They run at privileged |
Cluster requirements
| Requirement | Why |
|---|---|
| Kubernetes 1.30 or newer | The sidecar's AppArmor field is pruned without an error on 1.29, so the chart refuses older versions |
| GKE Standard, not Autopilot | The Sentinel needs host PID, host paths and capabilities |
| A node pool with confidential nodes of type SEV-SNP | The proxy reads its report from the node |
| Container-Optimized OS with containerd on the Polaris pool | The gke-cos baseline was learned on it |
| One node image and one machine type across the Polaris pool | The Policy Manager holds one baseline, and a pinned measurement covers firmware and vCPU count |
The node label key polaris.fr0ntierx.com/pool on Polaris nodes | Every Polaris component requires it. The value is free |
Workload Identity, with the node metadata server set to GKE_METADATA | Pods act as their Google service accounts |
| A CNI that enforces NetworkPolicy, such as GKE Dataplane V2 | The chart refuses to install without it |
| On a private cluster with the webhook on, a firewall rule from the control plane to the nodes on port 8443 | The API server calls the webhook there |
The chart's GKE values were measured on GKE Standard 1.35.8-gke.1225000, regular channel, with n2d-standard-4 nodes. Secure Boot is off on GKE node pools unless you create the pool with --shielded-secure-boot.
Values you must set
The render fails without these:
- The tokens:
tokens.existingSecret, or all four oftokens.management,tokens.readonly,tokens.workloadandtokens.sentinel. - A baseline:
policyManager.baseline.content,policyManager.baseline.existingConfigMaporpolicyManager.baseline.nodeImage. policyManager.trustDomainandpolicyManager.deploymentId.- With
policyManager.baseline.nodeImage: gke-cos: the fourpolicyManager.baseline.clusterranges,policyManager.service.clusterIP, and at least oneworkloadsentry withserviceAccount.cloudIdentity. name,imageandporton eachworkloadsentry.policyManager.issuerKeyKmsKeywhenpolicyManager.issuerKeyProtectioniskms.- With
policyManager.enabled: false,proxy.policyManagerUrl.
These render without an error, but the stack does not work without them on GKE:
values/gke-sev-snp.yaml, passed before your own values file.policyManager.kms, the key the Policy Manager gates.- The Workload Identity annotations on the Policy Manager's and each workload's service account, and
serviceAccount.cloudIdentityon each workload.
values.schema.json refuses unknown keys at every level, so a misspelt key fails at helm install rather than being ignored.
Values keys
Tokens and images
| Key | Default | What it does |
|---|---|---|
tokens.existingSecret | empty | A Secret in the release namespace with the keys management, readonly, workload and sentinel. Use this outside throwaway installs |
tokens.management, tokens.readonly, tokens.workload, tokens.sentinel | empty | The four tokens as values. They end up in the Helm release record |
images.proxy, images.policyManager, images.sentinel, images.sentinelInvary, images.webhook | Released digests | Each is repository and digest. The digest must be sha256: and 64 hex characters. The defaults follow manifest/known-good-images.yaml |
imagePullSecrets | [] | Secrets in the release namespace for registries the node cannot pull from. GKE pulls from Artifact Registry with the node's service account and needs none |
Namespace, scheduling and devices
| Key | Default | What it does |
|---|---|---|
namespace.create | false | Renders a Namespace with Pod Security labels. Helm will not adopt a namespace it made with --create-namespace, so use this only for a release in another namespace |
namespace.name | polaris-system | The namespace namespace.create renders |
namespace.podSecurity.enforce, .audit, .warn | privileged | The Pod Security labels on that Namespace |
nodeCriticalQuota.create | true | A ResourceQuota that lets system-node-critical pods run outside kube-system, which GKE requires |
nodeCriticalQuota.pods | 30 | Pods the quota admits, sized for three per node |
priorityClasses.create | true | Renders the two PriorityClasses |
priorityClasses.policyManager.value | 900000 | Priority of the Policy Manager pod |
priorityClasses.protected.value | 800000 | Priority of protected pods |
nodePool.labelKey | polaris.fr0ntierx.com/pool | The node label key every Polaris pod requires |
tee.devices | amd.com/sev-snp and google.com/cc, 1 each | Extended resources every proxy and Sentinel container requests. A values file replaces the whole list |
tee.configfs | true | Mounts /sys/kernel/config from the node, where the proxy asks for SEV-SNP reports |
tee.securityfs | true | Mounts /sys/kernel/security read-only, for the IMA log |
devicePlugin.gke.install | false, true in the GKE file | Installs google/cc-device-plugin |
devicePlugin.gke.image, .resources | Pinned digest | The plugin's image and resources |
devicePlugin.gke.nodeSelector | {} | The plugin runs on every node unless you set this |
AppArmor
| Key | Default | What it does |
|---|---|---|
apparmor.profile | empty, polaris-proxy in the GKE file | Empty runs the proxy sidecar Unconfined. A name runs it under that Localhost profile |
apparmor.loader.enabled | false, true in the GKE file | A DaemonSet that loads the chart's profile into each Polaris node with the node's own apparmor_parser |
apparmor.loader.external | false | Set when the profile reaches the nodes some other way. A named profile with neither this nor the loader fails the render |
apparmor.loader.image, .resources, .nodeSelector, .tolerations | busybox, small, empty, tolerate all | The loader's own settings |
Proxy sidecar
| Key | Default | What it does |
|---|---|---|
proxy.ratlsPlatform | gcp-sev-direct | What the workload sidecars attest as |
proxy.pmRatlsPlatform | empty | What the Policy Manager's proxy attests as and the sidecars pin it by. Empty means ratlsPlatform. With ratls trust it must be gcp-sev, gcp-sev-direct, aws-sev, svsm or snp-direct |
proxy.policyManagerTrust | ratls | ratls pins the Policy Manager's TEE identity. caCert is for Azure and Oracle |
proxy.policyManagerPin.measurementHex | empty | The Policy Manager's launch measurement, 96 hex characters. Set, every sidecar refuses a Policy Manager with any other. It covers the node's firmware and vCPU count, so it matches any SEV-SNP guest of that shape, not only your Policy Manager |
proxy.policyManagerPin.requiredVmpl | 0 | The VMPL the Policy Manager must report, 0 to 3 |
proxy.policyManagerCaCertSecret | empty | Only with caCert trust |
proxy.policyManagerUrl | empty | Where sidecars post evidence. Empty means the chart's Policy Manager Service |
proxy.reportIntervalSeconds | 30 | How often each sidecar reports. Must equal sentinel.reportIntervalSeconds |
proxy.enforcementGracePeriodSeconds | 0 | Wait before the gate closes on a missing first verdict. The readiness probe already keeps the pod out of its Service until then |
proxy.websocket | true | Passes WebSocket traffic through |
proxy.logging | true | Request logging |
proxy.managementPassthrough | false | Serves the Policy Manager's API as /management/* on every workload sidecar and gives each sidecar the read-only and workload tokens. Off by default, since it puts two Policy Manager credentials into every protected pod |
proxy.resources.cpu, .memory | 500m, 512Mi | Applied as both request and limit |
proxy.readinessProbe.periodSeconds, .failureThreshold | 5, 3 | The probe on /polaris-container/ready |
proxy.extraEnv | [] | Extra environment entries for every sidecar |
Workloads
workloadDefaults holds the defaults, and each workloads entry may set any of the same keys.
| Key | Default | What it does |
|---|---|---|
workloads[].name | required | Name of the Deployment, Service and ServiceAccount. Lowercase letters, digits and hyphens |
workloads[].image.repository, .digest | required | Your image, pinned by digest. A tag is refused |
workloads[].port | required | The port your container listens on. It cannot be the proxy's internal port |
replicas | 1 | Replicas of the Deployment |
containerName | client-workload | Your container's name. The baseline's image check is keyed on it |
proxyInternalPort | 3000 | The sidecar's own loopback port |
resources | 100m and 64Mi requested, 500m and 128Mi limit | Your container's resources |
capabilities | [] | Capabilities added back after all are dropped, without the CAP_ prefix |
writableDirs | /tmp, 64Mi | Paths your container may write to, each an emptyDir with a sizeLimit. The root filesystem is read-only. An entry replaces the whole list |
envFromSecret | empty | A Secret in the release namespace loaded as your container's environment |
serviceAccount.annotations | {} | Annotations on the ServiceAccount, such as iam.gke.io/gcp-service-account |
serviceAccount.cloudIdentity | empty | The Google service account the Policy Manager grants to. The gke-cos baseline lists it as allowed |
nodeSelector, tolerations | empty, a toleration for the pool taint in the GKE file | Scheduling on top of the pool label |
Soak traffic
| Key | Default | What it does |
|---|---|---|
soak.enabled | false | A CronJob that calls every workloads entry through its Service, so learning sees a working system |
soak.schedule | every minute | Cron schedule |
soak.image, soak.resources | curlimages/curl by digest, small | The CronJob's settings |
Webhook
| Key | Default | What it does |
|---|---|---|
webhook.enabled | false | Renders the sidecar webhook |
webhook.namespaceLabel.key, .value | polaris.fr0ntierx.com/webhook, enabled | The label that opts a namespace in |
webhook.replicas | 2 | Replicas, with a PodDisruptionBudget of one when more than one |
webhook.port | 8443 | The port the webhook pods listen on |
webhook.failurePolicy | Fail | Fail refuses a labelled pod while the webhook is down. Ignore admits it without a proxy |
webhook.timeoutSeconds | 10 | 1 to 30 |
webhook.certManager.enabled | false | cert-manager issues and renews the serving certificate |
webhook.certManager.issuerRef | {} | name, kind (Issuer or ClusterIssuer) and group. Empty uses a self-signed Issuer the chart makes |
webhook.certificate.validityDays | 3650 | Validity of a certificate the chart or cert-manager makes |
webhook.certificate.existingSecret | empty | A kubernetes.io/tls Secret you made, for GitOps without cert-manager |
webhook.certificate.caCert | empty | The PEM CA that signed it. Required with existingSecret |
webhook.resources, .nodeSelector, .tolerations | small, empty, empty | The webhook's own scheduling. It needs no confidential node |
Policy Manager
| Key | Default | What it does |
|---|---|---|
policyManager.enabled | true | Runs the Policy Manager in the cluster |
policyManager.logLevel | info | trace to panic |
policyManager.cloudProvider | gcp | The cloud whose attestation and KMS it uses |
policyManager.yangPush | true | The RESTCONF event streams |
policyManager.autolearn.enabled | true | Learning |
policyManager.autolearn.minObservationSeconds | 600 | The shortest a learning session watches |
policyManager.promotionWindowSeconds | 86400 | How long a layer must be clean under advise before it may enforce. 0 turns the check off |
policyManager.grantTtlSeconds | 120 | How long a grant lasts without a passing report |
policyManager.deadmanIntervalSeconds | 300 | How long a node may go silent before it counts as gone, and how long the Policy Manager waits after a restart before that check runs |
policyManager.oidcIssuer | Google's confidential computing issuer | Set on GCP |
policyManager.kms | {} | The gated key as environment variables. On GKE: PM_KMS_PROJECT_ID, PM_KMS_LOCATION, PM_KMS_KEY_RING and PM_KMS_KEY_ID |
policyManager.baseline.content | empty | A baseline file as text, usually with --set-file |
policyManager.baseline.existingConfigMap | empty | A ConfigMap you made with the key polaris-baseline.yaml |
policyManager.baseline.nodeImage | empty, gke-cos in the GKE file | The baseline the chart ships for that node image, used when the two above are empty |
policyManager.baseline.cluster.podRange, .serviceRange, .nodeRange | empty | The cluster's IPv4 ranges, required with gke-cos |
policyManager.baseline.cluster.controlPlaneAddress | empty | The control plane's private address, required with gke-cos |
policyManager.baseline.restrictedGoogleApis | false | For a VPC where *.googleapis.com resolves to restricted.googleapis.com. Limits the node's 443 to that range and GKE's. Not yet run on a node |
policyManager.trustDomain | empty | Names the issuer keys and node registry. Lowercase letters, digits, ., - and _. Never change it |
policyManager.deploymentId | empty | A UUID, chosen once. A Policy Manager that finds a registry under another ID refuses to start |
policyManager.service.clusterIP | empty | A fixed address in the service range for the Policy Manager's Service. Required with gke-cos. It cannot change once the Service exists |
policyManager.persistence.size | 1Gi | The state volume |
policyManager.persistence.storageClass | cluster default | Use a regional class when the Policy Manager's pool spans zones |
policyManager.persistence.existingClaim | empty | A claim you made. The chart then makes none |
policyManager.issuerKeyProtection | file | kms wraps the issuer keys with Cloud KMS. file keeps them on the volume and logs that it is not for production. vtpm and snp-derived tie the keys to one node, which a pod that moves cannot use |
policyManager.issuerKeyKmsKey | empty | The wrap key, as projects/<p>/locations/<l>/keyRings/<r>/cryptoKeys/<k>. Not the gated key |
policyManager.issuerKeyRewrapFrom | empty | Set to file for one upgrade to move existing keys to kms |
policyManager.signingKey.existingSecret | empty | A Secret with signing-key.pem, a P-256 key that seeds the first appraisal key of a new key set |
policyManager.license.existingSecret | empty | A Secret with license.jws. A renewal written into it reaches the Policy Manager within a minute |
policyManager.serviceAccount.annotations | {} | On GKE, iam.gke.io/gcp-service-account with the Policy Manager's Google service account |
policyManager.resources | 250m and 256Mi requested, 1 CPU and 512Mi limit | The Policy Manager container |
policyManager.proxy.resources | 250m, 256Mi | Its proxy, as request and limit |
policyManager.nodeSelector, .tolerations | empty, a toleration for the pool taint in the GKE file | Scheduling on top of the pool label |
policyManager.extraEnv | [] | Extra environment entries |
policyManager.releaseGrantsOnUninstall.enabled | true | On helm uninstall, a Job removes the two roles the Policy Manager binds on your key from each workloads[].serviceAccount.cloudIdentity. Renders only with cloudProvider: gcp, kms.PM_KMS_KEY_ID set and at least one cloudIdentity, and then needs PM_KMS_PROJECT_ID, PM_KMS_LOCATION and PM_KMS_KEY_RING too. Pods the webhook protects are not covered, and --no-hooks skips it |
policyManager.releaseGrantsOnUninstall.image | Google's google-cloud-cli slim image from gcr.io/google.com/cloudsdktool, by digest | The Job's image, as repository and digest |
policyManager.releaseGrantsOnUninstall.resources | 50m and 128Mi requested, 500m and 256Mi limit | The Job's resources |
Network policy
| Key | Default | What it does |
|---|---|---|
networkPolicy.enabled | true | Renders the ingress policies |
networkPolicy.defaultDeny | true | Denies ingress to every pod in the release namespace that no other policy admits |
networkPolicy.ingressFrom | [] | NetworkPolicy peers allowed to reach 443 on protected pods and the Policy Manager. Empty means anywhere |
networkPolicy.protectedNamespaces | [] | The namespaces you labelled for the webhook. Each gets the 443-only policy for protected pods. They must exist before the install |
networkPolicy.enforcementCheck.enabled | true | The install-time check that the cluster enforces NetworkPolicy. Turning it off is not supported |
networkPolicy.enforcementCheck.image, .timeoutSeconds | busybox, 120 | The check's settings |
Sentinel
| Key | Default | What it does |
|---|---|---|
sentinel.enabled | true | Runs the Sentinel DaemonSet |
sentinel.port | 54323 | Its port, also published on the node |
sentinel.integrity | true | Kernel integrity checks |
sentinel.imaDisabled | false, true in the GKE file | Turns off the IMA reader. GKE's images measure nothing in user space |
sentinel.accumulator | true | The evidence accumulator |
sentinel.persistKeyEpoch | true | Keeps the accumulator's epoch on the node, so a Sentinel rollout does not fail the runtime layer |
sentinel.pcrIndex | 15 | The TPM register the accumulator extends |
sentinel.reportIntervalSeconds | 30 | Must equal proxy.reportIntervalSeconds |
sentinel.resources.cpu, .memory | 1, 256Mi | As request and limit |
sentinel.readinessProbe | 5 s, 3 failures | The probe on /ready |
sentinel.nodeSelector, .tolerations | empty, tolerate all | Scheduling on top of the pool label |
sentinel.invary.* | off | The Invary kernel integrity sensor. values/invary.yaml turns it on |
Values files
| File | For | Status |
|---|---|---|
values/gke-sev-snp.yaml | GKE, SEV-SNP nodes on Container-Optimized OS | Measured |
values/gke-tdx.yaml | GKE, TDX nodes | Not measured |
values/aks-sev-snp.yaml | AKS, SEV-SNP | Not measured |
values/eks.yaml | EKS | Not measured |
values/oke.yaml | OKE | Not measured |
values/invary.yaml | The Invary Sentinel, on top of any of them |
values/gke-sev-snp.yaml sets the gcp-sev-direct platform, the two device plugin resources, the device plugin, the AppArmor profile and its loader, the Sentinel's IMA reader off, the gke-cos baseline, and a toleration for a polaris.fr0ntierx.com/pool taint on the Policy Manager and workloads. Pass it before your own file.
Labels and annotations
| Name | On | Set by | Meaning |
|---|---|---|---|
polaris.fr0ntierx.com/pool | Nodes | You | Polaris pods run only on nodes with this key |
polaris.fr0ntierx.com/webhook=enabled | Namespaces | You | Opts the namespace in to the webhook. The release namespace is never selected |
polaris.fr0ntierx.com/protected: "true" | Pods | The chart, or you | A protected pod. The webhook acts only on pods with it, and the Sentinel treats the pod's other containers as the workload |
polaris.fr0ntierx.com/protected: "false" | Pods | The chart, on the Policy Manager | A pod that runs the proxy with nothing behind it. The Sentinel reports every such pod, and the runtime layer fails on any the baseline does not expect. The Policy Manager expects its own pod |
polaris.fr0ntierx.com/workload-port | Pods | You | Which declared port the proxy forwards to, when the pod declares more than one |
polaris.fr0ntierx.com/cloud-identity | Pods | You | The Google service account the sidecar reports for a pod the webhook protects |
The protected label is set by whoever writes the pod and is not attested. The NetworkPolicies use it to narrow who can reach the Sentinel and the Policy Manager, and the tokens are what guard them.
What the chart creates
Always
- ResourceQuota
polaris-node-critical, forsystem-node-criticalpods. - PriorityClasses
polaris-policy-managerandpolaris-protected. They are cluster-scoped and named after the release. - Secret
polaris-tokens, unless you settokens.existingSecret.
Policy Manager
- ServiceAccount, Deployment and Service
polaris-policy-manager. The Deployment has one replica and theRecreatestrategy, and the Service exposes 443 only. - ConfigMap
polaris-policy-manager-baseline, unless you setbaseline.existingConfigMap. - PersistentVolumeClaim
polaris-policy-manager-state, ReadWriteOnce, unless you setpersistence.existingClaim. It is kept onhelm uninstall.
Sentinel
- ServiceAccount and DaemonSet
polaris-sentinel. - ClusterRole and ClusterRoleBinding
polaris-sentinel-pod-reader.
For each workloads entry
- ServiceAccount, Deployment and Service named after the entry. The Service exposes 443 only.
With the GKE values file
- DaemonSet
polaris-cc-device-plugin. - ConfigMap and DaemonSet
polaris-apparmor-loader.
With the webhook
- ServiceAccount, Deployment, Service and PodDisruptionBudget
polaris-webhook. - ConfigMap
polaris-webhook-sidecar, the sidecar the webhook injects. - MutatingWebhookConfiguration
polaris-webhook, cluster-scoped. - Secret
polaris-webhook-tls, or with cert-manager a Certificatepolaris-webhookand, without anissuerRef, an Issuerpolaris-webhook-selfsigned.
NetworkPolicies
polaris-default-deny-ingressin the release namespace.polaris-protected-https-onlyin the release namespace and in each ofnetworkPolicy.protectedNamespaces.polaris-policy-manager-https-only,polaris-sentinel-from-protectedand, with the webhook,polaris-webhook-from-apiserver.
At install and upgrade, then removed
- NetworkPolicy, Service and Job
polaris-network-policy-check, and Deploymentpolaris-network-policy-check-target. They stay behind when the check fails.
At uninstall, then removed
- Job
polaris-release-grants, withpolicyManager.releaseGrantsOnUninstall.enabledon GKE. It runs after the release's other objects are deleted, under a new ServiceAccountpolaris-policy-managerthat carries the Policy Manager's annotations, so Workload Identity gives it the Policy Manager's Google service account. Both are deleted when the Job succeeds, and stay behind when it fails.
Optional
- CronJob
polaris-soak, withsoak.enabled. - Namespace
namespace.name, withnamespace.create.
What runs where
| Pod | Runs as | Notable settings |
|---|---|---|
| Policy Manager | Its own ServiceAccount, on a Polaris node | Proxy sidecar on 443 with its internal port moved to 3001, Policy Manager on 3000, state volume at /var/lib/polaris-pm, labelled protected: "false" |
| Protected pod | Its own ServiceAccount, on a Polaris node | Proxy sidecar on 443, internal port on 127.0.0.1:3000. Your container with a read-only root filesystem, all capabilities dropped and no privilege escalation, when it comes from a workloads entry |
| Sentinel | polaris-sentinel, on every Polaris node | Host PID, the capabilities BPF, SYS_ADMIN, PERFMON, SYS_PTRACE and SYSLOG, AppArmor Unconfined, read-only root filesystem, host port 54323 |
| AppArmor loader | Default, on every Polaris node | Privileged, host PID |
| Device plugin | Default, on every node unless you set a selector | Privileged |
| Webhook | polaris-webhook, any node | Non-root, no service account token, read-only root filesystem |
The proxy sidecar runs under the polaris-proxy AppArmor profile in the GKE file, or Unconfined without it. Its requests equal its limits, so the kubelet kills your container before the proxy under memory pressure.
Kubernetes RBAC
The chart grants one permission in the cluster: get and list on pods, cluster-wide, to the Sentinel's ServiceAccount. The Sentinel uses it to tell the proxy, the workload and the node's own processes apart. Kubernetes cannot scope it to one node, so the Sentinel can read every pod spec in the cluster, including literal environment values. It cannot watch pods or read Secrets.
The webhook's ServiceAccount mounts no token and calls no Kubernetes API.
The person who installs the chart needs rights to create every object above, including the cluster-scoped PriorityClasses, ClusterRole, ClusterRoleBinding and MutatingWebhookConfiguration.
Google Cloud IAM
| Identity | Needs | On |
|---|---|---|
| The Policy Manager's Google service account | cloudkms.cryptoKeys.getIamPolicy and cloudkms.cryptoKeys.setIamPolicy, which the uninstall Job uses too | The gated key |
roles/cloudkms.cryptoKeyEncrypterDecrypter for its first start or a move to kms, then roles/cloudkms.cryptoKeyDecrypter | The wrap key, with issuerKeyProtection: kms | |
The Kubernetes ServiceAccount polaris-system/polaris-policy-manager | roles/iam.workloadIdentityUser | The Policy Manager's Google service account |
| Each workload's Kubernetes ServiceAccount | roles/iam.workloadIdentityUser | That workload's Google service account |
| Each workload's Google service account | roles/cloudkms.cryptoKeyEncrypterDecrypter and roles/cloudkms.publicKeyViewer, granted and removed by the Policy Manager | The gated key |
The member for a Workload Identity binding is serviceAccount:<project>.svc.id.goog[<namespace>/<ksa>]. Give the workload's Google service account no standing role on the gated key, or it keeps access when the Policy Manager removes its binding.
The Policy Manager removes an IAM binding per Google service account, not per pod. Every pod that runs as one account shares its binding, and a failure in one of them locks the account for all of them.
Anyone who can act as the Policy Manager's Google service account can unwrap its issuer keys under kms, and can change the IAM policy on your key.
Where a deployment connects
| From | To | For |
|---|---|---|
| Each proxy sidecar | The Policy Manager's Service on 443 | Evidence reports and verdicts, over attested TLS |
| Each proxy sidecar | The Sentinel on its node, port 54323 on the node's address | The node's runtime and kernel evidence, with the Sentinel token |
| Each proxy sidecar | Your container on 127.0.0.1:<port> | Forwarded requests |
| Each proxy sidecar | The GKE metadata server | Its identity |
| Your container | The GKE metadata server and Cloud KMS | Using the gated key |
| The Policy Manager | Cloud KMS | Reading and changing the IAM policy on your key, and unwrapping its issuer keys under kms |
| The Sentinel | The Kubernetes API server | The pod list for its node |
| The API server | The webhook on 8443 | Pod admission |
| Clients | Protected pods on 443 | Your workload, through the proxy |
NetworkPolicies
The chart writes ingress rules only. Egress is not limited.
| Policy | Selects | Admits |
|---|---|---|
| Default deny | Every pod in the release namespace | Nothing. Kubelet probes come from the node and still arrive |
| Protected, 443 only | Pods labelled protected: "true", in the release namespace and each listed namespace | 443 from networkPolicy.ingressFrom, or from anywhere when it is empty. In the release namespace the soak pod too, when soak.enabled is on |
| Policy Manager, 443 only | The Policy Manager pod | 443 from networkPolicy.ingressFrom plus protected pods in any namespace, or from anywhere when it is empty |
| Sentinel | Sentinel pods | Port 54323 from protected pods in any namespace |
| Webhook | Webhook pods | 8443 from anywhere, since the API server is not a pod |
In a namespace you listed for the webhook, the chart adds only the protected-pod policy. Another policy there that selects protected pods adds the ports it admits, since NetworkPolicies add up. A labelled namespace you did not list gets no policy, and its protected pods answer on every port they listen on.
The Policy Manager listens on 3000 on every interface. The NetworkPolicy is what keeps other pods off that port.
The gke-cos baseline
policyManager.baseline.nodeImage: gke-cos renders the baseline from the chart's template templates/policy-manager/_baseline-gke-cos.tpl.
| Part | What it holds |
|---|---|
| Enforcement | l2, l3 and l31 at alert, which the Policy Manager treats as advise. ima_boot off |
| Platform | The cloudIdentity of each workloads entry as an allowed service account |
| Containers | Each workloads entry's digest under its container name |
| IMA | Expected only when the Sentinel's IMA reader is on. The allowlist starts empty |
| Proxy profile | DNS to kube-dns, at the service range's base address plus 10, 443 to the Policy Manager's Service address, the Sentinel's port in the node range, and loopback to each workload port. Syscalls start at accept, accept4, connect, execve and openat, and the rest is learned |
| Workload profile | Inbound from loopback, the node range and 169.254.4.6, where kubelet's probes come from on Dataplane V2 from GKE 1.35.1-gke.1516000. Outbound is learned |
| Host profile | The binaries and connections of a GKE COS node, learned on a test node and pruned by hand, with the cluster's ranges filled in. 443 to any address, or only to the restricted Google ranges with restrictedGoogleApis |
| Kernel | Empty hashes and allow_unknown modules, so the kernel learner fills it |
It fails the render when a cluster range, the Policy Manager's Service address or every workload's cloudIdentity is missing. An empty list of allowed service accounts would skip that check.
Pods the webhook protects are not in this baseline. Their image digests and Google service accounts are not listed, so a cluster that runs them needs a baseline you write, passed with baseline.content or baseline.existingConfigMap.
Upgrade behaviour
| Change | What happens |
|---|---|
| The rendered baseline changes in any byte | The Policy Manager pod restarts, since it reads the baseline only at start. The new file wins over what it saved: learned allowlists and profiles go, layers return to the baseline's settings and learning starts again |
A workloads entry's image, port or cloudIdentity, a cluster range, service.clusterIP, sentinel.port or sentinel.imaDisabled | All of these are in the gke-cos baseline, so the same as above |
A chart version whose gke-cos template differs | The same as above |
| A ConfigMap you made yourself | Nothing until you restart the Policy Manager |
Proxy settings, such as proxy.policyManagerPin | The chart's workloads and the Policy Manager roll. Pods the webhook injected keep their old sidecar until you recreate them |
| The webhook's sidecar spec | The webhook pods roll and inject the new sidecar into pods created after that |
| The Policy Manager restarts | It is down while the old pod releases the volume. It then holds no grants and waits deadmanIntervalSeconds before its silence check runs. Each workload gets its key back on its next passing report |
trustDomain or deploymentId | The Policy Manager refuses to start on a registry stored under another ID |
Promotions you make through the API, and everything the Policy Manager learned, survive a restart as long as the baseline file is unchanged.
Policy Manager API routes
The Policy Manager answers on plain HTTP on port 3000 inside its pod, which you reach with kubectl port-forward. Its proxy also forwards /api/v1/* on 443.
| Route | Token | Returns or does |
|---|---|---|
GET /api/v1/status | Read-only or management | Every node's latest verdict, with its cvm_id |
GET /api/v1/status/<cvm_id> | Read-only or management | One node |
GET /api/v1/incidents/<cvm_id>/report | Read-only or management | The latest incident as plain text |
GET /api/v1/checks/<cvm_id>, GET /api/v1/history/<cvm_id> | Read-only or management | The latest checks, and past verdicts |
GET /api/v1/profiles/learn, /api/v1/ima/learn, /api/v1/kernel/learn | Read-only or management | Learning sessions and their state |
GET /api/v1/enforcement | Read-only or management | The current setting of each layer |
PUT /api/v1/enforcement | Management | Sets layers, for example {"l3": "enforce"}. Learned state is kept |
GET /api/v1/kms/locks | Read-only or management | Google service accounts locked by a failure |
DELETE /api/v1/kms/locks/<account> | Management | Lifts a lock |
GET /.well-known/jwks.json | None | The Policy Manager's signing keys. Its readiness probe |
The layers are l2, l3, l31, ima_boot, l3_syscalls, l3_binaries and l3_network, and each takes enforce, advise or off. An unset ima_boot follows l2, and unset l3_* parts follow l3.
Proxy routes
| Route | Where | Returns |
|---|---|---|
/polaris-container/health | 443 | The proxy is running |
/polaris-container/ready | 443 | 200 while the gate is open, 503 otherwise. The readiness probe |
/polaris-container/attestation-status | 443 | The latest verdict. With the management token it adds the reason and alerts. The read-only token works too only with proxy.managementPassthrough |