Skip to content

Gate copyleft base images in Dockerfiles - #579

Open
haiyanmeng wants to merge 1 commit into
agent-substrate:mainfrom
haiyanmeng:no-busybox
Open

Gate copyleft base images in Dockerfiles#579
haiyanmeng wants to merge 1 commit into
agent-substrate:mainfrom
haiyanmeng:no-busybox

Conversation

@haiyanmeng

Copy link
Copy Markdown
Collaborator

Add container-licenses.sh, picked up automatically by verify-all.sh, which fails a Dockerfile that bakes a denied base image into an image we build, via either FROM or COPY --from=.

The distinction the check encodes is between referencing and conveying.

  • Pulling an image at runtime -- the busybox:1.36 init container in manifests/ate-install/atenet-dns.yaml, the workload images under benchmarking, the fixture strings in actortemplate_validation_test.go -- is not distribution: the cluster fetches the image from its upstream registry and we never hand over the binary.
  • Baking one into an image we publish does make us a distributor of whatever that base contains, and for a GPL-2.0 base that obligation cannot be discharged once a release image is public.

DENY_IMAGES starts with busybox (GPL-2.0-only) and alpine (bundles busybox, so it carries the same obligation). Distro bases such as debian and ubuntu are deliberately absent: they ship GPL userland too, but are redistributed verbatim with the distro's own corresponding-source availability. That line is a policy choice rather than a derived legal one, and the script says so; adjust the list to whatever the project's license policy settles on.

The check self-tests before each scan.

No Dockerfile in tree is affected; the three present base on python-slim, golang-bookworm, distroless, node-slim and ubuntu.

Add container-licenses.sh, picked up automatically by verify-all.sh, which
fails a Dockerfile that bakes a denied base image into an image we build,
via either FROM or COPY --from=.

The distinction the check encodes is between referencing and conveying.
Pulling an image at runtime -- the busybox:1.36 init container in
manifests/ate-install/atenet-dns.yaml, the workload images under
benchmarking, the fixture strings in actortemplate_validation_test.go -- is
not distribution: the cluster fetches the image from its upstream registry
and we never hand over the binary. Baking one into an image we publish does
make us a distributor of whatever that base contains, and for a GPL-2.0 base
that obligation cannot be discharged once a release image is public. Only
the latter path is gated. A repo-wide grep for the string would fail on the
runtime references, which are fine, and would teach the wrong rule.

DENY_IMAGES starts with busybox (GPL-2.0-only) and alpine (bundles busybox,
so it carries the same obligation). Distro bases such as debian and ubuntu
are deliberately absent: they ship GPL userland too, but are redistributed
verbatim with the distro's own corresponding-source availability. That line
is a policy choice rather than a derived legal one, and the script says so;
adjust the list to whatever the project's license policy settles on.

Parsing handles what a grep gets wrong: --platform and other FROM flags,
registry prefixes and digests during normalization, stage aliases and
numeric indices in COPY --from=, scratch, unresolvable build args, and
comments. Matching on the exact repository name keeps a reference such as
mycorp/busybox-tools from tripping it.

The check self-tests before each scan, because its failure mode is otherwise
silent: if an edit breaks the parsing so that nothing ever matches, it does
not error, it prints that the tree is clean and exits 0, forever. A gate
that has quietly stopped gating is worse than no gate, because it carries
exactly the false confidence the check was written to avoid. So the parser
runs against a fixture with a known answer first, and refuses to report a
clean tree if it no longer behaves.

That fixture is a heredoc written to a temporary file rather than a
checked-in testdata/Dockerfile, because a real one would be matched by this
script's own git ls-files glob and would need an exclusion -- and that
exclusion would be a place to hide a Dockerfile from the check. Its last two
lines name a build stage `alpine` and copy from it; that case looks
redundant next to the existing `build` alias but is the only one that makes
losing stage tracking observable, since an alias that is not itself a denied
name normalizes to a harmless string.

Mutation testing confirms the fixture catches removal of the digest strip,
the registry-prefix strip, the tag strip, the FROM flag skip, stage-alias
tracking, the comment skip, the scratch and build-arg skips, and a widening
of the exact repository-name match to a substring match. Dropping the
numeric stage-index guard is deliberately not caught: an index normalizes to
a number that can never equal a denied name, so the guard documents intent
rather than carrying logic.

No Dockerfile in tree is affected; the three present base on python-slim,
golang-bookworm, distroless, node-slim and ubuntu. No test framework is
introduced; the repository has none for its 67 hack scripts, and one script
does not justify one. Verified clean under shellcheck at the repo's pinned
0.9.0 and under boilerplate.sh.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant