Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@
# https://fluxcd.io/flux/components/kustomize/kustomizations/#recommended-settings
# https://fluxcd.io/flux/components/helm/helmreleases/
#
# Runs in Enforce mode — non-compliant resources are denied at admission (and
# at Flux's server-side dry-run), so new Flux resources must declare all the
# Runs with Deny — non-compliant resources are refused at admission (and at
# Flux's server-side dry-run), so new Flux resources must declare all the
# recommended fields up front.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
#
# Exclusions are matchConditions, so an excluded object is out of scope rather
# than a pass. Each one names the Kustomization kind: a HelmRelease of the same
# name is still validated. The Hetzner overlay appends the UniFi exclusion.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: enforce-flux-best-practices
annotations:
policies.kyverno.io/title: Enforce Flux Best Practices
policies.kyverno.io/category: Flux, Best Practices
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Kustomization, HelmRelease
policies.kyverno.io/minversion: 1.13.0
policies.kyverno.io/minversion: 1.17.0
policies.kyverno.io/description: >-
Validates that Flux Kustomization and HelmRelease resources declare the
reliability-critical fields recommended by the Flux documentation:
Expand All @@ -26,83 +30,75 @@ metadata:
misconfigured resources at admission instead of waiting for a runtime
failure.
spec:
background: true
rules:
- name: kustomization-recommended-settings
match:
any:
- resources:
kinds:
- kustomize.toolkit.fluxcd.io/v1/Kustomization
exclude:
any:
# The bootstrap flux-system Kustomization is created and managed by
# `flux bootstrap` / ksail and not by GitOps from this repo, so we
# cannot set the recommended fields on it. Exclude it from the policy.
- resources:
namespaces:
- flux-system
names:
- flux-system
validate:
failureAction: Enforce
message: >-
Flux Kustomization must set the recommended reliability fields:
`spec.interval`, `spec.timeout`, `spec.retryInterval`,
`spec.prune: true` (garbage collection), and `spec.sourceRef`.
`spec.wait: true` is required except for the platform-owned GitHub
configuration, whose asynchronous API operations do not gate delivery.
See https://fluxcd.io/flux/components/kustomize/kustomizations/#recommended-settings.
anyPattern:
- spec:
interval: "?*"
timeout: "?*"
retryInterval: "?*"
prune: true
wait: true
sourceRef:
kind: "?*"
name: "?*"
# Bound the alternative to this exact platform-owned identity. Keep
# every other reliability requirement; this is not a policy exclusion.
- metadata:
name: github-config
namespace: github-config
spec:
interval: "?*"
timeout: "?*"
retryInterval: "?*"
prune: true
wait: false
sourceRef:
kind: "?*"
name: "?*"
- name: helmrelease-recommended-settings
match:
any:
- resources:
kinds:
# v2 only: the v2beta1/v2beta2 APIs were removed from the
# cluster (Flux ≥ 2.6 serves only helm.toolkit.fluxcd.io/v2).
# Matching the dead versions makes Kyverno's GVK→GVR lookup
# fail on every admission review and background scan
# ("unable to convert GVK to GVR ... resource not found").
- helm.toolkit.fluxcd.io/v2/HelmRelease
validate:
failureAction: Enforce
message: >-
Flux HelmRelease must set the recommended reliability fields:
`spec.interval`, `spec.timeout`, install + upgrade remediation
retries, and `spec.upgrade.remediation.remediateLastFailure: true`.
See https://fluxcd.io/flux/components/helm/helmreleases/.
pattern:
spec:
interval: "?*"
timeout: "?*"
install:
remediation:
retries: ">=1 | -1"
upgrade:
remediation:
retries: ">=1 | -1"
remediateLastFailure: true
validationActions:
- Deny
evaluation:
background:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: ["kustomize.toolkit.fluxcd.io"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["kustomizations"]
# v2 only: the v2beta1/v2beta2 APIs were removed from the cluster
# (Flux ≥ 2.6 serves only helm.toolkit.fluxcd.io/v2). Matching the dead
# versions makes Kyverno's GVK→GVR lookup fail on every admission review
# and background scan ("unable to convert GVK to GVR ... resource not found").
- apiGroups: ["helm.toolkit.fluxcd.io"]
apiVersions: ["v2"]
operations: ["CREATE", "UPDATE"]
resources: ["helmreleases"]
matchConditions:
# The bootstrap flux-system Kustomization is created and managed by
# `flux bootstrap` / ksail and not by GitOps from this repo, so we cannot
# set the recommended fields on it.
- name: not-flux-bootstrap-kustomization
expression: >-
!(object.kind == 'Kustomization' &&
object.metadata.?namespace.orValue('') == 'flux-system' &&
object.metadata.name == 'flux-system')
variables:
- name: isKustomization
expression: object.kind == 'Kustomization'
# Bound the health-gating alternative to this exact platform-owned
# identity, whose asynchronous API operations do not gate delivery. It
# keeps every other reliability requirement; this is not an exclusion.
- name: isGitHubConfig
expression: >-
object.metadata.name == 'github-config' &&
object.metadata.?namespace.orValue('') == 'github-config'
- name: installRetries
expression: object.spec.?install.?remediation.?retries.orValue(0)
- name: upgradeRetries
expression: object.spec.?upgrade.?remediation.?retries.orValue(0)
validations:
- expression: >-
!variables.isKustomization || (
object.spec.?interval.orValue('') != '' &&
object.spec.?timeout.orValue('') != '' &&
object.spec.?retryInterval.orValue('') != '' &&
object.spec.?prune.orValue(false) == true &&
(object.spec.?wait.orValue(false) == true ||
(variables.isGitHubConfig && object.spec.?wait.orValue(true) == false)) &&
object.spec.?sourceRef.?kind.orValue('') != '' &&
object.spec.?sourceRef.?name.orValue('') != '')
message: >-
Flux Kustomization must set the recommended reliability fields:
`spec.interval`, `spec.timeout`, `spec.retryInterval`,
`spec.prune: true` (garbage collection), and `spec.sourceRef`.
`spec.wait: true` is required except for the platform-owned GitHub
configuration, whose asynchronous API operations do not gate delivery.
See https://fluxcd.io/flux/components/kustomize/kustomizations/#recommended-settings.
- expression: >-
variables.isKustomization || (
object.spec.?interval.orValue('') != '' &&
object.spec.?timeout.orValue('') != '' &&
(variables.installRetries >= 1 || variables.installRetries == -1) &&
(variables.upgradeRetries >= 1 || variables.upgradeRetries == -1) &&
object.spec.?upgrade.?remediation.?remediateLastFailure.orValue(false) == true)
message: >-
Flux HelmRelease must set the recommended reliability fields:
`spec.interval`, `spec.timeout`, install + upgrade remediation
retries, and `spec.upgrade.remediation.remediateLastFailure: true`.
See https://fluxcd.io/flux/components/helm/helmreleases/.
16 changes: 8 additions & 8 deletions k8s/providers/hetzner/infrastructure/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -214,19 +214,19 @@ patches:
# remains strict for every other environment. The authorization validator pins
# the rendered exception set and the reconciler's immutable source/delete bounds.
- target:
group: kyverno.io
group: policies.kyverno.io
version: v1
kind: ClusterPolicy
kind: ValidatingPolicy
name: enforce-flux-best-practices
patch: |
- op: add
path: /spec/rules/0/exclude/any/-
path: /spec/matchConditions/-
value:
resources:
namespaces:
- unifi
names:
- unifi
name: not-unifi-kustomization
expression: >-
!(object.kind == 'Kustomization' &&
object.metadata.?namespace.orValue('') == 'unifi' &&
object.metadata.name == 'unifi')
# gateway-patch self-identifies (Gateway/platform/kube-system), so a path is
# enough — no explicit target, matching the coroot patch below.
- path: patches/attach-hcloud-load-balancer.yaml
Expand Down
120 changes: 72 additions & 48 deletions scripts/validate-eks-ci-role-policy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
Expand Down Expand Up @@ -184,72 +185,95 @@ func validateUnifiSigningKey(documents []map[string]any) error {
return nil
}

// errUnifiPruneExemption is the single diagnostic for every shape the UniFi
// prune exemption must not take.
var errUnifiPruneExemption = errors.New(
"enforce-flux-best-practices must exempt only unifi/unifi from prune enforcement",
)

// fluxPolicyMatchConstraints is the only scope enforce-flux-best-practices may
// render with: an added resourceNames, a dropped operation or rule, or any
// exclude or selector field would take objects out of scope without a
// matchCondition.
func fluxPolicyMatchConstraints() map[string]any {
return map[string]any{"resourceRules": []any{
map[string]any{
"apiGroups": []any{"kustomize.toolkit.fluxcd.io"},
"apiVersions": []any{"v1"},
"operations": []any{"CREATE", "UPDATE"},
"resources": []any{"kustomizations"},
},
map[string]any{
"apiGroups": []any{"helm.toolkit.fluxcd.io"},
"apiVersions": []any{"v2"},
"operations": []any{"CREATE", "UPDATE"},
"resources": []any{"helmreleases"},
},
}}
}

// fluxPolicyExclusionExpression is the matchCondition expression that leaves
// exactly one namespaced Kustomization out of enforce-flux-best-practices.
func fluxPolicyExclusionExpression(namespace, name string) string {
return fmt.Sprintf(
"!(object.kind == 'Kustomization' && "+
"object.metadata.?namespace.orValue('') == '%s' && "+
"object.metadata.name == '%s')",
namespace, name,
)
}

// validateUnifiPruneExemption keeps the deliberate non-pruning reconciler
// deployable while requiring the admission-policy exception to remain scoped
// to exactly one namespaced Kustomization.
// to exactly one namespaced Kustomization. The policy's exclusions are its
// matchConditions, so the rendered policy must carry exactly the bootstrap and
// UniFi conditions and no other way to narrow its scope.
func validateUnifiPruneExemption(document map[string]any, identity resourceIdentity) error {
wantIdentity := resourceIdentity{
apiVersion: "kyverno.io/v1",
kind: "ClusterPolicy",
apiVersion: "policies.kyverno.io/v1",
kind: "ValidatingPolicy",
name: "enforce-flux-best-practices",
}
if identity != wantIdentity {
return nil
}

exactSingleton := func(value any, want string) bool {
values, ok := value.([]any)
return ok && len(values) == 1 && values[0] == want
}
spec, ok := document["spec"].(map[string]any)
if !ok {
return errors.New("enforce-flux-best-practices must exempt only unifi/unifi from prune enforcement")
return errUnifiPruneExemption
}
rules, ok := spec["rules"].([]any)
constraints, ok := spec["matchConstraints"].(map[string]any)
if !ok {
return errors.New("enforce-flux-best-practices must exempt only unifi/unifi from prune enforcement")
return errUnifiPruneExemption
}
for _, ruleValue := range rules {
rule, ok := ruleValue.(map[string]any)
if !ok || rule["name"] != "kustomization-recommended-settings" {
continue
}
exclude, ok := rule["exclude"].(map[string]any)
if !ok {
break
}
exclusions, ok := exclude["any"].([]any)
if !ok {
break
}
seenFluxSystem := false
seenUnifi := false
for _, exclusionValue := range exclusions {
exclusion, ok := exclusionValue.(map[string]any)
if !ok {
break
}
resources, ok := exclusion["resources"].(map[string]any)
if !ok {
break
}
switch {
case exactSingleton(resources["namespaces"], "flux-system") &&
exactSingleton(resources["names"], "flux-system") && !seenFluxSystem:
seenFluxSystem = true
case exactSingleton(resources["namespaces"], "unifi") &&
exactSingleton(resources["names"], "unifi") && !seenUnifi:
seenUnifi = true
default:
return errors.New("enforce-flux-best-practices must exempt only unifi/unifi from prune enforcement")
}
if !reflect.DeepEqual(constraints, fluxPolicyMatchConstraints()) {
return errUnifiPruneExemption
}
conditions, ok := spec["matchConditions"].([]any)
if !ok || len(conditions) != 2 {
return errUnifiPruneExemption
}
want := map[string]string{
"not-flux-bootstrap-kustomization": fluxPolicyExclusionExpression("flux-system", "flux-system"),
"not-unifi-kustomization": fluxPolicyExclusionExpression("unifi", "unifi"),
}
for _, conditionValue := range conditions {
condition, ok := conditionValue.(map[string]any)
if !ok || len(condition) != 2 {
return errUnifiPruneExemption
}
if seenFluxSystem && seenUnifi && len(exclusions) == 2 {
return nil
name, _ := condition["name"].(string)
expression, _ := condition["expression"].(string)
wantExpression, known := want[name]
if !known || strings.Join(strings.Fields(expression), " ") != wantExpression {
return errUnifiPruneExemption
}
break
delete(want, name)
}
if len(want) != 0 {
return errUnifiPruneExemption
}
return errors.New("enforce-flux-best-practices must exempt only unifi/unifi from prune enforcement")
return nil
}

// expectedRenderedHashes preserves object-specific diagnostics for the core
Expand Down
Loading
Loading