Skip to content

fix(security): move Flux cosign verify to the path KSail reads - #2919

Merged
devantler merged 1 commit into
mainfrom
claude/flux-verify-config-path-2627
Aug 3, 2026
Merged

fix(security): move Flux cosign verify to the path KSail reads#2919
devantler merged 1 commit into
mainfrom
claude/flux-verify-config-path-2627

Conversation

@devantler

@devantler devantler commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

Why

Production has been pulling its infrastructure manifests with signature verification switched off, and nothing reported it. The cosign policy was written and looks correct — it just sits under a key KSail does not read, so it was silently discarded. Confirmed on the live cluster today: the flux-system source tracking the mutable latest tag has no verification attached.

What

Moves the cosign block to the key KSail actually reads. The policy itself is untouched — same provider, same three trusted signers, same wording; only its location changes.

Operational note — this changes production behaviour. Once merged and reconciled, Flux starts enforcing signatures on the infrastructure source. That is the intended effect, but it is a real switch: if the published artifact does not match one of the three trusted signers, Flux stops applying new manifests (it keeps running the last good state, and reverting this restores the old behaviour).

Staying a draft until one thing is confirmed. I could not independently re-verify the live artifact's signature in this run — cosign is not installed on the agent host — so I have not proven the artifact will pass. That check is the promotion gate, not the review.

This delivers #2850. The rest of parent #2627 — publishing latest only after signing and attesting, and the DR rebuild's permissions — is tracked in its other children and untouched here.

Fixes #2850
Part of #2627

The cosign keyless verify block sat at spec.cluster.verify. KSail reads it at
spec.workload.flux.verify (FluxVerifySpec is reachable only via
WorkloadSpec.Flux.Verify; ClusterSpec has no verify field), so the block was an
unknown key and was silently dropped. The generated flux-system OCIRepository
therefore carries no spec.verify, and production pulls the manifests artifact
from a mutable `latest` tag with signature verification disabled.

Moves the block verbatim — provider, all three matchOIDCIdentity entries, and
their rationale — to spec.workload.flux.verify. Content is unchanged; only the
location moves.

Part of #2627
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Evidence

1. The key is unreachable. FluxVerifySpec is referenced by exactly one field in the KSail config API:

  • pkg/apis/cluster/v1alpha1/types.go:287Verify FluxVerifySpec in FluxConfig
  • types.go:207Flux FluxConfig \json:"flux"` in **WorkloadSpec`**
  • types.go:63Workload WorkloadSpec \json:"workload"``

⇒ the only path is spec.workload.flux.verify. ClusterSpec has no verify field — its keys are distributionConfig, connection, distribution, provider, cni, csi, cdi, metricsServer, loadBalancer, certManager, imageVerification, policyEngine, localRegistry, gitOpsEngine, sops, nodeAutoscaling, autoscaler, importImages, controlPlanes, workers, kubernetesVersion, oidc, vanilla, talos, eks. It does have imageVerification, an adjacent and easily-confused control, which is the likely origin of the mistake.

2. The consequence is live (kubectl --context admin@prod, 2026-08-03):

NAME          URL                                               TAG      VERIFY
flux-system   oci://ghcr.io/devantler-tech/platform/manifests   latest   <none>

3. The move is content-preserving. Comparing the old block against the new one as parsed data (yq -o=json) gives a byte-identical result, and the identity count is 3 before and after. Control: injecting provider: notation into the comparison copy makes the diff fire, so the check discriminates rather than passing vacuously.

4. Validation. ksail --config ksail.prod.yaml workload validate556 files validated, exit 0 (ksail 7.178.9). The prod overlay also builds under kubectl kustomize.

What is NOT proven

That the published artifact will actually pass these three matchers at pull time. cosign is not installed on the agent host, so I could not re-verify the live signature. The in-repo comment records that the identity and ref were confirmed against the live signature previously, but that is a prior claim, not evidence I reproduced here. Until it is re-confirmed, enabling enforcement is an unverified step — which is why this stays a draft.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Ablation — why this went unnoticed for three weeks

I ran the repo's own validation against both configs, changing only the location of the verify block:

Arm config result
A spec.workload.flux.verify (this PR) 556 files validated, exit 0
B spec.cluster.verify (main today) 556 files validated, exit 0

Identical, and arm B emits no unknown-key or ignored-field warning — the only warnings in either run are unrelated GHCR_* substitution notices.

So ksail workload validate cannot distinguish the broken config from the correct one. The pre-merge gate this repo relies on is structurally blind to the defect, which is why a policy that reads correctly and validates green has been inert in production since it was written.

