Skip to content

feat: secure image admission — Trust Policy + digest/cosign foundation (1/3, #4638)#4656

Draft
alxwlw wants to merge 14 commits into
Dokploy:canaryfrom
alxwlw:feat/secure-admission-foundation
Draft

feat: secure image admission — Trust Policy + digest/cosign foundation (1/3, #4638)#4656
alxwlw wants to merge 14 commits into
Dokploy:canaryfrom
alxwlw:feat/secure-admission-foundation

Conversation

@alxwlw

@alxwlw alxwlw commented Jun 17, 2026

Copy link
Copy Markdown

Summary

Foundation (PR 1 of 3) for native, opt-in supply-chain admission at deploy time, addressing #4638: resolve image tags to immutable @sha256 digests and verify their cosign/sigstore signatures before containers run, failing closed.

This PR adds the reusable building blocks; it does not yet wire them into the deploy pipeline (that's PR 2 = Applications, PR 3 = Compose). No existing deploy path changes — the new code is opt-in and currently uncalled by design, so this is intentionally a draft to align on the approach before the pipeline integration lands.

What's included

  • trustPolicy entity (org-scoped, modeled on registry): schema + migration + zod + CRUD service + admin-only tRPC router + an organization-settings UI. Holds cosign verification config: keyed (public key) or keyless (cert-identity regexp + OIDC issuer), ignoreTlog for private/air-gapped registries, and an optional cosignImage override.
  • packages/server/src/utils/admission/ module
    • image-ref.ts — digest-aware reference parsing (extracted from the existing webhook parser and shared; webhook behavior unchanged).
    • resolve-digest.ts — pull-then-inspect digest resolution with repository-matched RepoDigests selection (never a blind [0]).
    • verify-signature.ts — builds the cosign argv and runs cosign as an ephemeral docker run container.
    • admit.tsadmitImage() orchestration (resolve → verify → pinned ref), fail-closed.

Design notes

  • Ephemeral cosign, no host/image changes. cosign runs as docker run <pinned-image> verify …, which works for both local and remote/SSH deploys (both have Docker). The dokploy image is untouched; cloud (no Docker) simply won't use the feature.
  • Distroless-safe. The cosign image has no shell — the command is an argv array (execFile locally), never sh -c.
  • Fail-closed by construction. Resolution/verification throw and never fall back to the mutable tag; a non-pullable/local-only ref fails the docker pull guard before selection.
  • Credential scoping. A per-deploy throwaway DOCKER_CONFIG dir under the host-shared /etc/dokploy tree is used so docker run -v resolves it on the host daemon (and not inside the dokploy container).
  • Hardening. Operator-controlled inputs (cosignImage, the pinned ref, the config dir) are validated against argv flag-smuggling; trust-policy mutations are admin-only with per-row org-ownership checks; the default cosign image is digest-pinned.

Testing

  • Unit tests for the pure logic: ref parsing (incl. @sha256 and numeric tags), RepoDigests selection (incl. fail-closed paths), cosign argv construction per trust mode, and the flag-smuggling guards.
  • The existing webhook image-parser tests stay green (the shared extraction was a non-regressing move).
  • apps/dokploy typecheck clean.

Not done in this PR (flagged for reviewers)

  • The migration (0173_…) was validated by inspection; migration:run against a live Postgres still needs to run.
  • The Trust Policies settings UI hasn't had a manual smoke test yet.
  • The exec wrappers (resolveDigest/verifySignature/admitImage) have unit-tested pure cores but no integration test — that belongs with the pipeline wiring in PR 2.

Happy to adjust the approach (entity shape, cosign execution model, phasing) to maintainer preferences before PR 2/3.

🤖 Generated with Claude Code

alxwlw and others added 14 commits June 17, 2026 15:55
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d onLog streaming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add admit.ts: admitImage() provisions throwaway DOCKER_CONFIG under
  /etc/dokploy/.cosign-auth/<deployId>, optionally docker-logins, calls
  resolveDigest then verifySignature (if trust policy present), returns
  pinned repo@sha256:…, always cleans up (fail-closed). Uses shell-quote
  for all interpolated path values per codebase idiom.
- Add index.ts barrel re-exporting image-ref, resolve-digest,
  verify-signature, and admit modules.
- Pin DEFAULT_COSIGN_IMAGE to ghcr.io/sigstore/cosign/cosign:v2.4.1@sha256:
  b03690aa52bfe94054187142fba24dc54137650682810633901767d8a3e15b31

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds trustPolicyRouter with create/update/remove/one/all procedures using
adminProcedure (owner/admin only). Registers in root.ts and extends
AuditResourceType with "trustPolicy" (type-only, no migration).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Trust Policies settings page, list component, and create/edit dialog
mirroring the Registry settings pattern; add Trust Policies nav entry in
the settings sidebar next to Registry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…signment on update, parse numeric tags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant