GKE Binary Authorization require attestation and a Rust image census
The cluster flag selects the project policy. The Pod spec only stores an image string.
<!-- hal:authoritative:yaml -->
The cluster flag selects the project policy. The policy names the attestor. The Pod spec only stores an image string.
§I. Frame
K8s day 73 still ships Ops, Dev, and Cert together. The Ops-plus-Cert-only clock starts Sunday.
09-12 already put Pod Security Admission labels on an AKS namespace. 09-27 was GKE surge and PodDisruptionBudgets. 09-30 was EKS Fargate profile selectors. The 09-18 Gateway lesson named Binary Authorization as a later GKE fire. That later fire is this one.
The problem for today: point one Standard cluster at the project Binary Authorization policy, require an existing attestor on the default rule, and census image strings on Pods. A digest in the spec is not an attestation.
§II. Three owners
- Cluster switch.
google_container_cluster.binary_authorization.evaluation_mode.PROJECT_SINGLETON_POLICY_ENFORCEmakes that cluster use the project singleton policy.DISABLEDdoes not. The boolenabledis deprecated. The Terraform resource documents only those two strings.gcloud betaalso accepts policy-binding modes. Do not write those strings into this block.
- Project policy.
google_binary_authorization_policyis one resource for the project.default_admission_rule.evaluation_modeisALWAYS_ALLOW,ALWAYS_DENY, orREQUIRE_ATTESTATION.REQUIRE_ATTESTATIONrequires a non-emptyrequire_attestations_by. The attestor must already exist, and the caller changing the policy must be able to read it.enforcement_modeis a second field.ENFORCED_BLOCK_AND_AUDIT_LOGrejects the Pod and writes Cloud Audit Logs.DRYRUN_AUDIT_LOG_ONLYadmits the Pod and still writes the violation.
- Exemptions.
global_policy_evaluation_mode = "ENABLE"turns on Google's system-image evaluation, so GKE-managed images are not held to your attestor.admission_whitelist_patterns.name_patternis your own name list. A namespace label does not exempt an image. That label is the 09-12 PSA mechanism, and this policy does not read it.
cluster_admission_rules overrides the default for one key shaped like us-central1-a.payments. No matching key means the default rule applies.
There is one Binary Authorization policy per project. A second google_binary_authorization_policy resource does not create a second policy. Import the existing one if Terraform does not already own it.
Flip the cluster only after you have read the live default rule. PROJECT_SINGLETON_POLICY_ENFORCE plus ALWAYS_DENY rejects every image that is not exempt, including ones you meant to attest later. PROJECT_SINGLETON_POLICY_ENFORCE plus ALWAYS_ALLOW admits everything and teaches you nothing. The useful pair is REQUIRE_ATTESTATION with the attestor list filled in, then the cluster flag. Dry-run (DRYRUN_AUDIT_LOG_ONLY) is the safe first enforcement value while you watch Cloud Audit Logs for misses.
A rejected create comes back from the API server as a forbidden Pod. The audit log is the record of which rule and which attestor missed. This lesson does not paste a kubectl error string, because no cluster was queried.
§III. The Terraform
Schema-faithful to current hashicorp/google docs. Not applied, and not terraform validated this fire.
resource "google_container_cluster" "payments" {
name = "payments"
location = "us-central1-a"
initial_node_count = 1
binary_authorization {
evaluation_mode = "PROJECT_SINGLETON_POLICY_ENFORCE"
}
}
resource "google_binary_authorization_policy" "project" {
global_policy_evaluation_mode = "ENABLE"
admission_whitelist_patterns {
name_pattern = "us-central1-docker.pkg.dev/payments-prod/break-glass/*"
}
default_admission_rule {
evaluation_mode = "REQUIRE_ATTESTATION"
enforcement_mode = "ENFORCED_BLOCK_AND_AUDIT_LOG"
require_attestations_by = [var.build_attestor_name]
}
}
var.build_attestor_name is the attestor resource name. Use projects/PROJECT/attestors/NAME when the attestor lives in another project. This lesson does not create the Container Analysis note or the Cloud KMS key. Those feed the attestor. They are not the admission rule.
A dry-run rollout keeps REQUIRE_ATTESTATION and sets enforcement_mode to DRYRUN_AUDIT_LOG_ONLY. That change does not add or remove attestors. It changes whether a miss blocks.
§IV. The Rust census
Admission evaluates the digest resolved at that moment. The Pod spec is a weaker record: a tag can move after an attestor signed the old digest. The census classifies the string. It does not call Container Analysis, so it never prints "attested".
The match guard runs first. A reference containing @sha256: is DigestPinned, including repo/app:1.2@sha256:dead. Otherwise take the name before @, then the tag after the last slash. A colon in front of that slash is a registry port (localhost:5000/app), not a tag. No tag, or the tag latest, is ImplicitLatest. Any other tag is MovingTag. Empty or absent is Missing.
Print ATTENTION for MovingTag, ImplicitLatest, and Missing. DigestPinned means the spec names a digest. It does not mean an attestor signed it.
Full script: gke-binauthz-image-census.rs (nightly -Zscript). Nix wrapper: gke-binauthz-image-census.nix (writeBashBin). The wrapper was not built this fire.
Checked on the lab Mac. cargo check of a scratch bin crate (script body, same deps, stable rustc 1.99.0) finished with exit 0. Resolved kube 2.0.1, k8s-openapi 0.26.1 feature v1_34, tokio 1.53.2. Not run against a cluster.
Illustrative cluster output (no kubeconfig used):
payments/api image=us-central1-docker.pkg.dev/payments-prod/app/api@sha256:abc shape=DigestPinned
payments/api image=us-central1-docker.pkg.dev/payments-prod/app/api:1.4.2 shape=MovingTag ATTENTION
summary containers=2 attention=1
Real cargo run --offline --quiet of the same classifier on the lab Mac (no cluster):
None -> Missing
Some("gcr.io/payments/api@sha256:abc") -> DigestPinned
Some("gcr.io/payments/api:1.4.2") -> MovingTag
Some("gcr.io/payments/api:latest") -> ImplicitLatest
Some("gcr.io/payments/api") -> ImplicitLatest
Some("localhost:5000/payments/api") -> ImplicitLatest
Some("localhost:5000/payments/api:1") -> MovingTag
Some("gcr.io/payments/api:1.2@sha256:dead") -> DigestPinned
§V. How to run
cargo +nightly -Zscript ./gke-binauthz-image-census.rs
Point kubeconfig at the cluster you mean to read. The script lists every namespace with Api::all and prints one line per container. It does not delete or patch Pods.
§VI. Close
Three owners: the cluster evaluation mode, the project rule (what to check, then whether a miss blocks), and the exemption lists. The Rust census reads image-reference shape from Pod specs. It does not prove an attestation.
Paired Dev: the same classifier written as match and match guards, inside the Patterns chapter already shipped. Paired Cert: enforced block versus dry-run, and why a registry-prefix webhook is a different control.
Related
- Tome: CKS study guide ch06 image-validation webhook README (prefix allowlist) (referenced, not this control)
- Tome: TRPL 19.3 match guards (grounded-in)
- Gap: no Binary Authorization tome in the vault. Field names are from the Google provider docs and the policy YAML reference.
- Web:
google_binary_authorization_policy,google_container_cluster, policy YAML reference, creating a cluster