T-0.5 Kyverno L0 policies + CLI tests + admission scaffolding (R-TEST-01, R-NET-01, R-SEC-01/02, R-IDP-02, R-GITOPS-02)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
1 parent
bb08d0e048
commit
bb2e9fc931
28 files changed
+1243
-2
No files matched your search
@@ -0,0 +1,39 @@
|
||||
# In-cluster admission enforcement (scaffolding — Phase 2 / T-2.7)
|
||||
|
||||
Installs Kyverno and runs the **same L0 policies** as an in-cluster **admission
|
||||
controller**, so a non-conforming resource that somehow bypassed CI still cannot be
|
||||
admitted (defense in depth — `docs/07`, ADR-0011). **Not applied in Phase 0** (no
|
||||
cluster exists yet); this is scaffolding wired up in task **T-2.7**.
|
||||
|
||||
## Enforcement COMPLEMENTS, never REPLACES, behavior tests
|
||||
|
||||
> A policy admitting a manifest is **not** proof the runtime behaves (`docs/14`
|
||||
> principle 1, R-TEST-01). Kyverno confirms a resource is *shaped* correctly; it does
|
||||
> **not** prove default-deny actually blocks a packet, that the VPN kill-switch fails
|
||||
> closed, or that a PVC survives a rebuild. Those are proven only by the **L2/L3
|
||||
> Chainsaw** behavior and fault-injection tests (`conformance/`, ADR-0011), which
|
||||
> remain **mandatory**. Admission is a gate, not a guarantee of behavior.
|
||||
|
||||
## Authored once, shared
|
||||
|
||||
`kustomization.yaml` references the policy files in `../policies/` **verbatim** — the
|
||||
identical objects the CI `kyverno test` gate validates — plus the Kyverno install.
|
||||
No policy is forked between CI and admission, with **one deliberate exception**:
|
||||
|
||||
## The one deliberate CI vs admission divergence — Secrets
|
||||
|
||||
`../policies/disallow-plaintext-secrets.yaml` (CI) requires a top-level `sops` block
|
||||
on the Secret manifest. Flux **decrypts SOPS before admission**, so that block is
|
||||
gone by the time the API server sees the Secret — the CI policy cannot run in-cluster.
|
||||
The admission equivalent is `secret-provenance.yaml`, which enforces **provenance**
|
||||
(Secret came from Flux / is an exempt controller Secret) instead of ciphertext. This
|
||||
is escalated in the T-0.5 report because `docs/07`'s literal wording ("reject a
|
||||
plaintext Secret at admission") is not achievable post-decrypt.
|
||||
|
||||
## Operational note (from ADR-0011)
|
||||
|
||||
Admission enforcement adds a failure mode: a bad policy can block legitimate
|
||||
reconciliation. Mitigations: policies ship with their own `kyverno test` suite (this
|
||||
directory's `../tests`), roll out via GitOps, and the webhook `failurePolicy: Fail`
|
||||
is chosen so the system fails **closed** (coordination-protocol.md #8) — validate for
|
||||
bootstrap deadlocks in dev/test before prod.
|
||||
@@ -0,0 +1,51 @@
|
||||
# SCAFFOLDING for a later phase (Phase 2, task T-2.7) -- NOT applied in Phase 0
|
||||
# (no cluster exists yet). Installs Kyverno so the SAME L0 policies run in-cluster
|
||||
# as an admission controller (defense in depth per docs/07, ADR-0011). Flux
|
||||
# HelmRelease skeleton; chart/app versions are pinned and bumped via the update path.
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: kyverno
|
||||
namespace: kyverno
|
||||
spec:
|
||||
interval: 24h
|
||||
url: https://kyverno.github.io/kyverno/
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: kyverno
|
||||
namespace: kyverno
|
||||
spec:
|
||||
interval: 1h
|
||||
chart:
|
||||
spec:
|
||||
chart: kyverno
|
||||
version: "3.3.x" # pin to the exact chart the operator vendors
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: kyverno
|
||||
namespace: kyverno
|
||||
install:
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: 3
|
||||
values:
|
||||
# HA admission controller so a single pod restart cannot open an enforcement gap.
|
||||
admissionController:
|
||||
replicas: 3
|
||||
backgroundController:
|
||||
enabled: true # needed for generate (default-deny baseline) + sync
|
||||
reportsController:
|
||||
enabled: true
|
||||
cleanupController:
|
||||
enabled: true
|
||||
# Fail-CLOSED: if the webhook is unreachable, reject rather than silently admit
|
||||
# non-conforming resources (coordination-protocol.md #8). Tune per-resource in
|
||||
# the policies' webhook config if a bootstrap deadlock appears.
|
||||
config:
|
||||
webhooks:
|
||||
- failurePolicy: Fail
|
||||
@@ -0,0 +1,18 @@
|
||||
# In-cluster enforcing bundle (SCAFFOLDING for T-2.7; not applied in Phase 0).
|
||||
# Installs Kyverno and applies the SAME policies authored for the CI L0 gate as an
|
||||
# admission controller -- authored once, shared between CLI and admission (ADR-0011).
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: kyverno
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- helmrelease.yaml
|
||||
# --- policies SHARED verbatim with the CI L0 gate ---
|
||||
- ../policies/require-default-deny-baseline.yaml
|
||||
- ../policies/validate-default-deny-shape.yaml
|
||||
- ../policies/restrict-host-namespaces.yaml
|
||||
- ../policies/require-egress-class-label.yaml
|
||||
- ../policies/require-auth-mode-label.yaml
|
||||
# --- secrets: the admission variant REPLACES the CI-only sops-block policy ---
|
||||
# (../policies/disallow-plaintext-secrets.yaml is CI-only; see secret-provenance.yaml)
|
||||
- secret-provenance.yaml
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: kyverno
|
||||
labels:
|
||||
# Platform namespace -- deliberately NOT an application namespace, so the
|
||||
# app-scoped policies do not apply to Kyverno's own workloads.
|
||||
homelab.keithjackson.dev/app-namespace: "false"
|
||||
pod-security.kubernetes.io/enforce: restricted
|
||||
@@ -0,0 +1,57 @@
|
||||
# Serves: R-SEC-01 (in-cluster admission variant)
|
||||
#
|
||||
# WHY THIS EXISTS (and diverges from the CI policy):
|
||||
# The CI policy `disallow-plaintext-secrets` requires a top-level `sops` block on
|
||||
# the Secret manifest. That works in CI (it scans the encrypted git manifest) but
|
||||
# is IMPOSSIBLE to satisfy at admission: Flux decrypts SOPS *before* the Secret
|
||||
# reaches the API server, so an admitted Secret never has a `sops` block. Applying
|
||||
# the CI policy in-cluster would reject every real Secret.
|
||||
#
|
||||
# So the in-cluster invariant is PROVENANCE, not ciphertext: a Secret in an
|
||||
# application namespace must have been produced by the SOPS/Flux path (it carries
|
||||
# Flux's kustomize labels) OR be an exempt controller-generated Secret. A Secret
|
||||
# hand-applied with `kubectl` (the plaintext-into-the-cluster path) has neither and
|
||||
# is rejected. This is the enforceable reading of docs/07's "reject a plaintext
|
||||
# Secret at admission" -- flagged as an escalation in the T-0.5 report.
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: secret-provenance
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require managed provenance on app-namespace Secrets
|
||||
policies.kyverno.io/category: Secrets
|
||||
policies.kyverno.io/severity: critical
|
||||
homelab.keithjackson.dev/requirements: R-SEC-01
|
||||
homelab.keithjackson.dev/scope: admission-only
|
||||
spec:
|
||||
validationFailureAction: Enforce
|
||||
background: false
|
||||
rules:
|
||||
- name: secrets-must-be-flux-managed
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Secret
|
||||
namespaceSelector:
|
||||
matchLabels:
|
||||
homelab.keithjackson.dev/app-namespace: "true"
|
||||
# Controller-generated Secrets (SA tokens, cert-manager, Helm) are exempt.
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
selector:
|
||||
matchLabels:
|
||||
homelab.keithjackson.dev/secret-managed-by: controller
|
||||
- resources:
|
||||
selector:
|
||||
matchExpressions:
|
||||
- key: kustomize.toolkit.fluxcd.io/name
|
||||
operator: Exists
|
||||
validate:
|
||||
message: >-
|
||||
Secrets in application namespaces must be delivered by Flux from
|
||||
SOPS-encrypted git (they carry a kustomize.toolkit.fluxcd.io/name label) or
|
||||
be exempt controller-generated Secrets. Hand-applied (plaintext) Secrets are
|
||||
rejected (R-SEC-01, docs/07).
|
||||
deny: {}
|
||||
Reference in new issue
Block a user