Stella Ops Signer Architecture (Relocated)
This module dossier is a stable redirect for older links, plus a source-grounded summary of the Signer runtime for readers who land here. The canonical architecture material lives in the Attestor dossier.
Signer source now lives under src/Attestor/StellaOps.Signer/ (the WebService, Core, Infrastructure, and Tests projects), with key-rotation and trust-anchor logic in src/Attestor/__Libraries/StellaOps.Signer.KeyManagement/. The canonical architecture material is maintained in the Attestor dossier:
- Signer relocation README
- Attestor architecture: Trust Domain Model (Sprint 204)
- Attestor architecture: PostgreSQL schema ownership (Signer
signerschema + KeyManagement boundary) - Attestor architecture: Security boundary — no-merge ADR
Do not add new standalone Signer architecture content here. Update the Attestor architecture dossier (and the relocation README) and keep this redirect current. The summary below is a source-grounded pointer for the many docs that still link here — it is not the authoritative dossier.
Runtime at a glance (source-grounded)
Verified against src/Attestor/StellaOps.Signer/ on this checkout.
- Docker image / base path:
stellaops/signer; HTTP surface is rooted at/api/v1/signer/(SignerEndpoints.MapSignerEndpoints). - Authentication: Authority resource-server auth in live hosts (
AddStellaOpsResourceServerAuthentication); a test bearer/scope handler is injected only in theTestingenvironment by the test harness (Program.cs). - Database: Signer’s own PostgreSQL database
stellaops_signer, schemasigner, auto-migrated byStellaOps.Signer.Persistence. Its consolidated001_signer_consolidated_baseline.sqlreplaces the legacy KeyManagement startup chain;002_dsse_request_replays.sqlis the forward idempotency store. Live tables:signer.key_history,signer.key_audit_log,signer.trust_anchors,signer.ceremonies,signer.ceremony_approvals,signer.ceremony_audit_log,signer.operator_decision_envelopes, andsigner.dsse_request_replays. A liveSTELLAOPS_POSTGRES_SIGNER_CONNECTION(orPostgres:Signer:ConnectionString) is required outside the test host harness; EF InMemory key-management storage is rejected. (019 SGN-6, 2026-08-18: the formerConnectionStrings:KeyManagementkey is retired and no longer accepted.) - Ceremony-quorum trust evidence is durable (SPRINT_20260712_003 EVI-2): on quorum,
CeremonyQuorumAttestorassembles the multi-signedoperator-decision@v1DSSE envelope and persists it throughPostgresOperatorDecisionEnvelopeStoreintosigner.operator_decision_envelopes(idempotent on(tenant_id, envelope_digest)).InMemoryOperatorDecisionEnvelopeStoreis now registered only when no Signer-owned connection is configured (the test host) andSignerRuntimeGuardsrejects it outside Development/Testing. See operator-decision predicate — envelope persistence.
HTTP endpoints
Routes and policies as registered in Program.cs / the *Endpoints.cs files.
| Method & path | Policy (scope) | Source |
|---|---|---|
GET / | none (readiness banner) | Program.cs |
GET /healthz, GET /readyz | anonymous | Program.cs (SIGNER_HEALTH-01) |
GET /internal/plugins/status | anonymous | Program.cs:269 — crypto-provider pack readiness (provider names, pack status) |
POST /internal/plugins/probe | anonymous | Program.cs:297 — crypto-provider pack probe |
build-info endpoint (MapBuildInfoEndpoint) | anonymous | Program.cs:329 — version/build banner |
GET /openapi/* (MapOpenApi) | anonymous | Program.cs:255 — OpenAPI document |
POST /api/v1/signer/sign/dsse | Signer.Sign → signer:sign | SignerEndpoints |
POST /api/v1/signer/verify/dsse | Signer.Verify → signer:read | SignerEndpoints |
GET /api/v1/signer/verify/referrers?digest= | Signer.Verify → signer:read | SignerEndpoints |
POST /api/v1/anchors/{anchorId}/keys | Signer.KeyManagement → signer:rotate | KeyRotationEndpoints |
POST /api/v1/anchors/{anchorId}/keys/{keyId}/revoke | Signer.KeyManagement → signer:rotate | KeyRotationEndpoints |
GET /api/v1/anchors/{anchorId}/keys/{keyId}/validity | Signer.KeyManagement → signer:rotate | KeyRotationEndpoints |
GET /api/v1/anchors/{anchorId}/keys/history | Signer.KeyManagement → signer:rotate | KeyRotationEndpoints |
GET /api/v1/anchors/{anchorId}/keys/warnings | Signer.KeyManagement → signer:rotate | KeyRotationEndpoints |
POST /api/v1/ceremonies | Signer.CeremonyCreate → signer:sign | CeremonyEndpoints |
GET /api/v1/ceremonies | Signer.CeremonyRead → signer:read | CeremonyEndpoints |
GET /api/v1/ceremonies/{ceremonyId} | Signer.CeremonyRead → signer:read | CeremonyEndpoints |
POST /api/v1/ceremonies/{ceremonyId}/approve | Signer.CeremonyApprove → signer:sign | CeremonyEndpoints |
POST /api/v1/ceremonies/{ceremonyId}/execute | Signer.CeremonyExecute → signer:admin | CeremonyEndpoints |
DELETE /api/v1/ceremonies/{ceremonyId} | Signer.CeremonyCancel → signer:admin | CeremonyEndpoints |
Notes:
- The ceremony and anchor endpoints declare the canonical
SignerPolicies.*constants (Signer.CeremonyRead,Signer.KeyManagement, …) registered inProgram.cs; the pre-consolidation raw string aliases (ceremony:read,KeyManagement, …) were removed 2026-08-25 (P8-14). POST /verify/dsseverifies a complete Signer-produced bundle over DSSE PAE. The bundle carries the signer-selected provider and algorithm;IDsseVerifierresolves that registered provider and key id throughICryptoProviderRegistry. Raw-envelope input is retained only for the explicit Development/Testing HMAC signer, and that path is PAE-bound as well. This is Signer-key verification, not arbitrary Sigstore/certificate-chain/transparency-policy evaluation; the broader trust decision remains Attestor-owned.POST /sign/dsseaccepts an optionalIdempotency-Key. Signer binds it to tenant + authenticated subject + canonical operation, persists exact response bytes, and returns409for a different operation or425while another lease owns the same operation. Replays revalidate explicit tenant, subject, exact audiencesigner,signer:sign, and current PoE entitlement. A stale takeover may re-sign across the signed-before-completion crash boundary; the contract is not an exactly-once audit claim.- Generated OpenAPI declares the body, optional idempotency header, and
200/400/403/409/425/429/500. Its current generator does not project the minimal-API authorization policy into a security scheme; generated “Auth: Not declared” text does not override source-enforced authorization or DPoP/PoE. - In-toto statement version is selected from the normalized predicate type on every default signing backend. SLSA provenance v1 and
stella.ops/*predicates usehttps://in-toto.io/Statement/v1; legacy SLSA v0.2 and the currently mapped CycloneDX/SPDX/OpenVEX families retain Statement v0.1. KMS, keyless, and the Development/Testing HMAC harness must not produce different statement contracts for the same predicate solely because the signer backend changed. GET /verify/referrersresolves release-integrity trust for a scanner image digest viaIReleaseIntegrityVerifierand returns{ trusted, releaseSigner }.
Scopes
The Signer scope constants live in StellaOps.Auth.Abstractions/StellaOpsScopes.cs:
signer:read(SignerRead) — read Signer configuration/key metadata and perform DSSE verify / ceremony read.signer:sign(SignerSign) — create signatures, create/approve ceremonies.signer:rotate(SignerRotate) — key-rotation and trust-anchor management.signer:admin(SignerAdmin) — execute/cancel ceremonies; administrative control.
Configuration keys
Bound in Program.cs:
Authority:ResourceServer(resource-server auth; audiencesigner).STELLAOPS_POSTGRES_SIGNER_CONNECTION(orPostgres:Signer:ConnectionString) (PostgreSQL, databasestellaops_signer; required in live hosts. The legacyConnectionStrings:KeyManagementkey was retired by 019 SGN-6 and is no longer accepted).Signer:Entitlements,Signer:ReleaseVerification,Signer:Crypto,Signer:Dsse.Signer:Dsse:CadesBaselineBPfxPath— optional offline-only CAdES-B companion (PKCS#12/PFX). It does not provide qualified eIDAS/QSCD evidence and does not satisfy the T/LT/LTA profiles, which remain fail-closed (cadenced.cades.provider_pack_required).Router(Stella Router microservice registration;serviceName: "signer").
DSSE signing notes
- The DSSE sign endpoint runs
ISignerPipeline.SignAsync, which validates the proof-of-entitlement (PoE), sender binding (DPoP for JWT PoE, mTLS client cert for mTLS PoE), predicate, release integrity, quota, and provider resolution before emitting the DSSE envelope and an audit id. - The in-toto request predicate type must match the exact
PredicateTypesallowlist and the predicate payload must be a JSON object. Unsupported types and null/array/scalar payloads fail aspredicate_type_unsupportedorpredicate_invalidbefore entitlement, release-integrity, quota, provider, crypto, or audit side effects. - Signing mode is
kms(default) orkeyless; primary signatures are algorithm-pinned by the active crypto profile before provider lookup. - The CRA “cadenced” DSSE wrapper is opt-in via
options.cadenced=trueor areturnBundletoken containingcadenced, with CAdES profilesBaselineB/BaselineT/BaselineLT/BaselineLTA. - In local-runtime environments (
Development/Testing) the pipeline is replaced with a deterministicHmacDsseSignerharness; live hosts reject it. - Audit capture is body-disabled on the sign route. PoE values and signing payloads are not copied into Timeline audit, and rejected entitlement logging emits no token-derived length, name, or fingerprint.
- The direct endpoint currently performs structural, not cryptographic, DPoP validation. A live consumer must traverse a validating gateway or wait for a direct-endpoint
htu/htm/iat/jti/ath/JWK-signature closure.
Why the move
Signer, Attestor, and Provenance form the trust domain — the services responsible for cryptographic evidence production, transparency logging, and verification. Consolidating source ownership under src/Attestor/ makes trust-boundary responsibilities explicit while preserving runtime isolation between Signer key/state material and Attestor evidence state. See the no-merge decision ADR.
