On this page
Publish drill
scripts/dev/e2e-publish.sh (make e2e-publish) runs the submission and promotion
pipeline of 06 §3 with the real tools — cosign,
Trivy, Grype, syft, Helm, Docker and CNCF Distribution — instead of the fakes the unit,
integration and acceptance tests use. It is the only place where a project's build is
signed, attested, submitted, verified, scanned, mirrored and promoted end to end on one
machine, and then consumed the way a user of the CommonLayer registry would.
Running it
make up && make dev-backend && make seed # infrastructure, backend, development data
make -C projects/base build # the base image, pinned by its local digest
make e2e-publish # the drill; transcript in var/drill/transcript.md
The worker needs the Trivy and Grype databases under COMMONLAYER__SECURITY__SCANNER_CACHE_DIR
(.env: ~/.cache; download once with trivy image --download-db-only --cache-dir ~/.cache/trivy
and GRYPE_DB_CACHE_DIR=~/.cache/grype/db grype db update, about 5 GB together).
The script is idempotent: it keeps its key pair and state in var/drill/, reuses the
image revision of the previous run when the rebuild has the same digest (the submission
then answers with the existing, promoted submission), reuses a promoted chart, and
re-submits the unsigned negative case, which answers with the existing rejection.
Next to someone else's backend. The CommonLayer registry's token realm must be the
API the drill talks to, or pull authorization would be decided by another database. The
registry-alt Compose profile runs a second token-auth registry on :5003 whose realm
is the API on CL_ALT_API_PORT (default 18080), and scripts/dev/run-backend.sh runs an
independent backend instance with its own database and ports (CL_DEV_INSTANCE,
CL_DEV_DATABASE_URL, CL_DEV_HTTP_PORT, CL_DEV_GRPC_PORT, CL_DEV_OPS_PORTS,
CL_DEV_REGISTRY); the script's header shows the commands.
What it does and what each step proves
| # | Step | Proves |
|---|---|---|
| 0 | Preflight: API, project, both registries, and that the registry's token realm is this API | the drill cannot pass against the wrong backend |
| 1 | A cosign key pair (once); a copy of projects/nginx with the drill's manifest choices (below); clctl manifest validate; clctl dev manifest-sync | the manifest the platform verifies against declares the drill's public key; the platform stores the key from the repository file it names |
| 2 | Signs in as the project admin, creates a project CI service account (role maintainer) and a one-day API token with artifacts:submit | the submission path a maintainer sets up for CI works through the REST API with sessions and CSRF |
| 3 | Builds nginx with the project's scripts/build.sh (one version line, linux/amd64) and pushes it to the source registry (localhost:5001, standing in for GHCR) | the project build produces an image the policy accepts (non-root, labels, platforms) |
| 4 | SPDX and CycloneDX SBOMs with the project's scripts/generate-sbom.sh | the SBOM step works on a pushed digest |
| 5 | cosign sign, cosign attest (SPDX, CycloneDX, SLSA v1 provenance) with the drill key | the worker's verification accepts signatures and attestations made by cosign |
| 6 | clctl submit --wait; the worker fetches by digest, verifies with cosign, verifies attestations, stores SBOMs and statements, scans with Trivy and Grype (real databases), evaluates the 17 policy checks, mirrors into the CommonLayer registry, re-verifies there and promotes | every image check passes for a correctly built and signed image; mirroring through the registry's token authentication (promoter tokens signed by the worker) works |
| 7 | Anonymous docker pull by digest from the CommonLayer registry (token flow, pull-only token); cosign verify and cosign verify-attestation (all three types) against the CommonLayer reference; SBOM download; verify instructions; artifact page data | signatures and attestations survive mirroring unchanged; anonymous users get pull and nothing else; the public API serves what a consumer needs |
| 8 | The nginx Helm chart: packaged by the project's scripts/publish-chart.sh with the promoted digest, pushed as OCI, signed, provenance attested, submitted, promoted; helm pull from the CommonLayer registry; the chart's default image is <registry>/nginx@<promoted digest> | chart.images_pinned and chart.schema against a real chart; charts are distributed from the CommonLayer registry |
| 9 | Negative case: an unsigned image (no signature, SBOM or provenance) pushed to the source repository and submitted as a new revision | it is rejected with the failed checks listed, and the CommonLayer registry answers 404 for its digest: nothing unverified is published |
The transcript (var/drill/transcript.md) lists every policy check result, the
digests, the anonymous token's access list and the duration of each step.
What it does not prove
- Provenance is written by the script. Locally the SLSA v1 statement (in the shape of
actions/attest-build-provenance) is produced and signed bye2e-publish.shitself; it proves that the platform parses, checks and preserves provenance, not that a build service attested the build. In CI the provenance comes from GitHub's attestation action and is signed keylessly by the workflow identity. - Keyless signing is not exercised. The drill signs with a key pair and without a
transparency-log entry (
COMMONLAYER__REGISTRY__COSIGN_REQUIRE_TRANSPARENCY_LOG=false, refused in production). Keyless verification needs Fulcio, Rekor, the Sigstore TUF root and a GitHub Actions OIDC identity, none of which a sandbox without direct internet access reaches. The verify instructions the platform publishes (cosign verify --key …) assume a transparency-log entry, as production requires; against the drill's artifacts they need--insecure-ignore-tlog=true. - The manifest is synced from a local file.
clctl dev manifest-sync(development and test only) runs the platform's manifest sync — the same validation, public-key extraction and storage as theprojects.sync_manifestjob — but reads the files from the drill's copy instead of fetching them from GitHub, which the SSRF-safe client cannot reach from this sandbox (it does not use the egress proxy, by design). A maintainer commits the manifest and the key to the repository and the platform fetches them. - The project and its verified repository come from the development seed
(
github.com/commonlayer-dev/nginx, verified through the seed's development boundary); repository verification against GitHub is not exercised. - One platform. The drill's manifest declares
linux/amd64only; the release workflow buildslinux/amd64andlinux/arm64on native runners. - Push by digest. The project's
build.shpushes by digest with a BuildKit container builder in CI; Docker's own builder cannot, so the drill builds into the local store and pushes withdocker push(a single-platform manifest, not an index). - The drill does not measure performance; the scan wait is bounded by
registry.scan_poll_interval_secs(30 s).
The drill's manifest choices
The copy of projects/nginx/commonlayer.yaml in var/drill/repository/ differs from the
project's in exactly these fields: registry.source = localhost:5001/devseed/nginx (the
source repository the development seed defined), registry.destinations = the CommonLayer
registry under test, images[].platforms = [linux/amd64], images[].base.digest = the
local base digest, and security.signing = {key: {publicKey: cosign.pub}}.
Results of the runs recorded for WP-70g: docs/status/WP-70g.md.