Appearance
Signing Service
This is the authoritative bounded-context reference for the Signing Service that ships under internal/signing and the cmd/plexsphere-signer binary. The reference is split into four focused pages plus this entry point.
The Signing Service is the sharpest privilege edge in a plexsphere deployment: the only process in the system that ever touches Ed25519 private key material. Every other subsystem reaches signatures through the two-RPC gRPC surface and holds only public halves. The "what the Signing Service is not" list (in Operations) pins the shape of that boundary verbatim against ../../../README.md#signing-service.
Pages
- Deployment scoping and rotation — the
(saas | selfhosted-single | selfhosted-multi)profile matrix, the three-state rotation lifecycle (active,rotating,retired), and the rotation overlap window invariant. - KeyProvider contract and adapters — the outbound port every back-end implements (software, PKCS #11 HSM, cloud KMS, test double) plus the production HSM wiring rules.
- Security invariants — the no-private-half invariant in the core binary, the mTLS peer-identity gate, and the one-entry-per-RPC audit contract.
- gRPC surface and operations — the two RPCs and their stable error strings, the NetworkPolicy topology, the operator debugging workflow, and the explicit "what the Signing Service is not" boundary.
See also
../../architecture/overview.md#sharpest-privilege-edge--signing-service— the architecture-level framing of the two-binary split and why the signer runs in its own process.../../contributing/layout.md— the bounded-context map row that enumerates theinternal/signingsub-packages and thedepguardrules that keep them isolated.../../how-to/platform/rotate-the-signing-key.md— operator runbook for opening and closing a rotation transition throughplexctl.../../../README.md#signing-service— top-level product specification; this reference is the implementation-side companion to that specification.../../../tests/integration/signer_core_binary_no_material_test.go— static-analysis invariant proving noed25519.PrivateKeyreference reaches the core binary's transitive graph.../../../tests/integration/signer_audit_contract_test.go— pins theaudit.Entryshape the signer emits per RPC.../../../tests/e2e/security/signer-roundtrip/— Chainsaw suite that drives a sign/verify round-trip against the deployed manifests.
Ubiquitous language
The terms below travel together across the Go code, the audit log, the .proto contract, and operator-facing tooling. Names are preserved verbatim in error messages and structured log attributes.
| Term | Definition | Code anchor |
|---|---|---|
| SigningKey | The aggregate that owns a single signing-key row: (scope, key_id, state, valid_from, valid_until, public_bytes, provider_handle_id). Carries only the public half plus the opaque provider_handle_id; the private bytes never enter this process address space. | internal/signing/types.go |
| Scope | The compound identifier (ScopeKind, ID) where ScopeKind is platform or domain. The wire form is platform or domain:<uuid>; NewScope enforces ID == uuid.Nil iff Kind == platform. | Scope, NewScope, ParseScope in internal/signing/types.go |
| KeyID | An opaque URL-safe identifier of a single SigningKey row (^[A-Za-z0-9._~-]+$, ≤ 128 bytes). The zero value is always invalid. | NewKeyID in internal/signing/types.go |
| State | The lifecycle phase of a SigningKey: active, rotating, or retired. Transitions are funnelled exclusively through internal/signing/rotation. | State constants in internal/signing/types.go; transition fns in internal/signing/rotation/state.go |
| RotationTransition | The aggregate binding an outgoing OldKey and incoming NewKey to a Window. Cross-scope transitions are rejected at construction time. | internal/signing/rotation/overlap.go |
| Window | The (OpenedAt, ClosesAt) pair during which both halves of a rotation are advertised. Strict OpenedAt < ClosesAt. | NewWindow in internal/signing/rotation/overlap.go |
| KeyProvider | The outbound port for the signing back-end (software, PKCS #11 HSM, cloud KMS, test double). The only code in the system that touches private key material. | internal/signing/ports.go |
| SignRequest / SignResponse | The Sign RPC shape: canonical_bytes, scope, key_id in; raw 64-byte Ed25519 signature plus echoed key_id out. | api/proto/signing/v1/signing.proto |
| PublicKeyRequest / PublicKeyResponse | The PublicKey RPC shape: scope, key_id in; 32-byte public_key, echoed key_id, and lifecycle state out. | api/proto/signing/v1/signing.proto |
| canonical bytes | The exact pre-serialised byte sequence passed to Sign. The signer NEVER re-canonicalises or transforms the payload; the caller owns deterministic encoding. | internal/signing/envelope/canonical.go |
| SPIFFE peer identity | The spiffe:// URI SAN on the caller's mTLS client certificate, extracted from peer.FromContext before any business logic runs. Drives admission through the --allow-spiffe-id allow-list. | internal/signing/peeridentity/interceptor.go |
| audit grant / deny | One audit.Entry per admission decision: granted when the allow-list admits, insufficient_relation when the allow-list refuses (or when the domain refuses with ErrClientIdentityDenied), caveat_violation for lifecycle refusals (ErrKeyRetired, ErrRotationInProgress, ErrProviderUnavailable). | internal/signing/audit_middleware.go, internal/signing/peeridentity/interceptor.go |