Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -727,14 +727,23 @@ signer/administrator-key SHA-256, keyless issuer where applicable, the
authenticated bundle SHA-256, canonical UTC verification time, verifier
identity/hash and the complete effective machine-policy fingerprint. Partial,
duplicate, malformed, cross-repository or local-image evidence is rejected.
For an online policy-covered repository, `cb lock` and `cb update` resolve the
For a policy-covered repository, `cb lock` and `cb update` resolve the
exact digest first, execute only the authenticated staged cosign snapshot,
download bounded signature bundles for that immutable reference, and locally
reverify each bundle against the exact digest, cosign predicate, and configured
identity/key. The lockfile is promoted to schema 2 only when exactly one bundle
passes. Zero or multiple matching bundles, verifier failure, malformed output,
or unavailable policy material aborts the refresh without a digest-only
fallback. Project-scoped refresh preserves unrelated schema-2 evidence. Runtime
fallback. Project-scoped refresh preserves unrelated schema-2 evidence.
Schema-4 `offline-bundle` rules additionally require a pinned Sigstore
TrustedRoot. ContainerBin authenticates and privately stages those exact bytes,
then invokes cosign with offline/new-bundle mode and `--trusted-root`, so missing
transparency proof cannot fall back to Rekor or TUF. This mode requires cosign
3.1.0 or newer, an image published with a new-format Sigstore bundle, and a
registry that supports the OCI 1.1 referrers API; legacy signature objects fail
closed. Registry access is still required to download the image signature
bundle; private-registry credentials are not inherited and require the separate
explicit credential bridge. Runtime
executes a policy-covered digest only while its schema-2 evidence matches the
current canonical repository, exact digest, signature mechanism/network mode,
verifier, signer/key identity, issuer and complete machine-policy fingerprint.
Expand Down Expand Up @@ -783,11 +792,12 @@ and overlap rotation. Signed registries are read-only to `cb`; updates must be
provisioned with a matching signature by the administrator. Policy schema 3 can
add repository-bound image-signature requirements. `cb lock` and `cb update`
now authenticate and privately stage the pinned verifier and key bytes, run
online verification against the resolved exact repository digest, independently
verification against the resolved exact repository digest, independently
validate bounded JSON results, record the result as schema-2 evidence, and
authorize runtime use only while that evidence remains fresh against the exact
digest and current effective policy. Offline rules and private-registry
credential bridging remain fail closed.
digest and current effective policy. Policy schema 4 adds pinned Sigstore
TrustedRoot bytes for fail-closed offline-bundle verification. Private-registry
credential bridging remains fail closed.
Lower-precedence registry or command-line choices cannot weaken policy. See
[enterprise machine policy](docs/enterprise-policy.md) for the schema,
ownership rules, normalization behavior and stable diagnostic codes.
Expand Down
13 changes: 7 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,25 +231,26 @@ lockfile-only operations remain separate.

Policy schema 3 adds a canonical repository-bound image-trust rule set plus
absolute SHA-256 pins for an external cosign verifier and any public-key files.
Schema 4 additionally pins the exact Sigstore TrustedRoot used by offline rules.
Rule lookup reuses Docker Hub normalization and selects the most-specific
repository boundary. The policy layer can authenticate the exact configured
cosign file as a bounded regular non-symlink file with the pinned digest, but
does not invoke external code. Authentication yields an immutable byte snapshot,
not a path that could be replaced between checking and execution.

`internal/imagetrust` owns the invocation boundary. It authenticates and stages
only immutable verifier/key snapshots in a protected current-user directory,
only immutable verifier/key/trusted-root snapshots in a protected current-user directory,
uses bounded two-minute child processes with a minimal environment, and asks
the staged verifier to download signature bundles for one exact canonical
`repository@sha256` value. Each bounded bundle is privately staged and passed
back to the same verifier for local verification against the exact digest,
`https://sigstore.dev/cosign/sign/v1` predicate, and configured identity/key;
only those authenticated bundle bytes can become evidence. Staged material is
re-hashed after every use. Only online rules are accepted in this slice.
`offline-bundle` fails before process execution until
policy can pin the complete trusted-root material needed to guarantee a truly
network-independent verification; inherited registry credentials are also not
passed to the verifier yet.
re-hashed after every use. Offline rules additionally require both cosign
offline mode and the exact privately staged TrustedRoot, preventing missing
bundle proof from falling back to transparency-log or TUF access.
Inherited registry credentials are not passed to the verifier; private
registries still require a separate explicit credential bridge.