That makes a regression guard genuinely load-bearing rather than nice-to-have — a green validation run is not evidence that verification is configured. Filed separately; it wants a check that asserts the rendered/effective state, not one that greps the config, since a text check would be defeated by exactly the kind of restructuring that caused this.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

⚠️MegaLinter analysis: Success with warnings

⚠️ BASH / bash-exec - 5 errors
Results of bash-exec linter (version 5.3.9)
See documentation on https://megalinter.io/9.6.0/descriptors/bash_bash_exec/
-----------------------------------------------

✅ [SUCCESS] scripts/dr-rebuild-supersession-guard.sh
❌ [ERROR] scripts/ghcr-auth-lib.sh
    Error: File:[scripts/ghcr-auth-lib.sh] is not executable

✅ [SUCCESS] scripts/guard-cilium-homogeneous-device-rollout.sh
✅ [SUCCESS] scripts/megalinter-scan-counts.sh
✅ [SUCCESS] scripts/normalize-sarif-paths.sh
❌ [ERROR] scripts/refresh-flux-ghcr-auth-safety.sh
    Error: File:[scripts/refresh-flux-ghcr-auth-safety.sh] is not executable

✅ [SUCCESS] scripts/refresh-flux-ghcr-auth.sh
✅ [SUCCESS] scripts/run-ksail-prod-with-pull-auth.sh
✅ [SUCCESS] scripts/summarize-sarif-findings.sh
✅ [SUCCESS] scripts/tests/test-cilium-bandwidth-manager-component.sh
✅ [SUCCESS] scripts/tests/test-cilium-homogeneous-devices-activation.sh
✅ [SUCCESS] scripts/tests/test-cilium-homogeneous-devices-autoscaler-gate.sh
✅ [SUCCESS] scripts/tests/test-cilium-homogeneous-devices-flux-wait.sh
✅ [SUCCESS] scripts/tests/test-dr-rebuild-supersession-guard.sh
✅ [SUCCESS] scripts/tests/test-kyverno-admission-vpa.sh
❌ [ERROR] scripts/tests/test-normalize-sarif-paths.sh
    Error: File:[scripts/tests/test-normalize-sarif-paths.sh] is not executable

✅ [SUCCESS] scripts/tests/test-openbao-oidc-role.sh
❌ [ERROR] scripts/tests/test-opencost-usage-scraper.sh
    Error: File:[scripts/tests/test-opencost-usage-scraper.sh] is not executable

❌ [ERROR] scripts/tests/test-refresh-flux-ghcr-auth-safety.sh
    Error: File:[scripts/tests/test-refresh-flux-ghcr-auth-safety.sh] is not executable

✅ [SUCCESS] scripts/tests/test-restrict-tenant-secret-stores.sh
✅ [SUCCESS] scripts/tests/test-summarize-sarif-findings.sh
✅ [SUCCESS] scripts/validate-alert-coverage.sh
✅ [SUCCESS] scripts/wait-for-platform-flux-revision.sh
⚠️ REPOSITORY / checkov - 34 errors
set"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-12

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_11: "CPU limits should be set"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-10

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_22: "Use read-only filesystem for containers where possible"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-21

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_40: "Containers should run as a high UID to avoid host conflict"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-37

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_43: "Image should use digest"
	FAILED for resource: Deployment.cdi.cdi-operator
	File: /k8s/bases/infrastructure/controllers/cdi/cdi-operator.yaml:5671-5779
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-39

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_155: "Minimize ClusterRoles that grant control over validating or mutating admission webhook configurations"
	FAILED for resource: ClusterRole.default.kubevirt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:7135-8469
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/ensure-clusterroles-that-grant-control-over-validating-or-mutating-admission-webhook-configurations-are-minimized

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_38: "Ensure that Service Account Tokens are only mounted where necessary"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-35

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_15: "Image Pull Policy should be Always"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-14

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_13: "Memory limits should be set"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-12

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_11: "CPU limits should be set"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-10

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_22: "Use read-only filesystem for containers where possible"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-21

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_40: "Containers should run as a high UID to avoid host conflict"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-37

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
Check: CKV_K8S_43: "Image should use digest"
	FAILED for resource: Deployment.kubevirt.virt-operator
	File: /k8s/bases/infrastructure/controllers/kubevirt/kubevirt-operator.yaml:8486-8620
	Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/kubernetes-policies/kubernetes-policy-index/bc-k8s-39

		Code lines for this resource are too many. Please use IDE of your choice to review the file.
github_actions scan results:

Passed checks: 100, Failed checks: 0, Skipped checks: 0

