Mirror ingest (Path B)¶
Bring a third-party image into the internal registry and re-attest it under the enterprise's own key, so that at admission there is exactly one trust root to check regardless of where an image came from.
Flow¶
$ gapsign ingest -config config.yaml docker.io/library/nginx:1.25 mirror.internal/docker.io/nginx:1.25
ingested mirror.internal/docker.io/nginx@sha256:...
from: docker.io/library/nginx:1.25
signed by: mirror-x509
signature: mirror.internal/docker.io/nginx@sha256:...
gapsign ingest:
- Authorizes the destination against the policy (
AuthorizeSigner, default-deny) and checks the source registry host against the required allowlist — both before any network call. - Pins the source to a digest, bounds its footprint, and mirrors that exact digest into the destination (content-addressed, digest-verified copy).
- Confirms the destination now serves that digest, then signs it and pushes the signature.
Configuration¶
Ingest is governed and fails closed without it — a policy, a signer identity, and a source allowlist are all required:
backend: cosign-key
signer_name: mirror-x509 # must match a policy rule's signer
backend_opts:
key: ~/.config/gapsign/mirror.key
policy: ~/.config/gapsign/policy.yaml
sources: # SSRF guard — hosts ingest may pull from
- docker.io
- quay.io
# max_ingest_bytes / max_ingest_layers cap the mirrored footprint (defaults 2GiB / 256)
Security posture¶
- Digest-pinned. The source is pinned before the copy and that exact digest is what gets signed — the signature binds the content actually mirrored, not whatever a mutable source tag later points to.
- SSRF-bounded. The source host must be on the
sourcesallowlist (host-boundary matched, sodocker.io.evil.comnever matchesdocker.io); enforced before any network call, and an empty allowlist fails closed. - Footprint-bounded. A compromised-but-allowlisted upstream cannot exhaust the internal registry: child-manifest count and index depth are capped from the manifest before any child is fetched, and an index child of an unaccountable media type is refused.
- No shell. All registry I/O is via Go libraries — there is no
skopeosubprocess and therefore no argument/option-injection surface. (gapsign fits the same mirror flow; it just does the copy itself.)