`cb lock` and `cb update` now invoke this boundary after exact digest resolution
for every policy-covered repository. Evidence production requires exactly one
Expand Down
47 changes: 33 additions & 14 deletions docs/enterprise-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Policy summaries report whether registry signatures are required plus trusted
and revoked key counts. They never print public-key material or signature
contents.

## Schema 3 — repository-bound image trust policy
## Schema 3/4 — repository-bound image trust policy

Schema 3 retains every earlier control and adds the fail-closed policy contract
for Sigstore/cosign image verification. ContainerBin can authenticate the exact
Expand All @@ -222,13 +222,18 @@ signer/key identity, issuer and verifier hash must exactly match current machine
policy. Missing or stale evidence is rejected with
`policy.image_trust_unverified` before Docker execution.

Schema 4 adds the complete pinned trusted-root input required by
`offline-bundle` rules. Existing schema-3 offline rules remain parseable but
fail closed before verifier execution, preserving their prior behavior.

The repository includes an opt-in Windows qualification test for this producer
path. Build it with the `image_trust_e2e` tag and set the five
path. Build it with the `image_trust_e2e` tag and set the six
`CONTAINERBIN_IMAGE_TRUST_E2E*` variables documented by the test. It calls the
real `Lock` and `Update` entry points against a Linux-container Docker Desktop
engine, authenticates and privately stages the selected native cosign binary,
verifies the selected public image, and reloads the resulting schema-2 lockfile
after each operation. The test synthesizes an isolated policy and registry in
verifies the selected public image in both online and pinned-root offline mode,
and reloads the resulting schema-2 lockfile after each operation. The test
synthesizes isolated policies and registries in
its temporary directory; the build-tagged policy loader skips only the
administrator-ownership check and is not compiled into production binaries.
Normal CI does not claim this qualification because GitHub-hosted Windows
Expand All @@ -247,17 +252,20 @@ $env:CONTAINERBIN_IMAGE_TRUST_E2E_COSIGN = "C:\absolute\path\to\cosign.exe"
$env:CONTAINERBIN_IMAGE_TRUST_E2E_IMAGE = "registry.example.com/team/signed-image:immutable-tag"
$env:CONTAINERBIN_IMAGE_TRUST_E2E_ISSUER = "https://issuer.example"
$env:CONTAINERBIN_IMAGE_TRUST_E2E_SUBJECT = "exact-certificate-identity"
$env:CONTAINERBIN_IMAGE_TRUST_E2E_TRUSTED_ROOT = "C:\absolute\path\to\trusted-root.json"
& "$env:TEMP\container-bin-image-trust-e2e.test.exe" `
'-test.v' '-test.run=^TestImageTrustLockAndUpdateWindowsDockerDesktop$'
```

