Skip to content
CommonLayer
Documentation menu
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

#StepProves
0Preflight: API, project, both registries, and that the registry's token realm is this APIthe drill cannot pass against the wrong backend
1A cosign key pair (once); a copy of projects/nginx with the drill's manifest choices (below); clctl manifest validate; clctl dev manifest-syncthe manifest the platform verifies against declares the drill's public key; the platform stores the key from the repository file it names
2Signs in as the project admin, creates a project CI service account (role maintainer) and a one-day API token with artifacts:submitthe submission path a maintainer sets up for CI works through the REST API with sessions and CSRF
3Builds 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)
4SPDX and CycloneDX SBOMs with the project's scripts/generate-sbom.shthe SBOM step works on a pushed digest
5cosign sign, cosign attest (SPDX, CycloneDX, SLSA v1 provenance) with the drill keythe worker's verification accepts signatures and attestations made by cosign
6clctl 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 promotesevery image check passes for a correctly built and signed image; mirroring through the registry's token authentication (promoter tokens signed by the worker) works
7Anonymous 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 datasignatures and attestations survive mirroring unchanged; anonymous users get pull and nothing else; the public API serves what a consumer needs
8The 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
9Negative case: an unsigned image (no signature, SBOM or provenance) pushed to the source repository and submitted as a new revisionit 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 by e2e-publish.sh itself; 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 the projects.sync_manifest job — 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/amd64 only; the release workflow builds linux/amd64 and linux/arm64 on native runners.
  • Push by digest. The project's build.sh pushes by digest with a BuildKit container builder in CI; Docker's own builder cannot, so the drill builds into the local store and pushes with docker 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.