feat(security): cosign-verify the infrastructure Flux OCI artifact#2319
Conversation
Fixes #1570. Enables keyless cosign signature verification on the flux-system OCIRepository (the platform's own manifests source — the largest blast radius in the cluster), closing the supply-chain gap #1559 opened for apps but left on the infra source. Deep investigation dissolved the 'gated' status this issue was parked under: - The SIGN half (my 2026-06-26 triage's 'real remaining gap') is ALREADY done — the deploy-prod composite signs the manifests artifact keyless (push → sign → attest). The triage trusted the issue body's stale 'no signing step' and never re-checked the composite. - So only the VERIFY half remained. Per ksail#4987 the flux-system OCIRepository is KSail-generated/owned, so a hand-written spec.verify would lose the reconcile fight; KSail exposes spec.cluster.verify (FluxVerifySpec, v7.84.x — the pinned version) which it renders onto the generated OCIRepository. The signer identity was confirmed against the LIVE signature (cosign verify on ghcr.io/devantler-tech/platform/manifests:latest): signed by .github/workflows/ci.yaml (the merge-queue deploy-prod path) under GitHub's Fulcio OIDC issuer. cd.yaml (manual deploy, same composite/identity flow) is allowed too; @.+ keeps the matcher resilient to the per-run ref (merge_group signs under refs/heads/gh-readonly-queue/...). Validated: YAML parses; config schema-valid (provider enum + matchOIDCIdentity); ksail v7.84.1 (platform's exact pin) accepts the field; render is unit-tested upstream (ocirepository_verify_test.go). Upstream third-party Helm OCI charts remain unverifiable (can't sign upstream artifacts) — out of scope.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a ChangesCosign Keyless Verification for flux-system
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ksail.prod.yaml`:
- Around line 138-155: This change edits protected production cluster config in
ksail.prod.yaml, so do not commit the verification override directly there;
instead remove the change from the live prod manifest and route it through the
approved protected-config exception/change-control process, or move the rendered
verify configuration to the intended allowed source used by KSail’s
reconciliation (the verify block under the generated OCIRepository). Keep the
subject/issuer matching logic in the generated source rather than hand-editing
ksail.prod.yaml so the reconciler-owned config remains the single source of
truth.
- Line 155: The OIDC subject matcher is too broad because the `subject` pattern
in the production config accepts any ref for the `ci.yaml` and `cd.yaml`
workflows. Tighten the matcher in the OIDC policy to only allow the trusted
production publishing refs, using the relevant subject rule near the existing
`subject` entry and the workflow names `ci.yaml`/`cd.yaml`; keep `main` and the
merge-queue prefix if those are the only approved sources, and avoid a catch-all
`@.+` unless all refs are intended to publish `latest`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 63bf2f14-ea4a-4cc6-b53a-3ca24286acdc
📒 Files selected for processing (1)
ksail.prod.yaml
Address CodeRabbit review: `@.+` accepted a signature from ci.yaml/cd.yaml on any ref. Constrain to the two production deploy paths confirmed against the live signature: ci.yaml on the merge-queue ref (refs/heads/gh-readonly-queue/main/<pr>) and cd.yaml on refs/heads/main. Tightens the trust surface on a high-blast-radius source without changing legitimate signers (re-verified the live artifact still passes).
|
🎉 This PR is included in version 1.84.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Closes #1570 — enables keyless cosign signature verification on the
flux-systemOCIRepository(the platform's own manifests source: the single largest blast radius in the cluster — it defines every controller, policy, and gateway). Extends the supply-chain verification #1559 added for apps to the infrastructure source.This is the issue I'd been self-blocking on — deep investigation dissolved the gate
My 2026-06-26 triage parked #1570 'awaiting maintainer direction' and called signing 'the real remaining gap'. Investigating live proved that wrong:
deploy-prodcomposite already signs the manifests artifact keyless (push → sign → attest;cosign sign --yes --recursiveon the digest, plus SBOM attestation). My triage trusted the issue body's stale 'no signing step' and never re-checked the composite.flux-systemOCIRepositoryis KSail-generated and continuously reconciled, so a hand-writtenspec.verifywould lose the reconcile fight. KSail exposesspec.cluster.verify(FluxVerifySpec, present in v7.84.x — platform's exact pin) which it renders onto the generatedOCIRepository. This PR sets it.The matcher is grounded in the real signature, not a guess
This is high blast radius — a wrong identity makes Flux reject the infra artifact and halt all reconciliation — so I read the live signature rather than guessing.
cosign verify ghcr.io/devantler-tech/platform/manifests:latestconfirmed the artifact is signed by.github/workflows/ci.yaml(the merge-queuedeploy-prodpath) under issuerhttps://token.actions.githubusercontent.com—ci.yamlmatches,cd.yamldoes not (no manual deploy has signed yet). The matcher allows bothci.yamlandcd.yaml(same composite/identity flow, so a future manual deploy isn't rejected), and@.+keeps it resilient to the per-run ref (merge_groupsigns underrefs/heads/gh-readonly-queue/...).Validation
ksail.prod.yamlparses (yq); config is schema-valid against ksail's schema (providerenum +matchOIDCIdentityshape, no extra keys); ksail v7.84.1 (platform's exact pin) accepts the field; the render is unit-tested upstream (ocirepository_verify_test.go).Blast radius — why it's a draft
Verification can't be fully exercised without a deploy. It's left as a draft so you can promote it when you're ready to watch the first merge-queue
deploy-prodreconcile with verification on. If anything were wrong with the identity, the symptom would be theflux-systemOCIRepositoryfailing to verify — recoverable by reverting this one-field change.Out of scope: upstream third-party Helm OCI charts (hcloud-csi, longhorn, …) remain unverified — we can't sign upstream artifacts; that's a separate concern, not part of this issue's 'our own infra artifact' fix.