Splits the 20 EPIC-1 (#1096) compliance ClusterPolicy templates out of bp-kyverno (engine umbrella chart) into a dedicated Blueprint bp-kyverno-policies@1.0.0 with its own HelmRelease, ordered via HR-to-HR dependsOn on bp-kyverno in the bootstrap-kit Kustomization. WHY (the bug we're killing): PR #1138 (2026-05-08) shipped 20 ClusterPolicy templates with `enabled: false` defaults → dead-on-arrival for 11 days. PR #1933 (2026-05-19) flipped 18 defaults to `enabled: true` + bumped chart 1.1.0 → 1.2.0 + bumped the bootstrap-kit pin — but hit a CRD install- ordering race on fresh prov t33: ClusterPolicy CRs (in templates/policies/baseline/*.yaml) and Kyverno CRDs (in upstream charts/crds/templates/) render in the SAME Helm pass, and the apiserver's RESTMapper has not yet learned kyverno.io/v1.ClusterPolicy when Helm applies the ClusterPolicy CRs. PR #1935 reverted ONLY the bootstrap-kit pin (1.2.0 → 1.1.0) — chart source kept claiming policies were on by default while the deployed pin pulled an engine-only artifact with zero policies. "Theater on theater" — founder walk on t34 confirmed GET /api/v1/sovereigns/<id>/compliance/policies returns `policyCount=0`, only `useraccess-boundary` (from bp-crossplane-claims) was installed. The structural fix is splitting the chart so the engine + CRDs reconcile + register first, THEN the policy chart applies its CRs cleanly. Audit mode default = non-blocking (admission still passes, PolicyReport rows populate). Operators flip individual policies to Enforce per-Sovereign overlay or via EnvironmentPolicy.spec.compliance.modes (slice C2 controller path — separate work item). CHANGES: 1. NEW chart `platform/kyverno-policies/chart/`: - Chart.yaml: name=bp-kyverno-policies, version=1.0.0, no subchart deps - values.yaml: `compliancePolicies:` block moved verbatim from bp-kyverno (defaults: 18 enabled+Audit, 2 intentionally OFF — `hubbleFlowsSeen` stub for W2 evaluator, `cosignVerified` until operator supplies PEM) - templates/baseline/01-..20-*.yaml: 20 ClusterPolicy templates moved via `git mv` (preserves blame; preserves PR #1933's 3 operator fixes — regex_match JMESPath + operator: Equals for 11/12/19) - tests/fixtures/: moved with the policies (fixtures reference policy output, not engine output) 2. ENGINE chart `platform/kyverno/chart/`: - Chart.yaml: 1.2.0 → 1.2.1 (policies removed, source no longer drift-claims compliance content) - values.yaml: `compliancePolicies:` block deleted (now lives in bp-kyverno-policies) - templates/clusterpolicy-mutate-add-openova-labels.yaml + ...require- openova-labels.yaml KEPT (engine-coupled mutating policies, EPIC-0 label-vocab E1/E2, defaults OFF — separate concern from EPIC-1 compliance library) - Empty `templates/policies/` directory removed 3. NEW bootstrap-kit slot `clusters/_template/bootstrap-kit/27a-kyverno- policies.yaml`: - HelmRelease bp-kyverno-policies pinned at chart `1.0.0` - HR-level `dependsOn: [bp-kyverno]` — same-kind, honored by Flux (per docs/INVIOLABLE-PRINCIPLES.md #14 cross-kind HR→Kustomization dependsOn is silently ignored, so we keep ordering at HR→HR within the single bootstrap-kit Kustomization) - targetNamespace: kyverno (same as engine — ClusterPolicy is cluster- scoped but the umbrella overlay namespacing matches the engine) - disableWait: true — Kyverno reports ClusterPolicy Ready asynchronously so we don't want downstream HRs stalling on policy-level health 4. UPDATED `clusters/_template/bootstrap-kit/kustomization.yaml`: - Added `27a-kyverno-policies.yaml` immediately after `27-kyverno.yaml` 5. BUMPED `clusters/_template/bootstrap-kit/27-kyverno.yaml`: - Engine pin 1.1.0 → 1.2.1 (engine-only; install behavior identical to 1.1.0 since policies + their values are no longer in this chart) VALIDATION (Principle #15 — validate against fresh state, not stable state): $ helm template bp-kyverno-policies platform/kyverno-policies/chart \ | grep -c '^kind: ClusterPolicy' 18 $ helm lint platform/kyverno-policies/chart && helm lint platform/kyverno/chart ==> 1 chart(s) linted, 0 chart(s) failed (both) $ helm template bp-kyverno platform/kyverno/chart \ | grep -c '^kind: ClusterPolicy' 0 # engine no longer renders any ClusterPolicy CRs $ helm package platform/kyverno-policies/chart Successfully packaged → bp-kyverno-policies-1.0.0.tgz (20 templates) CRD-race REPRODUCED locally without container runtime: applying the rendered policy YAML to a cluster WITHOUT Kyverno CRDs returns "no matches for kind \"ClusterPolicy\" in version \"kyverno.io/v1\" ensure CRDs are installed first" for every policy — proving the install-order fix is necessary. Full `helm install` from-scratch on Kind blocked locally (no container runtime on bastion); the Blueprint-Release CI workflow runs the full `helm dependency build` + package + GHCR push pipeline AND a `helm template` smoke render at publish time — that is the fresh-state Helm install gate before any pin lands. CI / GHCR (Principle #13): Blueprint-Release workflow auto-detects `platform/kyverno-policies/chart/**` and publishes `oci://ghcr.io/openova-io/bp-kyverno-policies:1.0.0` on push to main. The slot pin in 27a-kyverno-policies.yaml is set to `1.0.0` to match (auto-bump-pin step is a no-op when source version already matches the slot pin). DELIBERATELY OUT OF SCOPE: - W2 Go evaluator for `hubble-flows-seen` (stub stays a no-op) - Cosign publicKey supply path for `cosign-verified` - Per-Environment EnvironmentPolicy.spec.compliance.modes enforcement flip controller - Score-aggregator weight defaults configuration UI - `useraccess-boundary` (lives in bp-crossplane-claims, unchanged) This does NOT close #1096. The EPIC remains open until a fresh-prov walk shows `kubectl get clusterpolicies -A` returning the 18 baseline policies + useraccess-boundary, plus the AppDetail Compliance tab rendering non- zero policyCount. Founder closes #1096 after that walk. Refs #1096, Refs #2019, Refs #1929, Refs #1936
75 lines
2.7 KiB
YAML
75 lines
2.7 KiB
YAML
{{/*
|
|
K1 — topology-spread (resilience, permissive default).
|
|
|
|
Deployments / StatefulSets with replicas >= 2 must declare a
|
|
topologySpreadConstraint across either kubernetes.io/hostname or
|
|
topology.kubernetes.io/zone, so a single host or AZ failure cannot
|
|
remove every replica.
|
|
|
|
Single-replica workloads are out of scope here (multi-replica policy
|
|
catches those). PodAntiAffinity is an acceptable alternative pattern
|
|
but Kyverno's pattern engine cannot cleanly express OR across two
|
|
unrelated tree shapes (topologySpreadConstraints vs affinity); the
|
|
affinity case is checked by the W2 evaluator as a synthetic compliance
|
|
row. This policy enforces ONLY the topologySpreadConstraints branch
|
|
when triggered.
|
|
|
|
Default mode permissive per §4.3.
|
|
*/}}
|
|
{{- if .Values.compliancePolicies.topologySpread.enabled -}}
|
|
apiVersion: kyverno.io/v1
|
|
kind: ClusterPolicy
|
|
metadata:
|
|
name: topology-spread
|
|
labels:
|
|
app.kubernetes.io/name: bp-kyverno
|
|
app.kubernetes.io/component: compliance-policy
|
|
catalyst.openova.io/policy-tier: compliance
|
|
catalyst.openova.io/policy-domain: resilience
|
|
annotations:
|
|
policies.kyverno.io/title: Multi-replica workloads must spread across hostname or zone
|
|
policies.kyverno.io/category: catalyst-compliance
|
|
policies.kyverno.io/severity: medium
|
|
policies.kyverno.io/description: >-
|
|
Deployments / StatefulSets with replicas >= 2 must declare
|
|
topologySpreadConstraints with topologyKey of kubernetes.io/hostname
|
|
or topology.kubernetes.io/zone, so a single host or AZ failure
|
|
can't take down every replica.
|
|
spec:
|
|
validationFailureAction: {{ .Values.compliancePolicies.topologySpread.action | default "Audit" }}
|
|
background: true
|
|
rules:
|
|
- name: workload-must-declare-spread
|
|
match:
|
|
any:
|
|
- resources:
|
|
kinds:
|
|
- Deployment
|
|
- StatefulSet
|
|
exclude:
|
|
any:
|
|
- resources:
|
|
namespaces:
|
|
{{- range $ns := .Values.compliancePolicies.topologySpread.excludeNamespaces }}
|
|
- {{ $ns }}
|
|
{{- end }}
|
|
preconditions:
|
|
all:
|
|
- key: '{{ printf "{{ request.object.spec.replicas || `0` }}" }}'
|
|
operator: GreaterThanOrEquals
|
|
value: 2
|
|
validate:
|
|
message: >-
|
|
Multi-replica workload
|
|
{{ `{{request.object.kind}}/{{request.object.metadata.name}}` }} is
|
|
missing topologySpreadConstraints. Add at least one constraint
|
|
with topologyKey kubernetes.io/hostname or
|
|
topology.kubernetes.io/zone.
|
|
pattern:
|
|
spec:
|
|
template:
|
|
spec:
|
|
topologySpreadConstraints:
|
|
- topologyKey: "kubernetes.io/hostname | topology.kubernetes.io/zone"
|
|
{{- end -}}
|