Skip to content

Configuration

gapsign reads its configuration from $GAPSIGN_CONFIG, else ~/.config/gapsign/config.yaml. Secrets are referenced, never inlined: signing keys, KMS credentials, and registry credentials live under ~/.config/gapsign/ (mode 0600) or a KMS/env, and only their paths appear in the config. Unknown keys are a hard error (strict decoding), so a typo can't silently empty a control.

Full example

# Signer backend (signing side):
#   cosign-key   — key-based (file); airgap default
#   cosign-x509  — certificate mode; chains to the corporate CA
backend: cosign-key

# Logical signing identity, referenced by policy rules (rule.signer). Required
# for `gapsign ingest` so the destination repo can be authorized.
signer_name: ci-kms

# Non-secret backend options. Secret material is referenced, not inlined.
backend_opts:
  key: ~/.config/gapsign/ci-kms.key
  # cosign-x509: key + cert + chain
  # cert:  ~/.config/gapsign/leaf.crt
  # chain: ~/.config/gapsign/corp-ca.pem

# Default OCI registry host (informational).
registry: registry.internal.example.com

# The single source of truth for enforcement (see below).
policy: ~/.config/gapsign/policy.yaml

# Registry hosts `gapsign ingest` may pull from. REQUIRED for ingest (an empty
# list fails closed) — bounds the SSRF surface of an operator-supplied source.
# Host-boundary matched: "docker.io.evil.com" never matches "docker.io".
sources:
  - docker.io
  - quay.io

# Optional ingest footprint caps (defaults: 2 GiB / 256 layers).
# max_ingest_bytes: 2147483648
# max_ingest_layers: 256

# Trust bundle for `gapsign policy render` — maps each signer to its verify-side
# material. All values are public (safe to commit via GitOps). A signer missing
# or invalid here fails the render, never fail-open.
trust:
  # Key mode: a PKIX public key.
  ci-kms:
    public_key: ~/.config/gapsign/ci-kms.pub
  # X.509 mode: signature cert must chain to these CA roots and match identity + issuer.
  mirror-x509:
    ca_roots: ~/.config/gapsign/corp-ca.pem
    identity: "gapsign-ci@corp.example"
    issuer: "https://corp.example/oidc"

Policy file

policies:
  - match: "registry.internal/*"        # image-reference glob: exact, "*", or "prefix/*"
    signer: ci-kms                        # a signer named in `trust:`
    namespaces: ["prod", "payments"]      # scope; empty = all namespaces
    require: signed                       # signed (block) | warn (audit-only)

policies is ordered — the first matching rule wins. An image matching no rule is default-denied on a protected namespace.

Secrets

Material Where Committed?
Signing private key ~/.config/gapsign/*.key (0600) or KMS never
Registry / KMS credentials ~/.config/, docker keychain never
Public keys, CA roots referenced by path; embedded into the rendered policy yes (public)

The security residuals ledger (docs/SECURITY_RESIDUALS.md) is local-only and gitignored; it is never committed or deployed.