(Truncated to last 5714 characters out of 16340)
⚠️ SPELL / cspell - 2744 errors
"deploymentruntimeconfigs",
        "descheduler",
        "descheduling",
        "desynchronising",
        "devantler",
        "diffable",
        "dispositioned",
        "dockerconfigjson",
        "docstrings",
        "dorny",
        "dpkg",
        "drainable",
        "emptydir",
        "endgroup",
        "envsubst",
        "esac",
        "etcdctl",
        "evictable",
        "externalsecret",
        "externalsecrets",
        "fanout",
        "featureflagsource",
        "fleetdm",
        "fromdateiso",
        "gatewayapi",
        "generatable",
        "generatingpolicies",
        "genkey",
        "gethomepage",
        "golangci",
        "gosec",
        "grjtvs",
        "growfs",
        "growpart",
        "healthchecks",
        "healthz",
        "helmrelease",
        "helmreleases",
        "helmv",
        "homelab",
        "hostnames",
        "httproute",
        "imagetools",
        "imagevalidatingpolicy",
        "imranismail",
        "inspectable",
        "iscsi",
        "ivpol",
        "keypair",
        "kprobes",
        "kptr",
        "krew",
        "ksail",
        "ksail's",
        "ksailcd",
        "kubeconfig",
        "kubeconform",
        "kubeconform's",
        "kubelet",
        "kubelet's",
        "kubelets",
        "kubelogin",
        "kubescape",
        "kubespan",
        "kubevirt",
        "kubevuln",
        "kustomization",
        "kustomizations",
        "kyverno",
        "letsencrypt",
        "libc",
        "libgnutls",
        "lintable",
        "livez",
        "loadtester",
        "locationless",
        "luks",
        "lycheeignore",
        "machineconfig",
        "materialise",
        "materialised",
        "materialises",
        "maxage",
        "maxbackup",
        "maxsize",
        "maxx",
        "microtime",
        "misconfig",
        "misconfigs",
        "mktemp",
        "mlock",
        "mutatingpolicies",
        "mutatingwebhookconfigurations",
        "myapp",
        "najsk",
        "neighbour",
        "netlink",
        "netpol",
        "netpols",
        "nextjs",
        "nftables",
        "nilnil",
        "nobuckets",
        "nodepod",
        "nodeport",
        "nolint",
        "nonroot",
        "normalisation",
        "normalised",
        "normalises",
        "normalising",
        "nosec",
        "nullglob",
        "openbao",
        "opencost",
        "openfeature",
        "overprovisioning",
        "parallelised",
        "pasteable",
        "permissioning",
        "pipefail",
        "policyignore",
        "policyreports",
        "portforward",
        "preemptible",
        "preservingly",
        "prioritisable",
        "prioritisation",
        "prioritised",
        "privesc",
        "providerconfigs",
        "publishapp",
        "publishprovider",
        "pushsecret",
        "pushsecrets",
        "pycache",
        "randomises",
        "rdqwpktr",
        "readyz",
        "reassertions",
        "recognise",
        "recognised",
        "reconverges",
        "refreshfluxghcrauth",
        "regenerable",
        "releaserc",
        "rematerialise",
        "rematerialised",
        "replicaset",
        "repoint",
        "repointed",
        "repoints",
        "repositoryrulesets",
        "resizer",
        "restrictor",
        "retabbed",
        "rmem",
        "rolebindings",
        "rollouts",
        "rshared",
        "sarif",
        "schedulability",
        "schedulable",
        "scopeable",
        "scopeless",
        "seccomp",
        "secretbox",
        "secretstore",
        "seedable",
        "serialised",
        "serverside",
        "serviceaccount",
        "serviceaccounts",
        "sgdisk",
        "shellcheck",
        "shfmt",
        "shopt",
        "siderolabs",
        "siderolink",
        "sigstore",
        "skmde",
        "slurpfile",
        "sngle",
        "specnull",
        "spiffe",
        "srole",
        "startswith",
        "statefulset",
        "statemanager",
        "stdlib",
        "storageclass",
        "subtest",
        "summarised",
        "surfaceless",
        "syft",
        "syscall",
        "sysctls",
        "talosconfig",
        "talosctl",
        "tanzu",
        "templatesyncignore",
        "thresholded",
        "tmpl",
        "tostring",
        "tracepoints",
        "trixie",
        "trueish",
        "trustd",
        "umami",
        "umami's",
        "uncertifiable",
        "unclickable",
        "uncompilable",
        "uncordon",
        "uncordoned",
        "undecoded",
        "undercounts",
        "unexcepted",
        "ungenerated",
        "unifi",
        "unmarshalling",
        "unmarshals",
        "unmodelled",
        "unparseable",
        "unrecognisable",
        "unrecognised",
        "unreviewed",
        "unroutable",
        "unrun",
        "unshippable",
        "untrackable",
        "upbound",
        "updatekeys",
        "upjet",
        "upstreaming",
        "urlencode",
        "userinfo",
        "userns",
        "ushfn",
        "validatable",
        "validatealertcoverage",
        "validatingwebhookconfigurations",
        "vcunav",
        "velero",
        "volumesnapshot",
        "vpas",
        "vulnerabilitymanifests",
        "vulnerabilitymanifestsummary",
        "vulns",
        "vxlan",
        "wffc",
        "wgpolicyk",
        "wildcarded",
        "wlid",
        "wmem",
        "workloadconfigurationscansummary",
        "worktrees",
        "yannh",
        "yubikey",
        "yzwvjjmcyfnl",
        "zizmor"
    ]
}


