Summary
Investigate replacing the chainsaw CLI binary in the deployment
validator image with an in-process Go executor for the
assert/error allowlist subset. The goal is a pure-Go validator
image built by ko (no Dockerfile, no GitHub-release fetch, no
sha256 dance, no .settings.yaml build-arg plumbing), with the
read-only allowlist becoming structural rather than a YAML
validator.
Background
PR #1235 (closed #1220) ships the chainsaw binary in the deployment
validator image via a multi-stage Dockerfile that downloads + checksums
the upstream release tarball. Two dispatch paths exist in
validators/chainsaw/runner.go:
- Raw K8s YAML asserts →
assertRawResources →
github.com/kyverno/chainsaw/pkg/engine/checks (in-process Go
library). No binary needed; ko would handle this path today.
- Chainsaw
Test format (apiVersion: chainsaw.kyverno.io/v1alpha1,
kind: Test) → runChainsawBinary → external chainsaw CLI. This
is what the Dockerfile multi-stage exists for.
After #1235 lands, registry-declared content is restricted to a
read-only allowlist (assert and error operations only). That means
the in-process executor would only need to drive those two ops, not
the full chainsaw operation set (script, apply, wait, catch,
finally, ...). Significantly smaller surface than reimplementing
chainsaw.
Proposal
Write a runChainsawTestInProcess shim that:
- Unmarshals the Test YAML to
v1alpha1.Test (already done by
ValidateTestReadOnly in validators/chainsaw/allowlist.go).
- Walks
test.Spec.Steps[].Try[], dispatching each assert /
error to
github.com/kyverno/chainsaw/pkg/engine/checks.Check (the same
library assertRawResources already uses).
- Applies a retry loop with
defaults.AssertRetryInterval until the
step passes or the per-step timeout from defaults.ChainsawAssertTimeout
elapses.
- Returns the same
chainsaw.Result shape as runChainsawBinary.
Acceptance Criteria
- New
runChainsawTestInProcess function in validators/chainsaw/
with parity tests covering every registry check (use the existing
TestValidateTestReadOnly_RegistryContent corpus as the source).
- Output parity proven against the binary path on all in-tree
recipes/checks/*/health-check.yaml files — same Result.Passed
/ Result.Error for the same input + cluster state.
runChainsawBinary deleted; ChainsawBinary interface +
NewChainsawBinary removed.
validators/deployment/Dockerfile deleted; deployment validator
switched to ko (matches cmd/aicr build pattern in the Makefile).
- Makefile
image-validators target collapses the chainsaw build-args
plumbing (no more CHAINSAW_VERSION / per-arch checksums for the
deployment image).
.settings.yaml testing_tools.chainsaw and chainsaw_checksums
remain (still used by tools/setup-tools and make check-health),
but no longer flow into image build args.
- Read-only allowlist enforcement collapses into structural shape:
there is no code path in the validator image that can script or
apply (the in-process executor only implements assert/error).
ValidateTestReadOnly may remain as a defense-in-depth fast-fail
before the executor runs, or be dropped if the executor's
type-switch is itself the gate.
- Image size measured before / after; target ~30-40MB pure-Go vs
the current ~80MB+ with chainsaw binary.
Trade-offs
Pros:
- Smaller image, no Dockerfile, no fetch / checksum dance.
- Read-only allowlist becomes structural — the validator image
cannot invoke state-changing chainsaw ops because the code to
do so isn't compiled in.
ko build matches the rest of the AICR Go binary build path.
- No GitHub-release dependency at image build time.
Cons:
- Takes ownership of a chainsaw-Test-subset executor. When
kyverno/chainsaw bumps semantics for assert (e.g., new
operators, evaluator behavior changes), AICR must track it.
- Spike work for the parity test corpus; semantics-equivalence is
the load-bearing argument, not just type-equivalence.
Open Questions
- Does the
kyverno/chainsaw pkg/engine/checks API surface change
often enough that tracking it is meaningful work, or is it stable
enough to vendor-pin against?
- Is there a stable upstream
pkg/runner/ (or equivalent) that
exposes a Test-step executor we could call directly, instead of
writing our own walker? Worth a quick survey before spike.
- Should the spike target
assert/error only, or also accept
description/timeouts metadata that exists on
v1alpha1.Operation?
Sequencing
Independent of the #660 epic (#1219–#1223). Land after #1235 so the
binary-shipping baseline exists to measure parity against. The
allowlist lint guard (#1223) is independent and can land either
before or after this.
Related
Summary
Investigate replacing the chainsaw CLI binary in the deployment
validator image with an in-process Go executor for the
assert/errorallowlist subset. The goal is a pure-Go validatorimage built by
ko(no Dockerfile, no GitHub-release fetch, nosha256 dance, no
.settings.yamlbuild-arg plumbing), with theread-only allowlist becoming structural rather than a YAML
validator.
Background
PR #1235 (closed #1220) ships the chainsaw binary in the deployment
validator image via a multi-stage Dockerfile that downloads + checksums
the upstream release tarball. Two dispatch paths exist in
validators/chainsaw/runner.go:assertRawResources→github.com/kyverno/chainsaw/pkg/engine/checks(in-process Golibrary). No binary needed;
kowould handle this path today.Testformat (apiVersion: chainsaw.kyverno.io/v1alpha1,kind: Test) →runChainsawBinary→ external chainsaw CLI. Thisis what the Dockerfile multi-stage exists for.
After #1235 lands, registry-declared content is restricted to a
read-only allowlist (
assertanderroroperations only). That meansthe in-process executor would only need to drive those two ops, not
the full chainsaw operation set (
script,apply,wait,catch,finally, ...). Significantly smaller surface than reimplementingchainsaw.
Proposal
Write a
runChainsawTestInProcessshim that:v1alpha1.Test(already done byValidateTestReadOnlyinvalidators/chainsaw/allowlist.go).test.Spec.Steps[].Try[], dispatching eachassert/errortogithub.com/kyverno/chainsaw/pkg/engine/checks.Check(the samelibrary
assertRawResourcesalready uses).defaults.AssertRetryIntervaluntil thestep passes or the per-step timeout from
defaults.ChainsawAssertTimeoutelapses.
chainsaw.Resultshape asrunChainsawBinary.Acceptance Criteria
runChainsawTestInProcessfunction invalidators/chainsaw/with parity tests covering every registry check (use the existing
TestValidateTestReadOnly_RegistryContentcorpus as the source).recipes/checks/*/health-check.yamlfiles — sameResult.Passed/
Result.Errorfor the same input + cluster state.runChainsawBinarydeleted;ChainsawBinaryinterface +NewChainsawBinaryremoved.validators/deployment/Dockerfiledeleted; deployment validatorswitched to
ko(matchescmd/aicrbuild pattern in the Makefile).image-validatorstarget collapses the chainsaw build-argsplumbing (no more
CHAINSAW_VERSION/ per-arch checksums for thedeployment image).
.settings.yamltesting_tools.chainsawandchainsaw_checksumsremain (still used by
tools/setup-toolsandmake check-health),but no longer flow into image build args.
there is no code path in the validator image that can
scriptorapply(the in-process executor only implementsassert/error).ValidateTestReadOnlymay remain as a defense-in-depth fast-failbefore the executor runs, or be dropped if the executor's
type-switch is itself the gate.
the current ~80MB+ with chainsaw binary.
Trade-offs
Pros:
cannot invoke state-changing chainsaw ops because the code to
do so isn't compiled in.
kobuild matches the rest of the AICR Go binary build path.Cons:
kyverno/chainsawbumps semantics forassert(e.g., newoperators, evaluator behavior changes), AICR must track it.
the load-bearing argument, not just type-equivalence.
Open Questions
kyverno/chainsawpkg/engine/checksAPI surface changeoften enough that tracking it is meaningful work, or is it stable
enough to vendor-pin against?
pkg/runner/(or equivalent) thatexposes a Test-step executor we could call directly, instead of
writing our own walker? Worth a quick survey before spike.
assert/erroronly, or also acceptdescription/timeoutsmetadata that exists onv1alpha1.Operation?Sequencing
Independent of the #660 epic (#1219–#1223). Land after #1235 so the
binary-shipping baseline exists to measure parity against. The
allowlist lint guard (#1223) is independent and can land either
before or after this.
Related
would later replace