From 22b2944494c4ef9003d5f3d82eeebe744dd30bf4 Mon Sep 17 00:00:00 2001 From: stxkxs Date: Mon, 22 Jun 2026 16:19:10 -0700 Subject: [PATCH] feat(policies): verify-images Kyverno policy for factory-built images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The factory signs every image it ships — the operator and all four tenant release workflows run `cosign sign` (keyless OIDC) and `cosign attest` on each pushed image. Nothing on the cluster side ever checked those signatures, so a hand-pushed or tampered image was admitted identically to a signed, gate-approved one. This adds the admission-time check that closes that gap. ─────────────────────────── What changed ─────────────────────────── policies/kyverno/supply-chain/ (new policy group, pure Kustomize like the others) — a `verify-images` ClusterPolicy with a base + dev/staging/production overlays: - Scopes to imageReferences `ghcr.io/nanohype/*`. Images outside the org registry (cert-manager, kyverno, etc.) are not matched and pass untouched, so the blast radius is the factory's own images only. - Keyless Cosign attestor matching the release-workflow identity: issuer `https://token.actions.githubusercontent.com`, subject regex `github.com/nanohype//.github/workflows/release.ya?ml@refs/tags/*` (operator signs from release.yaml, tenants from release.yml; both on tag pushes), verified against public Rekor. - required: true, mutateDigest/verifyDigest: false — pure signature verification for the rollout, no tag-to-digest rewriting yet. - webhookTimeoutSeconds: 30 (registry + Rekor lookups exceed the 10s default). - Excludes kube-system/kube-public/kube-node-lease/kyverno from the match. applicationsets/kyverno-policies.yaml — adds the `kyverno-supply-chain` entry at sync-wave 22 (after best-practices at 21), same matrix generator and per-environment overlay path as the existing policy groups. ─────────────────────────── Rollout ─────────────────────────── Ships in Audit mode across all three environments: every unsigned ghcr.io/nanohype/* image is reported in PolicyReports without blocking admission. This is deliberate — it is the cluster's first image-verification policy, and going straight to Enforce before confirming the deployed image set verifies clean would break admission for any pre-signing or mutable-tag image. The overlays are structured so the flip to Enforce is a one-line value change per environment: staging first after a clean audit week, production after staging proves clean. Validated with `task validate` (yamllint + kustomize build, all environments). --- applicationsets/kyverno-policies.yaml | 3 + .../supply-chain/base/kustomization.yaml | 5 ++ .../supply-chain/base/verify-images.yaml | 58 +++++++++++++++++++ .../overlays/dev/kustomization.yaml | 14 +++++ .../overlays/production/kustomization.yaml | 16 +++++ .../overlays/staging/kustomization.yaml | 15 +++++ 6 files changed, 111 insertions(+) create mode 100644 policies/kyverno/supply-chain/base/kustomization.yaml create mode 100644 policies/kyverno/supply-chain/base/verify-images.yaml create mode 100644 policies/kyverno/supply-chain/overlays/dev/kustomization.yaml create mode 100644 policies/kyverno/supply-chain/overlays/production/kustomization.yaml create mode 100644 policies/kyverno/supply-chain/overlays/staging/kustomization.yaml diff --git a/applicationsets/kyverno-policies.yaml b/applicationsets/kyverno-policies.yaml index f0bf77a..b928623 100644 --- a/applicationsets/kyverno-policies.yaml +++ b/applicationsets/kyverno-policies.yaml @@ -23,6 +23,9 @@ spec: - appName: kyverno-best-practices path: policies/kyverno/best-practices syncWave: "21" + - appName: kyverno-supply-chain + path: policies/kyverno/supply-chain + syncWave: "22" template: metadata: name: '{{ .appName }}' diff --git a/policies/kyverno/supply-chain/base/kustomization.yaml b/policies/kyverno/supply-chain/base/kustomization.yaml new file mode 100644 index 0000000..a92f076 --- /dev/null +++ b/policies/kyverno/supply-chain/base/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - verify-images.yaml diff --git a/policies/kyverno/supply-chain/base/verify-images.yaml b/policies/kyverno/supply-chain/base/verify-images.yaml new file mode 100644 index 0000000..884870d --- /dev/null +++ b/policies/kyverno/supply-chain/base/verify-images.yaml @@ -0,0 +1,58 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: verify-images + annotations: + policies.kyverno.io/title: Verify Image Signatures + policies.kyverno.io/category: Supply Chain + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Factory-built images (ghcr.io/nanohype/*) must carry a valid keyless + Cosign signature produced by the org release workflow. The release + workflows sign with GitHub Actions OIDC (Fulcio + public Rekor), so the + attestor matches that workflow identity. An unsigned or foreign-signed + image — hand-pushed or tampered — fails verification. Images outside + ghcr.io/nanohype/* are not matched and pass unaffected. +spec: + # Audit during the signature rollout: report every unsigned nanohype image + # in PolicyReports without blocking admission. Overlays flip this to Enforce + # per environment once reports are clean (see overlays/). + validationFailureAction: Audit + # Image verification calls the registry + Rekor; the 10s default is tight. + webhookTimeoutSeconds: 30 + background: false + rules: + - name: verify-ghcr-nanohype + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kube-public + - kube-node-lease + - kyverno + verifyImages: + - imageReferences: + - "ghcr.io/nanohype/*" + # Pure signature verification for the rollout: report presence/validity + # without rewriting tags to digests or requiring digest references yet. + required: true + mutateDigest: false + verifyDigest: false + attestors: + - count: 1 + entries: + - keyless: + # GitHub Actions OIDC identity of the org release workflows. + # Operator signs from release.yaml, tenants from release.yml; + # both run on tag pushes (refs/tags/...). + issuer: "https://token.actions.githubusercontent.com" + subjectRegExp: '^https://github\.com/nanohype/[^/]+/\.github/workflows/release\.ya?ml@refs/tags/.+$' + rekor: + url: "https://rekor.sigstore.dev" diff --git a/policies/kyverno/supply-chain/overlays/dev/kustomization.yaml b/policies/kyverno/supply-chain/overlays/dev/kustomization.yaml new file mode 100644 index 0000000..9e633c7 --- /dev/null +++ b/policies/kyverno/supply-chain/overlays/dev/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +# Dev: Audit mode (report unsigned images, don't block) +patches: + - patch: |- + - op: replace + path: /spec/validationFailureAction + value: Audit + target: + kind: ClusterPolicy diff --git a/policies/kyverno/supply-chain/overlays/production/kustomization.yaml b/policies/kyverno/supply-chain/overlays/production/kustomization.yaml new file mode 100644 index 0000000..81b2209 --- /dev/null +++ b/policies/kyverno/supply-chain/overlays/production/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +# Production: Audit during the signature rollout. Flip value to Enforce once +# staging has run a clean audit week and every ghcr.io/nanohype/* image +# verifies — at which point an unsigned or tampered image is blocked at admission. +patches: + - patch: |- + - op: replace + path: /spec/validationFailureAction + value: Audit + target: + kind: ClusterPolicy diff --git a/policies/kyverno/supply-chain/overlays/staging/kustomization.yaml b/policies/kyverno/supply-chain/overlays/staging/kustomization.yaml new file mode 100644 index 0000000..bc77218 --- /dev/null +++ b/policies/kyverno/supply-chain/overlays/staging/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +# Staging: Audit during the signature rollout. Flip value to Enforce once the +# PolicyReports here show every ghcr.io/nanohype/* image verifying clean. +patches: + - patch: |- + - op: replace + path: /spec/validationFailureAction + value: Audit + target: + kind: ClusterPolicy