You can also copy-paste megalinter-reports/.cspell.json at the root of your repository

(Truncated to last 5714 characters out of 484215)
⚠️ COPYPASTE / jscpd - 152 errors
(go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [447:52 - 464:5] (18 lines, 123 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [468:57 - 485:5]
Clone found (go)
 - scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [492:77 - 508:11] (17 lines, 118 tokens)
   scripts/tests/refresh-flux-ghcr-auth/rollout_safety_test.go [519:110 - 535:11]
Clone found (bash)
 - scripts/tests/test-cilium-bandwidth-manager-component.sh [9:1 - 54:2] (46 lines, 202 tokens)
   scripts/tests/test-cilium-homogeneous-devices-activation.sh [9:1 - 54:2]
Clone found (bash)
 - scripts/tests/test-cilium-bandwidth-manager-component.sh [52:5 - 72:2] (21 lines, 67 tokens)
   scripts/tests/test-cilium-homogeneous-devices-activation.sh [80:23 - 100:2]
Clone found (bash)
 - scripts/tests/test-cilium-bandwidth-manager-component.sh [54:1 - 72:2] (19 lines, 65 tokens)
   scripts/tests/test-opencost-usage-scraper.sh [15:1 - 33:2]
Clone found (bash)
 - scripts/tests/test-refresh-flux-ghcr-auth-safety.sh [177:5 - 185:51] (9 lines, 76 tokens)
   scripts/tests/test-refresh-flux-ghcr-auth-safety.sh [186:5 - 194:51]
Clone found (python)
 - scripts/tests/test_validate_homepage_bookmarks.py [46:57 - 54:54] (9 lines, 58 tokens)
   scripts/tests/test_validate_homepage_bookmarks.py [100:53 - 109:54]
Clone found (go)
 - scripts/validate-dr-signing/main.go [141:21 - 173:5] (33 lines, 145 tokens)
   scripts/validate-merge-group-heal/main.go [69:40 - 101:5]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [244:50 - 249:24] (6 lines, 103 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [756:31 - 761:24]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [276:14 - 282:4] (7 lines, 115 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [603:43 - 609:4]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [302:13 - 311:7] (10 lines, 158 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [828:18 - 837:7]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [311:1 - 316:8] (6 lines, 93 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [838:1 - 843:8]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [388:5 - 393:2] (6 lines, 82 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [574:8 - 579:9]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [389:1 - 399:23] (11 lines, 220 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [666:60 - 677:3]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [393:15 - 404:2] (12 lines, 185 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [494:17 - 505:2]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [440:15 - 451:12] (12 lines, 264 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [649:1 - 662:3]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [503:2 - 509:19] (7 lines, 89 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [688:7 - 694:19]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [503:2 - 509:33] (7 lines, 103 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [1013:58 - 1019:33]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [503:2 - 509:4] (7 lines, 74 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [1126:5 - 1132:4]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [596:30 - 601:8] (6 lines, 50 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [623:44 - 628:8]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [951:33 - 959:11] (9 lines, 118 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [963:130 - 971:11]
Clone found (go)
 - scripts/validate-eks-ci-role-policy/main_test.go [1158:47 - 1163:2] (6 lines, 166 tokens)
   scripts/validate-eks-ci-role-policy/main_test.go [1215:46 - 1220:2]
Clone found (python)
 - scripts/validate-naming.py [126:52 - 132:25] (7 lines, 53 tokens)
   scripts/validate-naming.py [171:82 - 177:29]
┌────────┬────────────────┬─────────────┬──────────────┬──────────────┬──────────────────┬───────────────────┐
│ Format │ Files analyzed │ Total lines │ Total tokens │ Clones found │ Duplicated lines │ Duplicated tokens │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ bash   │ 26             │ 8778        │ 35108        │ 15           │ 195 (2.22%)      │ 1188 (3.38%)      │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ go     │ 27             │ 17598       │ 123942       │ 71           │ 566 (3.22%)      │ 5917 (4.77%)      │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ python │ 4              │ 807         │ 5606         │ 2            │ 14 (1.73%)       │ 111 (1.98%)       │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ txt    │ 57             │ 3378        │ 109125       │ 64           │ 2417 (71.55%)    │ 80219 (73.51%)    │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ Total: │ 114            │ 30561       │ 273781       │ 152          │ 3192 (10.44%)    │ 87435 (31.94%)    │
└────────┴────────────────┴─────────────┴──────────────┴──────────────┴──────────────────┴───────────────────┘
Found 152 clones.
HTML report saved to megalinter-reports/copy-paste/jscpd-report.html
ERROR: jscpd found too many duplicates (10.4%) over threshold (0.0%)
time: 982.045ms

(Truncated to last 5714 characters out of 33639)
⚠️ SPELL / lychee - 1 error
📝 Summary
---------------------
🔍 Total..........254
🔗 Unique.........160
✅ Successful.....217
⏳ Timeouts.........0
🔀 Redirected.......8
👻 Excluded........36
❓ Unknown..........0
🚫 Errors...........1
⛔ Unsupported......1

Errors in docs/oidc-kubectl.md
[ERROR] https://dexidp.io/docs/configuration/custom-scopes-claims-clients/#cross-client-trust-and-authorized-party (at 186:7) | I/O error (ConnectionReset). Check network connectivity and server status

Hint: Followed 8 redirects. You might want to consider replacing redirecting URLs with the resolved URLs. Use verbose mode (`-v`/`-vv`) to see redirection details.
⚠️ MARKDOWN / markdownlint - 63 errors
Actual: 532]
AGENTS.md:250:401 error MD013/line-length Line length [Expected: 400; Actual: 523]
AGENTS.md:253:401 error MD013/line-length Line length [Expected: 400; Actual: 613]
AGENTS.md:254:401 error MD013/line-length Line length [Expected: 400; Actual: 714]
AGENTS.md:258:401 error MD013/line-length Line length [Expected: 400; Actual: 502]
AGENTS.md:262:401 error MD013/line-length Line length [Expected: 400; Actual: 441]
AGENTS.md:267:401 error MD013/line-length Line length [Expected: 400; Actual: 427]
AGENTS.md:370:401 error MD013/line-length Line length [Expected: 400; Actual: 1012]
AGENTS.md:372:401 error MD013/line-length Line length [Expected: 400; Actual: 1240]
AGENTS.md:377:401 error MD013/line-length Line length [Expected: 400; Actual: 430]
AGENTS.md:388:401 error MD013/line-length Line length [Expected: 400; Actual: 1230]
AGENTS.md:399:401 error MD013/line-length Line length [Expected: 400; Actual: 790]
AGENTS.md:404:401 error MD013/line-length Line length [Expected: 400; Actual: 515]
CLAUDE.md:1 error MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "@AGENTS.md"]
docs/dr/alerting.md:213:28 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:22:389 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:23:264 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:27:35 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:27:161 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:27:239 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/crypto-custody.md:114 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "Custody recommendations"]
docs/dr/crypto-custody.md:245 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "Custody recommendations"]
docs/dr/crypto-custody.md:251 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "What to do if it leaks"]
docs/dr/crypto-custody.md:258 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "What to do if it is *lost* (no..."]
docs/dr/restore-drill.md:42 error MD028/no-blanks-blockquote Blank line inside blockquote
docs/dr/runbook.md:23:102 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/runbook.md:23:487 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/runbook.md:34 error MD028/no-blanks-blockquote Blank line inside blockquote
docs/dr/runbook.md:41 error MD028/no-blanks-blockquote Blank line inside blockquote
docs/dr/runbook.md:50 error MD028/no-blanks-blockquote Blank line inside blockquote
docs/dr/runbook.md:489:92 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/spire-server-ha.md:93 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/dr/velero-cnpg.md:11 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/dr/velero-cnpg.md:56:78 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:56:166 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:57:78 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:57:227 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:58:78 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/dr/velero-cnpg.md:58:166 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/github-management.md:38:401 error MD013/line-length Line length [Expected: 400; Actual: 419]
docs/github-management.md:40:401 error MD013/line-length Line length [Expected: 400; Actual: 522]
docs/node-autoscaling.md:14 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/oidc-kubectl.md:95 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/runtime-security.md:114 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/rwx-storage.md:9 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/unifi-management.md:14 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
docs/unifi-management.md:62 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
README.md:116:401 error MD013/line-length Line length [Expected: 400; Actual: 540]
README.md:237:32 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:237:36 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]

(Truncated to last 5714 characters out of 6992)
⚠️ REPOSITORY / trivy - 1 error
──────────────────────────────────────
   7 ┌ spec:
   8 │   targetRef:
   9 │     apiVersion: apps/v1
  10 │     kind: StatefulSet
  11 └     name: spire-server
────────────────────────────────────────


KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/spire-server.yaml:7-11
────────────────────────────────────────
   7 ┌ spec:
   8 │   targetRef:
   9 │     apiVersion: apps/v1
  10 │     kind: StatefulSet
  11 └     name: spire-server
────────────────────────────────────────



k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon-operator.yaml (kubernetes)
=================================================================================================
Tests: 118 (SUCCESSES: 116, FAILURES: 2)
Failures: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 0, CRITICAL: 0)

KSV-0037 (MEDIUM): VerticalPodAutoscaler 'tetragon-operator' should not be set with 'kube-system' namespace
════════════════════════════════════════
ensure that user resources are not placed in kube-system namespace

See https://avd.aquasec.com/misconfig/ksv-0037
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon-operator.yaml:7-11
────────────────────────────────────────
   7 ┌ spec:
   8 │   targetRef:
   9 │     apiVersion: apps/v1
  10 │     kind: Deployment
  11 └     name: tetragon-operator
────────────────────────────────────────


KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon-operator.yaml:7-11
────────────────────────────────────────
   7 ┌ spec:
   8 │   targetRef:
   9 │     apiVersion: apps/v1
  10 │     kind: Deployment
  11 └     name: tetragon-operator
────────────────────────────────────────



k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon.yaml (kubernetes)
========================================================================================
Tests: 118 (SUCCESSES: 116, FAILURES: 2)
Failures: 2 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 0, CRITICAL: 0)

KSV-0037 (MEDIUM): VerticalPodAutoscaler 'tetragon' should not be set with 'kube-system' namespace
════════════════════════════════════════
ensure that user resources are not placed in kube-system namespace

See https://avd.aquasec.com/misconfig/ksv-0037
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon.yaml:9-13
────────────────────────────────────────
   9 ┌ spec:
  10 │   targetRef:
  11 │     apiVersion: apps/v1
  12 │     kind: DaemonSet
  13 └     name: tetragon
────────────────────────────────────────


KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/tetragon.yaml:9-13
────────────────────────────────────────
   9 ┌ spec:
  10 │   targetRef:
  11 │     apiVersion: apps/v1
  12 │     kind: DaemonSet
  13 └     name: tetragon
────────────────────────────────────────



ksail.prod.yaml (kubernetes)
============================
Tests: 118 (SUCCESSES: 117, FAILURES: 1)
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 ksail.prod.yaml:7-255
────────────────────────────────────────
   7 ┌   name: prod
   8 │ spec:
   9 │   cluster:
  10 │     distributionConfig: talos
  11 │     connection:
  12 │       context: admin@prod
  13 │       timeout: 30m
  14 │     distribution: Talos
  15 └     provider: Hetzner
  ..   
────────────────────────────────────────



ksail.yaml (kubernetes)
=======================
Tests: 118 (SUCCESSES: 117, FAILURES: 1)
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

KSV-0039 (LOW): A LimitRange policy with a default requests and limits for each container should be configured
════════════════════════════════════════
Ensure that a LimitRange policy is configured to limit resource usage for namespaces or nodes

See https://avd.aquasec.com/misconfig/ksv-0039
────────────────────────────────────────
 ksail.yaml:7-55
────────────────────────────────────────
   7 ┌   name: local
   8 │ spec:
   9 │   cluster:
  10 │     distributionConfig: talos-local
  11 │     connection:
  12 │       context: admin@local
  13 │       timeout: 40m
  14 │     distribution: Talos
  15 └     provider: Docker
  ..   
────────────────────────────────────────



📣 Notices:
  - Version 0.72.0 of Trivy is now available, current version is 0.71.2

To suppress version checks, run Trivy scans with the --skip-version-check flag

(Truncated to last 5714 characters out of 801281)

✅ Linters with no issues

actionlint, betterleaks, git_diff, golangci-lint, grype, jsonlint, osv-scanner, prettier, prettier, revive, secretlint, shellcheck, shfmt, syft, trivy-sbom, trufflehog, v8r, v8r, yamllint, zizmor

Notices

📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining SECURITY_SUGGESTIONS: false)

See detailed reports in MegaLinter artifacts

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Requesting a CodeRabbit review at the current head 5a7ade14fd (CI green, CLEAN, 0 unresolved threads). CodeRabbit is the first lane by priority; its account quota notice earlier this hour stated a 59-minute window from 00:02:55Z, which should now be at or past expiry.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@devantler: I will review pull request #2919 at head 5a7ade14fdf89566d6915148805990614f610e13.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The production configuration removes the misplaced top-level Flux OCIRepository Cosign verification block. It adds the verification policy under spec.workload.flux, where KSail renders it onto the managed Flux OCIRepository. The policy uses Cosign keyless verification and permits signatures from the production merge-queue, manual production, and disaster-recovery workflows with pinned GitHub OIDC issuer and subject patterns.

Possibly related issues

Possibly related PRs


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Linked Issues check ❌ Error The configuration move meets the primary objective, but the required rendered-shape test and DR convergence evidence are absent. Add a regression test for the rendered root OCIRepository and provide evidence that a from-zero DR rebuild converges under the activated policy.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changes are limited to moving the existing Flux Cosign policy to the KSail-supported path.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly describes moving Flux Cosign verification to the configuration path read by KSail.
Description check ✅ Passed The description explains the configuration fix, its production impact, validation status, and linked issue.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Re-triggering CodeRabbit at head 5a7ade14fd — the previous attempt at 00:57:11Z returned the quota shell stating a 5-minute window, which has now elapsed.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@devantler: I will review pull request #2919 at commit 5a7ade14fdf89566d6915148805990614f610e13.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 200-230: Add rendered-policy tests for the production Flux
workload configuration before enabling Cosign enforcement: verify KSail renders
OCIRepository.spec.verify with all three allowed matchOIDCIdentity subjects for
merge-queue ci.yaml, main-branch cd.yaml, and main-branch dr-rebuild.yaml, and
verify an invalid identity is rejected. Use the existing
effective-state/rendered validation test mechanisms and keep the configured
production identities unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 13564c65-155f-4ed4-baa9-a96a75100320

📥 Commits

Reviewing files that changed from the base of the PR and between 8bb0db9 and 5a7ade1.

📒 Files selected for processing (1)
  • ksail.prod.yaml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
ksail*.yaml

📄 CodeRabbit inference engine (AGENTS.md)

Treat ksail.yaml and ksail.prod.yaml as ordinary configuration, validate both environments, and do not modify cluster state during maintenance.

Files:

  • ksail.prod.yaml
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Use Conventional Commit messages and titles, keep changes small and focused, and create pull requests as drafts.
Land new behavior disabled, validate it, then enable it using the appropriate layer: OpenFeature for per-request flags, Flagger for traffic shifting, Helm values/Kustomize for component toggles, and Kubernetes feature gates for platform behavior.

Files:

  • ksail.prod.yaml

Comment thread ksail.prod.yaml
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

The relocation half is now proven; the artifact half is blocked on a token scope

Two separate claims sit behind this PR, and only one of them was ever about the review. Splitting them:

1. Does the block land where KSail reads it? — YES, verified structurally at the pinned version

ksail workload validate cannot answer this (it passes identically at either path — that is exactly #2920), so I went to the consumer instead of the config text.

Production renders with KSAIL_VERSION: 7.176.4 (pinned in ci.yaml, cd.yaml's deploy path, dr-rebuild.yaml and validate-main.yaml). Enumerating every verify key in that exact release's published config schema — not grepping for one:

query against schemas/ksail-config.schema.json@v7.176.4 result
all paths ending in verify exactly one: spec.workload.flux.verify
any verify path under spec.cluster 0

So the key this PR moves to is the only one that exists, and the key it moves from was never in the schema — which is precisely why it was accepted and discarded in silence. pkg/svc/installer/flux/ocirepository_patcher.go is the other end: buildVerifyPatch renders a FluxVerifySpec and applyVerify sets it at spec.verify on the flux-system OCIRepository.

2. Is verification actually off in production right now? — YES, confirmed live

flux-system  url=oci://ghcr.io/devantler-tech/platform/manifests  tag=latest  verify=NONE
             digest=latest@sha256:ac22292f3904bd347a5b1b28e92dade52fe235e57c7a65f612162523c1697851

3. Will the live artifact pass once enforcement is on? — STILL UNVERIFIED, and this is the promotion gate

The recorded blocker was "cosign is not installed on the agent host". That is no longer true — I installed it. The real blocker is one level down, and I established it rather than assuming it:

  • cosign login ghcr.io -u devantler with the host token succeeds (exit 0), so this is not an authentication failure;
  • the subsequent pull returns DENIED: requested access to the resource is denied for all three trusted identities;
  • GET /orgs/devantler-tech/packages?package_type=container returns 403 Resource not accessible by personal access token.

The host PAT is missing read:packages. That is the same scope gap already blocking #2815 and #2818's option 3, so it is one grant, not three.

Staying a draft. Merging starts enforcing signatures on the source that delivers every infrastructure manifest, so "the policy is now in the right place" is not sufficient — an artifact that fails the check stops manifest delivery. The relocation is right and the bug is real; the last step needs the scope.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Readiness — the promotion gate this PR was parked on is now closed

The draft said it was waiting on one thing: proof that the live artifact will actually pass the
policy once Flux starts enforcing it. cosign was missing from the agent host then; it is present
now (/opt/homebrew/bin/cosign), and the check has been done — by a route that needs no registry
credentials, since this repo is public and its signatures are in the public transparency log by
design.

1. The defect is real, confirmed live. On the prod cluster the infra source is running
unverified, while every tenant source is not:

OCIRepository verify ready
flux-system/flux-systemplatform/manifests:latest NONE True
ascoachingogvaner, aws, doggy-countdown, github-config, wedding-app cosign True

2. The key this PR moves to is the one KSail actually reads. Traced to the consumer, not
inferred from a green validate: Spec.Workload.Flux.Verify is read at
pkg/svc/installer/flux/resources.go:378 and rendered onto the generated OCIRepository. The path
resolves as spec.workloadWorkloadSpec.Flux (flux) → FluxConfig.Verify (verify) — exactly
where this PR puts it. The old location sat under a struct with no such field, which is why it was
silently discarded.

3. The live artifact is signed, by an identity this policy accepts. The artifact Flux is serving
today (latest@sha256:ac22292f…) has three Sigstore transparency-log entries. Their certificate
carries:

  • identity …/platform/.github/workflows/ci.yaml@refs/heads/gh-readonly-queue/main/pr-2911-8958022…
  • issuer https://token.actions.githubusercontent.com

which is the merge-queue prod deploy path — the first of the three pinned subjects.

4. The policy is discriminating, not vacuous. The accepting match was checked against five
near-miss controls, all correctly rejected: a feature-branch ref, a different workflow file, a
different repo, cd.yaml off a non-main ref, and the queue prefix with an empty suffix. A policy
that accepted everything would have passed the live case too, so the controls are what make the
match mean something.

5. Signing is unconditional and ran green. The 🖋️ Sign OCI manifest with cosign (keyless)
step in the deploy-prod composite has no if: guard and runs under set -euo pipefail; the job that
published the current artifact (run 30763969854) concluded success. That composite's own comment
anticipates this policy needing to allow both the ci.yaml and cd.yaml identities — which it does,
plus dr-rebuild.yaml.

Stated limit, so this is not read as more than it is. The agent PAT has no read:packages, so
what was verified is the attestation certificate for that digest — same run, same job, same OIDC
identity, same composite — rather than the .sig object itself. Everything above is consistent and
mutually corroborating, but that one indirection is real and worth naming.

Operational effect on merge is unchanged from the body: Flux begins enforcing signatures on the
infra source. If a future artifact is unsigned or signed by an unpinned ref, Flux keeps the last good
state rather than applying it, and reverting this restores the previous behaviour.

@devantler
devantler marked this pull request as ready for review August 3, 2026 03:26
@devantler
devantler added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit fd95976 Aug 3, 2026
30 checks passed
@devantler
devantler deleted the claude/flux-verify-config-path-2627 branch August 3, 2026 03:32
@github-project-automation github-project-automation Bot moved this from 🫴 Ready to ✅ Done in 🌊 Project Board Aug 3, 2026
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Post-merge verification — the config landed, the enforcement did not

Checked the live cluster after this merged and its deploy went green, rather than assuming the
effect. Reporting it plainly because the result is not the one this PR predicted:

  • 🚀 Deploy to Prod for the merge-queue commit: success, new artifact published.
  • ocirepository/flux-system: spec.verify still absent, Ready=True.

So the key move is correct and necessary — it is genuinely the path KSail reads — but on its own it
does not switch enforcement on for an already-running cluster. The operational note in the body
(“Flux starts enforcing signatures once reconciled”) has not taken effect, which also means the
risk that note flagged has not materialised: nothing is being rejected, because nothing is being
verified yet.

Two contributing causes, both checked: the verify patch sits on KSail's Flux install /
SetupInstance path rather than the routine deploy path, and the flux-system OCIRepository is
generated by the flux-operator FluxInstance, whose spec.sync carries no verify field.

Tracked as #2922 (sub-issue of #2627). #2850 asked to move the key and activate; the move landed
here, the activation is #2922. Worth noting the pre-merge signature evidence still stands — the live
artifact is signed by a pinned production identity — so activation should be safe when it is wired
to a path that survives reconcile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

Move root Cosign verify to the KSail-supported path and activate root source verification

1 participant