diff --git a/k8s/bases/infrastructure/cluster-policies/flux/enforce-flux-best-practices.yaml b/k8s/bases/infrastructure/cluster-policies/flux/enforce-flux-best-practices.yaml index 96958c6d97..e47471cba1 100644 --- a/k8s/bases/infrastructure/cluster-policies/flux/enforce-flux-best-practices.yaml +++ b/k8s/bases/infrastructure/cluster-policies/flux/enforce-flux-best-practices.yaml @@ -5,11 +5,15 @@ # 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: @@ -17,7 +21,7 @@ metadata: 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: @@ -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/. diff --git a/k8s/providers/hetzner/infrastructure/kustomization.yaml b/k8s/providers/hetzner/infrastructure/kustomization.yaml index 66092641be..201739acd2 100644 --- a/k8s/providers/hetzner/infrastructure/kustomization.yaml +++ b/k8s/providers/hetzner/infrastructure/kustomization.yaml @@ -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 diff --git a/scripts/validate-eks-ci-role-policy/main.go b/scripts/validate-eks-ci-role-policy/main.go index a25a020e68..9d44947ac6 100644 --- a/scripts/validate-eks-ci-role-policy/main.go +++ b/scripts/validate-eks-ci-role-policy/main.go @@ -22,6 +22,7 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "regexp" "sort" "strings" @@ -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 diff --git a/scripts/validate-eks-ci-role-policy/main_test.go b/scripts/validate-eks-ci-role-policy/main_test.go index 338d8db6f0..0b58064f02 100644 --- a/scripts/validate-eks-ci-role-policy/main_test.go +++ b/scripts/validate-eks-ci-role-policy/main_test.go @@ -437,27 +437,36 @@ func TestValidateUnifiSigningKey(t *testing.T) { // for any other Flux Kustomization. func TestValidateUnifiPruneExemption(t *testing.T) { targetIdentity := resourceIdentity{ - apiVersion: "kyverno.io/v1", - kind: "ClusterPolicy", + apiVersion: "policies.kyverno.io/v1", + kind: "ValidatingPolicy", name: "enforce-flux-best-practices", } - policy := func(exclusions ...any) map[string]any { + condition := func(name, namespace, object string) map[string]any { return map[string]any{ - "spec": map[string]any{ - "rules": []any{ - map[string]any{ - "name": "kustomization-recommended-settings", - "exclude": map[string]any{"any": exclusions}, - }, - }, - }, + "name": name, + "expression": fluxPolicyExclusionExpression(namespace, object), } } - resourceExclusion := func(namespace, name string) map[string]any { + bootstrap := condition("not-flux-bootstrap-kustomization", "flux-system", "flux-system") + unifi := condition("not-unifi-kustomization", "unifi", "unifi") + withConstraint := func(change func(map[string]any)) map[string]any { + constraints := fluxPolicyMatchConstraints() + change(constraints) + return constraints + } + kustomizationRule := func(constraints map[string]any) map[string]any { + rules, _ := constraints["resourceRules"].([]any) + rule, _ := rules[0].(map[string]any) + return rule + } + policy := func(constraints map[string]any, conditions ...any) map[string]any { + if constraints == nil { + constraints = fluxPolicyMatchConstraints() + } return map[string]any{ - "resources": map[string]any{ - "namespaces": []any{namespace}, - "names": []any{name}, + "spec": map[string]any{ + "matchConstraints": constraints, + "matchConditions": conditions, }, } } @@ -471,41 +480,94 @@ func TestValidateUnifiPruneExemption(t *testing.T) { { name: "exact narrow exemption", identity: targetIdentity, - document: policy( - resourceExclusion("flux-system", "flux-system"), - resourceExclusion("unifi", "unifi"), - ), + document: policy(nil, bootstrap, unifi), + }, + { + name: "folded expression whitespace is accepted", + identity: targetIdentity, + document: policy(nil, bootstrap, map[string]any{ + "name": "not-unifi-kustomization", + "expression": "!(object.kind == 'Kustomization' &&\n" + + "object.metadata.?namespace.orValue('') == 'unifi' &&\n" + + "object.metadata.name == 'unifi')", + }), }, { name: "missing exemption", identity: targetIdentity, - document: policy(resourceExclusion("flux-system", "flux-system")), + document: policy(nil, bootstrap), wantError: true, }, { - name: "namespace wildcard is not narrow", + name: "namespace wildcard is not narrow", + identity: targetIdentity, + document: policy(nil, bootstrap, map[string]any{ + "name": "not-unifi-kustomization", + "expression": "!(object.kind == 'Kustomization' && object.metadata.name == 'unifi')", + }), + wantError: true, + }, + { + name: "additional broad exemption is rejected", identity: targetIdentity, - document: policy(resourceExclusion("*", "unifi")), + document: policy(nil, bootstrap, unifi, map[string]any{"name": "everything", "expression": "false"}), wantError: true, }, { - name: "additional broad exemption is rejected", + name: "duplicate exemption is rejected", + identity: targetIdentity, + document: policy(nil, unifi, unifi), + wantError: true, + }, + { + name: "exclude resource rules are rejected", identity: targetIdentity, - document: policy( - resourceExclusion("flux-system", "flux-system"), - resourceExclusion("unifi", "unifi"), - resourceExclusion("*", "*"), - ), + document: policy(withConstraint(func(c map[string]any) { + c["excludeResourceRules"] = []any{map[string]any{"resourceNames": []any{"apps"}}} + }), bootstrap, unifi), + wantError: true, + }, + { + name: "namespace selector is rejected", + identity: targetIdentity, + document: policy(withConstraint(func(c map[string]any) { + c["namespaceSelector"] = map[string]any{} + }), bootstrap, unifi), + wantError: true, + }, + { + name: "resource names narrow the Kustomization rule", + identity: targetIdentity, + document: policy(withConstraint(func(c map[string]any) { + kustomizationRule(c)["resourceNames"] = []any{"apps"} + }), bootstrap, unifi), + wantError: true, + }, + { + name: "a dropped UPDATE operation narrows the scope", + identity: targetIdentity, + document: policy(withConstraint(func(c map[string]any) { + kustomizationRule(c)["operations"] = []any{"CREATE"} + }), bootstrap, unifi), + wantError: true, + }, + { + name: "a dropped Kustomization rule narrows the scope", + identity: targetIdentity, + document: policy(withConstraint(func(c map[string]any) { + rules, _ := c["resourceRules"].([]any) + c["resourceRules"] = rules[1:] + }), bootstrap, unifi), wantError: true, }, { name: "unrelated policy", identity: resourceIdentity{ - apiVersion: "kyverno.io/v1", - kind: "ClusterPolicy", + apiVersion: "policies.kyverno.io/v1", + kind: "ValidatingPolicy", name: "other", }, - document: policy(), + document: policy(nil), }, } @@ -564,8 +626,8 @@ func TestUnifiPruneExemptionIsHetznerScoped(t *testing.T) { found := false for _, document := range renderedDocuments { identity := identityOf(document) - if identity.apiVersion != "kyverno.io/v1" || - identity.kind != "ClusterPolicy" || + if identity.apiVersion != "policies.kyverno.io/v1" || + identity.kind != "ValidatingPolicy" || identity.name != "enforce-flux-best-practices" { continue } diff --git a/tests/enforce-flux-best-practices/kyverno-test.yaml b/tests/enforce-flux-best-practices/kyverno-test.yaml new file mode 100644 index 0000000000..d56b3afb0c --- /dev/null +++ b/tests/enforce-flux-best-practices/kyverno-test.yaml @@ -0,0 +1,56 @@ +# Pins the policy's behaviour across its ValidatingPolicy rewrite (#5128). +# A Kustomization needs a non-empty interval, timeout and retry interval, +# prune: true, wait: true and a named source; a HelmRelease needs an interval, +# a timeout, install and upgrade remediation retries (at least 1, or -1 for +# unlimited) and upgrade remediateLastFailure: true. The base policy has no +# UniFi exemption (the Hetzner overlay adds it), and the flux-system bootstrap +# exclusion covers only the Kustomization, never a HelmRelease of that name. +# The github-config health-gating alternative still needs wait set to false: +# omitting it fails. +apiVersion: cli.kyverno.io/v1alpha1 +kind: Test +metadata: + name: enforce-flux-best-practices +policies: + - ../../k8s/bases/infrastructure/cluster-policies/flux/enforce-flux-best-practices.yaml +resources: + - resources.yaml +results: + - policy: enforce-flux-best-practices + resources: + - tenant/ks-compliant + kind: Kustomization + result: pass + - policy: enforce-flux-best-practices + resources: + - tenant/ks-missing-interval + - tenant/ks-missing-timeout + - tenant/ks-missing-retry-interval + - tenant/ks-prune-disabled + - tenant/ks-missing-prune + - tenant/ks-missing-wait + - tenant/ks-missing-source-name + - tenant/ks-empty-interval + - unifi/unifi + - github-config/github-config + kind: Kustomization + result: fail + - policy: enforce-flux-best-practices + resources: + - tenant/hr-compliant + - tenant/hr-unlimited-retries + kind: HelmRelease + result: pass + - policy: enforce-flux-best-practices + resources: + - tenant/hr-missing-interval + - tenant/hr-missing-timeout + - tenant/hr-missing-install-remediation + - tenant/hr-zero-install-retries + - tenant/hr-missing-upgrade-remediation + - tenant/hr-zero-upgrade-retries + - tenant/hr-no-remediate-last-failure + - tenant/hr-missing-remediate-last-failure + - flux-system/flux-system + kind: HelmRelease + result: fail diff --git a/tests/enforce-flux-best-practices/resources.yaml b/tests/enforce-flux-best-practices/resources.yaml new file mode 100644 index 0000000000..e5998e3670 --- /dev/null +++ b/tests/enforce-flux-best-practices/resources.yaml @@ -0,0 +1,388 @@ +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-compliant + namespace: tenant +spec: + interval: 1m + timeout: 5m + retryInterval: 2m + prune: true + wait: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-missing-interval + namespace: tenant +spec: + timeout: 5m + retryInterval: 2m + prune: true + wait: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-missing-timeout + namespace: tenant +spec: + interval: 1m + retryInterval: 2m + prune: true + wait: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-missing-retry-interval + namespace: tenant +spec: + interval: 1m + timeout: 5m + prune: true + wait: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-prune-disabled + namespace: tenant +spec: + interval: 1m + timeout: 5m + retryInterval: 2m + prune: false + wait: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-missing-prune + namespace: tenant +spec: + interval: 1m + timeout: 5m + retryInterval: 2m + wait: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-missing-wait + namespace: tenant +spec: + interval: 1m + timeout: 5m + retryInterval: 2m + prune: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-missing-source-name + namespace: tenant +spec: + interval: 1m + timeout: 5m + retryInterval: 2m + prune: true + wait: true + sourceRef: + kind: OCIRepository +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: ks-empty-interval + namespace: tenant +spec: + interval: "" + timeout: 5m + retryInterval: 2m + prune: true + wait: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: unifi + namespace: unifi +spec: + interval: 1m + timeout: 5m + retryInterval: 2m + prune: false + wait: true + sourceRef: + kind: OCIRepository + name: platform +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-compliant + namespace: tenant +spec: + interval: 1h + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 3 + upgrade: + remediation: + retries: 3 + remediateLastFailure: true +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-unlimited-retries + namespace: tenant +spec: + interval: 1h + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: -1 + upgrade: + remediation: + retries: -1 + remediateLastFailure: true +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-missing-interval + namespace: tenant +spec: + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 3 + upgrade: + remediation: + retries: 3 + remediateLastFailure: true +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-missing-timeout + namespace: tenant +spec: + interval: 1h + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 3 + upgrade: + remediation: + retries: 3 + remediateLastFailure: true +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-missing-install-remediation + namespace: tenant +spec: + interval: 1h + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + upgrade: + remediation: + retries: 3 + remediateLastFailure: true +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-zero-install-retries + namespace: tenant +spec: + interval: 1h + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 0 + upgrade: + remediation: + retries: 3 + remediateLastFailure: true +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-missing-upgrade-remediation + namespace: tenant +spec: + interval: 1h + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 3 +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-zero-upgrade-retries + namespace: tenant +spec: + interval: 1h + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 3 + upgrade: + remediation: + retries: 0 + remediateLastFailure: true +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-no-remediate-last-failure + namespace: tenant +spec: + interval: 1h + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 3 + upgrade: + remediation: + retries: 3 + remediateLastFailure: false +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: hr-missing-remediate-last-failure + namespace: tenant +spec: + interval: 1h + timeout: 10m + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 3 + upgrade: + remediation: + retries: 3 +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: flux-system + namespace: flux-system +spec: + interval: 1h + chart: + spec: + chart: app + sourceRef: + kind: HelmRepository + name: app + install: + remediation: + retries: 3 + upgrade: + remediation: + retries: 3 + remediateLastFailure: true +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: github-config + namespace: github-config +spec: + interval: 1m + timeout: 5m + retryInterval: 2m + prune: true + sourceRef: + kind: OCIRepository + name: platform diff --git a/tests/flux-bootstrap-health-gating/kyverno-test.yaml b/tests/flux-bootstrap-health-gating/kyverno-test.yaml index c7e1a445b1..d16a5e303b 100644 --- a/tests/flux-bootstrap-health-gating/kyverno-test.yaml +++ b/tests/flux-bootstrap-health-gating/kyverno-test.yaml @@ -8,14 +8,12 @@ resources: - resources.yaml results: - policy: enforce-flux-best-practices - rule: kustomization-recommended-settings resources: - github-config/github-config - tenant/healthy kind: Kustomization result: pass - policy: enforce-flux-best-practices - rule: kustomization-recommended-settings resources: - tenant/github-config - github-config/sibling @@ -23,7 +21,6 @@ results: kind: Kustomization result: fail - policy: enforce-flux-best-practices - rule: kustomization-recommended-settings resources: - flux-system/flux-system kind: Kustomization diff --git a/tests/flux-bootstrap-health-gating/missing-reliability/kyverno-test.yaml b/tests/flux-bootstrap-health-gating/missing-reliability/kyverno-test.yaml index 334b156b91..0a4827eb6b 100644 --- a/tests/flux-bootstrap-health-gating/missing-reliability/kyverno-test.yaml +++ b/tests/flux-bootstrap-health-gating/missing-reliability/kyverno-test.yaml @@ -8,7 +8,6 @@ resources: - resources.yaml results: - policy: enforce-flux-best-practices - rule: kustomization-recommended-settings resources: - github-config/github-config kind: Kustomization diff --git a/tests/policy-failure-actions.json b/tests/policy-failure-actions.json index a3f37c33c1..f4639eb507 100644 --- a/tests/policy-failure-actions.json +++ b/tests/policy-failure-actions.json @@ -41,8 +41,7 @@ "validate-pod-security/deny-container-seccomp-unconfined": "Enforce", "validate-replica-ceiling": "Audit", "validate-replica-floor": "Audit", - "enforce-flux-best-practices/kustomization-recommended-settings": "Enforce", - "enforce-flux-best-practices/helmrelease-recommended-settings": "Enforce", + "enforce-flux-best-practices": "Enforce", "restrict-arc-retirement/fence-pool-retirement": "Enforce", "restrict-arc-retirement/fence-scale-set-retirement": "Enforce", "restrict-arc-retirement/fence-listener-retirement": "Enforce",