The committed L2/L3 Job pins `ghcr.io/kyverno/chainsaw:v0.2.12` with `command: ["./run.sh"]`. Those two lines cannot both be true. That image carries the chainsaw binary and essentially nothing else -- no bash, no python3, no kubectl, no git -- so the Job would have failed at exec of `./run.sh`, before a single test ran. Nothing in the tree said so. This adds the image that makes the Job runnable (plan docs/plans/l2-l3-conformance-ci-wiring.md section 3 item 1): * conformance/incluster/Containerfile -- bash + python3 + chainsaw + kubectl + git-core + tar/gzip/coreutils (the base64-tar results channel of item 3). Two-stage: a full Fedora fetches the release binaries and verifies kubectl's published sha256; the runtime layer is fedora-minimal and carries no downloader. Versions are --build-arg-injected from infra/devcontainer/versions.env (CHAINSAW_VERSION, KUBECTL_VERSION, FEDORA_VERSION) -- the single source of truth -- so the chainsaw and kubectl in the runner are the same pins the toolbox gate runs with. No new pin is invented here, and the ARG defaults are EMPTY so a build that forgets one fails loudly instead of baking a stale version. * conformance/incluster/build-image.sh -- builds it, PROVES the toolchain by executing it inside the image, and prints the digest. It never pushes: the registry credential is not an agent's to hold and publishing the artifact a fail-closed policy then trusts is an operator action. It prints the exact `podman push --digestfile ...` line instead. * conformance/versions.env -- CONFORMANCE_RUNNER_IMAGE, with the digest deliberately EMPTY and a loud block saying the operator fills it in after the push. Empty is a visible failure in whatever renders the Job; a guessed digest would be an ImagePullBackOff dressed up as a cluster fault. WHAT THE ADMISSION BOUNDARY FORCED, AND WHY THE IMAGE OBEYS IT platform/kyverno/admission/conformance-trigger-job-shape.yaml is Enforce + failurePolicy: Fail and it is the R-SEC-02 boundary. It is NOT modified here. Two of its clauses are load-bearing on the image itself: * `runAsNonRoot: true` with no explicit runAsUser means the kubelet rejects an image whose USER is a name it cannot resolve to a uid. So USER is NUMERIC (65532), not a friendly name. * The pod is readOnlyRootFilesystem and the policy admits only the `workspace` and `results` volumes -- there is no writable /tmp and one cannot be added without editing the boundary. So HOME, XDG_CACHE_HOME, XDG_CONFIG_HOME and TMPDIR all point into /workspace, and git's safe.directory config is written to /etc/gitconfig at BUILD time because a runtime --global write would fail. VERIFIED BY RUNNING IT, not by a successful build As uid 65532 with --read-only: bash 5.3.9, Python 3.14.7, git 2.55.0, kubectl v1.36.4 (client), chainsaw 0.2.12; / confirmed read-only, /workspace writable, uid confirmed non-zero. Then the real thing -- `./run.sh --lint`, `--coverage-only` and `--layer l2 --no-cluster` all rc 0 inside the image against this worktree, emitting coverage.md/.json and report.junit.xml. KNOWN LIMIT, FLAGGED NOT PAPERED OVER run.sh runs the selected offline L0/L1 checks before it touches the cluster for ANY selection that is not offline-only, and those checks shell out to flux, promtool, kyverno and kustomize, which this image does not carry. Baking the whole devcontainer toolchain into the runner would roughly double its size to re-run work ci/l0.sh and ci/l1.sh already did offline in the same CI run. So the Job must select the cluster-gated layers only (`--layer l2` / `--layer l3`); `--layer all` and a bare `--requirements <IDs>` would drag the L0 checks in and fail on a missing tool. `--layer l2` is verified above to resolve the offline selection to nothing. Choosing the Job's args belongs to T-6.1b; the constraint is documented in the Containerfile header and the incluster README rather than left for someone to discover from a pod log. chainsaw-job.yaml's `image:` is left stale ON PURPOSE -- there is no digest to put there yet, and the trigger renders that template per run (plan item 4). The README now says so instead of letting the placeholder read as a pin. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
68 lines
4.2 KiB
Bash
68 lines
4.2 KiB
Bash
# Pinned tool versions for the conformance harness (R-TEST-02: deterministic).
|
|
# The harness is only reproducible if the toolchain is pinned. Bumps flow through the
|
|
# normal update path (ADR-0011) with the suite re-green as the gate — never bumped
|
|
# silently. `run.sh` sources this file; the in-cluster Chainsaw Job image (see
|
|
# incluster/chainsaw-job.yaml) MUST use the same CHAINSAW_VERSION.
|
|
|
|
# Chainsaw — L1 rendering / L2 behavioral / L3 fault-injection runner (ADR-0011).
|
|
CHAINSAW_VERSION="v0.2.12"
|
|
# The UPSTREAM chainsaw image. It is NOT what the in-cluster Job runs — see
|
|
# CONFORMANCE_RUNNER_IMAGE below. It carries the chainsaw binary and essentially nothing
|
|
# else (no bash, no python3, no kubectl, no git), so `command: ["./run.sh"]` on this image
|
|
# fails at exec. Kept only as the provenance of the chainsaw binary we rebuild from.
|
|
CHAINSAW_IMAGE="ghcr.io/kyverno/chainsaw:v0.2.12"
|
|
|
|
# kubectl — used by run.sh for cluster reachability preflight and by probe scripts.
|
|
KUBECTL_VERSION="v1.36.4"
|
|
|
|
# Kyverno CLI — L0 static/policy gate (owned by T-0.5, referenced here for a single
|
|
# pinned toolchain view). Not invoked by run.sh yet.
|
|
KYVERNO_VERSION="v1.19.0"
|
|
|
|
# Python 3 — coverage generator + JUnit emitter use stdlib only (no pip deps), so the
|
|
# only constraint is a modern interpreter.
|
|
PYTHON_MIN="3.9"
|
|
|
|
# --- In-cluster conformance runner image -------------------------------------
|
|
# Built from conformance/incluster/Containerfile by conformance/incluster/build-image.sh.
|
|
# This — not CHAINSAW_IMAGE — is what the L2/L3 Job runs (bash + python3 + chainsaw +
|
|
# kubectl + git). Closes docs/plans/l2-l3-conformance-ci-wiring.md §3 item 1.
|
|
#
|
|
# THE REPOSITORY PATH IS NOT A PREFERENCE. platform/kyverno/admission/
|
|
# conformance-trigger-job-shape.yaml is a fail-closed Enforce ClusterPolicy (R-SEC-02) that
|
|
# ADMITS a conformance-trigger Job only when its image matches
|
|
# gitea.home.keithjackson.dev/home-network/conformance-runner@sha256:*
|
|
# Any other path, and any TAG reference, is DENIED at admission. Do not "tidy" these values.
|
|
CONFORMANCE_RUNNER_IMAGE_REPO="gitea.home.keithjackson.dev/home-network/conformance-runner"
|
|
# Build handle only. Admission never sees the tag; a Job must reference the digest.
|
|
CONFORMANCE_RUNNER_IMAGE_TAG="chainsaw0.2.12-kubectl1.36.4-fc44"
|
|
#
|
|
# ############################################################################
|
|
# # DIGEST DELIBERATELY EMPTY — THE IMAGE HAS NOT BEEN PUBLISHED YET. #
|
|
# ############################################################################
|
|
# The image has been BUILT and its toolchain verified locally (non-root uid 65532,
|
|
# read-only rootfs: bash 5.3.9, Python 3.14.7, git 2.55.0, kubectl v1.36.4, chainsaw 0.2.12,
|
|
# and `./run.sh --layer l2 --no-cluster` completing with rc 0). It has NOT been pushed:
|
|
# pushing needs a registry credential, and publishing the artifact a fail-closed security
|
|
# boundary then trusts is an OPERATOR action, not an agent's.
|
|
#
|
|
# OPERATOR, to complete the pin:
|
|
# 1. ./conformance/incluster/build-image.sh # rebuild, verify, print the commands
|
|
# 2. podman login gitea.home.keithjackson.dev
|
|
# 3. podman push --digestfile /tmp/conformance-runner.digest \
|
|
# "${CONFORMANCE_RUNNER_IMAGE_REPO}:${CONFORMANCE_RUNNER_IMAGE_TAG}"
|
|
# 4. Set CONFORMANCE_RUNNER_IMAGE_DIGEST below to `cat /tmp/conformance-runner.digest`
|
|
# — the digest the REGISTRY returned, not the local manifest digest the build printed.
|
|
# 5. Land steps 4 in a PR (this file is GitOps input, not a live edit).
|
|
#
|
|
# UNTIL THEN: this is empty on purpose, NOT a placeholder to be guessed at. An empty value
|
|
# makes any consumer that renders the Job fail visibly; a fake or stale digest would instead
|
|
# produce an ImagePullBackOff that looks like a cluster fault. Do not invent one.
|
|
CONFORMANCE_RUNNER_IMAGE_DIGEST=""
|
|
CONFORMANCE_RUNNER_IMAGE="${CONFORMANCE_RUNNER_IMAGE_DIGEST:+${CONFORMANCE_RUNNER_IMAGE_REPO}@${CONFORMANCE_RUNNER_IMAGE_DIGEST}}"
|
|
#
|
|
# AFTERWARDS, TIGHTEN THE BOUNDARY: the header of conformance-trigger-job-shape.yaml asks for
|
|
# its `@sha256:*` wildcard to be replaced with the LITERAL digest once the image exists —
|
|
# that closes the residual "anyone who can push to this path" gap. Separate PR, human
|
|
# reviewed, because it edits the security boundary itself.
|