```toml
policy_version = 3
policy_version = 4
require_lock = true
allowed_repositories = ["ghcr.io/acme", "registry.example.com/platform"]

cosign_path = "C:\\Program Files\\ContainerBin\\cosign.exe"
cosign_sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
cosign_trusted_root_path = "C:\\ProgramData\\ContainerBin\\sigstore\\trusted-root.json"
cosign_trusted_root_sha256 = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
image_trust_rules = [
"ghcr.io/acme|keyless|https://token.actions.githubusercontent.com|https://github.com/acme/tools/.github/workflows/release.yml@refs/tags/v1.2.3|online",
"registry.example.com/platform|key|C:\\ProgramData\\ContainerBin\\keys\\platform.pub|abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789|offline-bundle",
Expand All @@ -272,7 +280,8 @@ immutable byte snapshot rather than an executable path. The invocation layer
materializes only that snapshot inside its own protected current-user
staging directory and executes the staged copy; validating and then executing
the mutable configured pathname would leave a replacement race. It re-hashes
the staged verifier and key after execution and treats mutation as failure.
the staged verifier, key and trusted root after execution and treats mutation
as failure.

Each rule has five pipe-delimited fields:

Expand All @@ -289,10 +298,18 @@ Each rule has five pipe-delimited fields:
just as strictly as the cosign executable.
- `NETWORK_MODE` is `online` or `offline-bundle`. `online` permits the verifier
to obtain required Sigstore material from the network. `offline-bundle`
requires complete bundled evidence and forbids network fallback. The current
internal invocation boundary supports `online` only. It rejects
`offline-bundle` before executing cosign because the schema cannot yet pin the
complete Sigstore trusted-root material needed to guarantee no network use.
requires complete bundled evidence and forbids transparency-log or TUF
fallback. It requires cosign 3.1.0 or newer, an image published with a
new-format Sigstore bundle, and a registry that exposes that bundle through
the OCI 1.1 referrers API. Legacy signature objects and bundles produced
without new-bundle support fail closed rather than falling back online. To
enable an `offline-bundle` rule, use schema 4 and provide both
`cosign_trusted_root_path` and
`cosign_trusted_root_sha256`. The root is authenticated, privately staged and
supplied with `--offline=true`, `--new-bundle-format=true` and
`--trusted-root`; an incomplete bundle fails locally. Trusted-root fields
without an offline rule are
rejected rather than silently ignored.

Online execution receives a deliberately minimal environment and does not
inherit registry credential/configuration variables. Public-registry
Expand All @@ -314,11 +331,13 @@ The complete policy-byte fingerprint already covers verifier pins and every
trust rule. Lock schema 2 records that fingerprint beside the exact repository,
digest, verifier hash, signer/key identity, issuer, bundle hash and verification
time, so any policy change will make later lock evidence stale. Policy
summaries report only the rule count and whether cosign is pinned; they do not
print paths, hashes, issuer/subject identities or key material.
summaries report only the rule count and whether cosign and an offline trusted
root are pinned; they do not print paths, hashes, issuer/subject identities or
key material.

Schema 1 and schema 2 remain supported unchanged. Image-trust fields in an
older schema are rejected, and versions newer than 3 fail closed.
Schemas 1-3 remain supported unchanged. Image-trust fields in an older schema
are rejected, offline trusted-root controls require schema 4, and versions
newer than 4 fail closed.

The additional stable foundation errors are:

Expand Down
16 changes: 9 additions & 7 deletions docs/roadmap-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,12 @@ identity/hash and effective trust-policy fingerprint.

Lock schema 2 now defines and strictly validates that evidence shape while
preserving schema-1 reads and fail-closed old-binary/new-lock behavior. The
internal verifier executes authenticated staged cosign/key snapshots for online
exact-digest checks and independently validates bounded JSON output. Lock/update
produce that evidence, and runtime accepts it only while the digest, verifier,
signer/key and complete policy fingerprint still match. Offline verification
remains closed until policy can pin complete trusted-root inputs.
internal verifier executes authenticated staged cosign/key snapshots for
exact-digest checks and independently validates bounded JSON output. Schema 4
pins and stages the complete Sigstore TrustedRoot for `offline-bundle` rules,
which require both cosign offline mode and that exact root. Lock/update produce
the resulting evidence, and runtime accepts it only while the digest, verifier,
signer/key and complete policy fingerprint still match.

Runtime still executes the pinned digest and does not invoke cosign on every
tool launch. A changed digest, verifier or trust-policy fingerprint makes prior
Expand Down Expand Up @@ -325,8 +326,9 @@ is not completion.
- cosign verifier configuration, per-repository policy, schema-2 storage and
online lock-evidence production are implemented;
- runtime freshness authorization is implemented;
- offline verification and explicit private-registry credential bridging
remain.
- offline verification is implemented with schema-4 pinned TrustedRoot
inputs;
- explicit private-registry credential bridging remains.

2. **Remaining RM-31 self-update qualification**
- selection/check, bounded staging and `gh attestation verify` are merged in
Expand Down
12 changes: 7 additions & 5 deletions docs/roadmap-implementation-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ The minimum delivery gate for a code change is:
| RM-34 Cargo expose enhancement | **Intentionally deferred** | Existing expose-all/explicit selection are sufficient; reopen only for concrete unmet use case |
| Linux/macOS hosts | **Demand-gated** | WSL may factor reusable Linux host code; standalone support needs its own demand and qualification |
| Enterprise policy | **Foundation and signed registry shipped / image trust remains** | PRs #75 and #84 shipped the machine-owned constraint layer and authenticated registry; image trust remains |
| Image trust | **Online production and runtime authorization implemented / offline and private-registry work remains** | Add fully pinned offline inputs and an explicit private-registry credential bridge |
| Image trust | **Online/offline production and runtime authorization implemented / private-registry work remains** | Add an explicit private-registry credential bridge |
| Plugin/provider architecture | **Intentionally deferred** | Reopen only after at least two real integrations cannot fit the declarative model |
| WSL2 | **Installer foundation implemented / runtime qualification remaining** | PRs #77 and #83 shipped the fail-closed host boundary and fixed native layout/state identity; explicit read-only/apply filesystem preparation and the fixed-path native install/config/shim lifecycle are available alongside namespace-prefixed/labeled volume identity with proof-bound exact inspect/create/remove/discovery, Docker Desktop integration proof, bounded control requests, constrained attach and exact context-bound wait transports, while command/frontend wiring, stream/terminal/signal semantics and real WSL qualification remain |
| Per-project overlays | **Completed in PR #80** | Add-only digest-bound trust model shipped on the merged enterprise-policy foundation |
Expand Down Expand Up @@ -555,7 +555,7 @@ old-binary/new-lock tests and security-model documentation.
**Implementation status:** lock schema 2 now provides the structured storage,
strict repository/digest/evidence validation, schema-1 compatibility,
old-parser/new-lock rejection tests and security-model documentation. The
internal online invocation slice authenticates pinned verifier/key snapshots,
internal invocation slice authenticates pinned verifier/key snapshots,
stages them under current-user-only permissions, downloads bounded signature
bundles for the exact digest with a minimal environment, and locally re-verifies
each bundle against the digest, cosign predicate and configured identity/key.
Expand All @@ -564,9 +564,11 @@ document to schema 2 only after one authenticated transparency bundle can be
recorded; zero/multiple bundle results and verifier failures abort without a
digest-only fallback. Runtime freshness authorization consumes that evidence
only while its repository, digest, verifier, signer/key identity and complete
policy fingerprint match current machine policy. Offline mode remains blocked
until all trusted-root inputs can be pinned, and private-registry credentials
require an explicit non-ambient bridge.
policy fingerprint match current machine policy. Schema 4 pins the complete
Sigstore TrustedRoot input for `offline-bundle` rules; the verifier stages that
exact snapshot and requires both cosign offline mode and the pinned root so
incomplete proof cannot fall back to transparency-log or TUF access.
Private-registry credentials still require an explicit non-ambient bridge.

## Plugin/provider architecture

Expand Down
12 changes: 7 additions & 5 deletions docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,23 @@ readable, and dangerous to let others edit.
that boundary.
- **Repository-bound image trust policy.** Schema 3 pins an external cosign
executable and declares exact keyless or public-key trust for canonical
repository boundaries. Lock schema 2 can retain strictly validated,
repository boundaries; schema 4 additionally pins the exact Sigstore
TrustedRoot required by offline rules. Lock schema 2 can retain strictly validated,
repository/digest/verifier/policy-bound structured evidence; schema 1 remains
the digest-only compatibility format. The invocation layer executes only
authenticated verifier/key snapshots from a protected private directory,
bounds time and output, scrubs ambient environment state, checks staged bytes
again after execution, and re-verifies every downloaded bundle locally
against the exact digest, cosign predicate, and configured identity/key.
Lock/update records an online result only when
Offline verification authenticates and stages the pinned root and passes both
offline/new-bundle mode and `--trusted-root`, so incomplete proof cannot fall back
to transparency-log or TUF access. Lock/update records a result only when
exactly one authenticated transparency bundle fits the schema-2 evidence
contract. Runtime accepts a covered digest only when that evidence still
matches the current repository, digest, verifier pin, complete policy
fingerprint, mechanism and signer/key identity. Missing or stale evidence
never falls back to digest-only locking. Offline rules refuse process
execution until policy can pin the complete trusted-root and bundle inputs
needed to forbid network fallback.
never falls back to digest-only locking. Private-registry credentials remain
excluded until an explicit non-ambient bridge is implemented.
- **Fail-closed host boundary.** Non-bootstrap work currently runs only in a
native Windows process. Windows binaries launched through detected WSL
interoperability, WSL1, ordinary work on recognized-but-not-yet-enabled native WSL2,
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/image_trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func lockEvidenceFromVerification(result verifiedImageTrust) (*lockfile.ImageTru
if result == nil {
return nil, errors.New("verifier returned no result")
}
if result.NetworkMode() != policy.ImageTrustOnline {
if result.NetworkMode() != policy.ImageTrustOnline && result.NetworkMode() != policy.ImageTrustOfflineBundle {
return nil, fmt.Errorf("verification used unsupported network mode %q", result.NetworkMode())
}
if result.SignatureCount() < 1 {
Expand Down
Loading
Loading