Rewrite architecture docs and add Vexer connector template
This commit is contained in:
		
							
								
								
									
										418
									
								
								docs/ARCHITECTURE_SIGNER.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										418
									
								
								docs/ARCHITECTURE_SIGNER.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,418 @@ | ||||
| # component_architecture_signer.md — **Stella Ops Signer** (2025Q4) | ||||
|  | ||||
| > **Scope.** Implementation‑ready architecture for the **Signer**: the *only* service allowed to produce **Stella Ops‑verified** signatures over SBOMs and reports. It enforces **entitlement** (PoE), **release integrity** (scanner provenance), **sender‑constrained auth** (DPoP/mTLS), and emits **in‑toto/DSSE** bundles suitable for **Rekor v2** logging by the Attestor. Includes APIs, data flow, storage, quotas, security, and test matrices. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 0) Mission & boundaries | ||||
|  | ||||
| **Mission.** Convert authenticated signing requests from trusted Stella Ops services into **verifiable** DSSE bundles while enforcing **license policy** and **supply‑chain integrity**. | ||||
|  | ||||
| **Boundaries.** | ||||
|  | ||||
| * **Signer does not push to Rekor** — it returns DSSE to the caller; **Attestor** logs to **Rekor v2**. | ||||
| * **Signer does not compute PASS/FAIL** — it signs SBOMs/reports produced by Scanner/WebService after backend evaluation. | ||||
| * **Signer is stateless for hot path** — long‑term storage is limited to audit events; all secrets/keys live in KMS/HSM or are ephemeral (keyless). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1) Responsibilities (contract) | ||||
|  | ||||
| 1. **Authenticate** caller with **OpTok** (Authority OIDC, DPoP or mTLS‑bound). | ||||
| 2. **Authorize** scopes (`signer.sign`) + audience (`aud=signer`) + tenant/installation. | ||||
| 3. **Validate entitlement** via **PoE** (Proof‑of‑Entitlement) against Cloud Licensing `/license/introspect`. | ||||
| 4. **Verify release integrity** of the **scanner** image digest presented in the request: must be **cosign‑signed** by Stella Ops release key, discoverable via **OCI Referrers API**. | ||||
| 5. **Enforce plan & quotas** (concurrency/QPS/artifact size/rate caps). | ||||
| 6. **Mint signing identity**: | ||||
|  | ||||
|    * **Keyless** (default): get a short‑lived X.509 cert from **Fulcio** using the Signer’s OIDC identity and sign the DSSE. | ||||
|    * **Keyful** (optional): sign with an HSM/KMS key. | ||||
| 7. **Return DSSE bundle** (subject digests + predicate + cert chain or KMS key id). | ||||
| 8. **Audit** every decision; expose metrics. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2) External dependencies | ||||
|  | ||||
| * **Authority** (on‑prem OIDC): validates OpToks (JWKS/introspection) and DPoP/mTLS. | ||||
| * **Licensing Service (cloud)**: `/license/introspect` to verify PoE (active, claims, expiry, revocation). | ||||
| * **Fulcio** (Sigstore) *or* **KMS/HSM**: to obtain certs or perform signatures. | ||||
| * **OCI Registry (Referrers API)**: to verify **scanner** image release signature. | ||||
| * **Attestor**: downstream service that writes DSSE bundles to **Rekor v2**. | ||||
| * **Config/state stores**: Redis (caches, rate buckets), Mongo/Postgres (audit log). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3) API surface (mTLS; DPoP supported) | ||||
|  | ||||
| Base path: `/api/v1/signer`. **All endpoints require**: | ||||
|  | ||||
| * Access token (JWT) from **Authority** with `aud=signer`, `scope=signer.sign`. | ||||
| * **Sender constraint**: DPoP proof per request or mTLS client cert. | ||||
| * **PoE** presented as either: | ||||
|  | ||||
|   * **Client TLS cert** (if PoE is mTLS‑style) chained to Licensing CA, *or* | ||||
|   * **PoE JWT** (DPoP/mTLS‑bound) in `X-PoE` header or request body. | ||||
|  | ||||
| ### 3.1 `POST /sign/dsse` | ||||
|  | ||||
| Request (JSON): | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "subject": [ | ||||
|     { "name": "s3://stellaops/images/sha256:.../inventory.cdx.pb", | ||||
|       "digest": { "sha256": "..." } } | ||||
|   ], | ||||
|   "predicateType": "https://stella-ops.org/attestations/sbom/1", | ||||
|   "predicate": { | ||||
|     "image_digest": "sha256:...", | ||||
|     "stellaops_version": "2.3.1 (2027.04)", | ||||
|     "license_id": "LIC-9F2A...", | ||||
|     "customer_id": "CUST-ACME", | ||||
|     "plan": "pro", | ||||
|     "policy_digest": "sha256:...",        // optional for final reports | ||||
|     "views": ["inventory", "usage"], | ||||
|     "created": "2025-10-17T12:34:56Z" | ||||
|   }, | ||||
|   "scannerImageDigest": "sha256:sc-web-or-worker-digest", | ||||
|   "poe": { | ||||
|     "format": "jwt",                      // or "mtls" | ||||
|     "value": "eyJhbGciOi..."              // PoE JWT when not using mTLS PoE | ||||
|   }, | ||||
|   "options": { | ||||
|     "signingMode": "keyless",             // "keyless" | "kms" | ||||
|     "expirySeconds": 600,                 // cert lifetime hint (keyless) | ||||
|     "returnBundle": "dsse+cert"           // dsse (default) | dsse+cert | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Response 200: | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "bundle": { | ||||
|     "dsse": { "payloadType": "application/vnd.in-toto+json", "payload": "<base64>", "signatures": [ ... ] }, | ||||
|     "certificateChain": [ "-----BEGIN CERTIFICATE-----...", "... root ..." ], | ||||
|     "mode": "keyless", | ||||
|     "signingIdentity": { "issuer": "https://fulcio.internal", "san": "urn:stellaops:signer", "certExpiry": "2025-10-17T12:44:56Z" } | ||||
|   }, | ||||
|   "policy": { "plan": "pro", "maxArtifactBytes": 104857600, "qpsRemaining": 97 }, | ||||
|   "auditId": "a7c9e3f2-1b7a-4e87-8c3a-90d7d2c3ad12" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Errors (RFC 7807): | ||||
|  | ||||
| * `401 invalid_token` (JWT/DPoP/mTLS failure) | ||||
| * `403 entitlement_denied` (PoE invalid/revoked/expired; release year mismatch) | ||||
| * `403 release_untrusted` (scanner image not Stella‑signed) | ||||
| * `429 plan_throttled` (license plan caps) | ||||
| * `413 artifact_too_large` (size cap) | ||||
| * `400 invalid_request` (schema/predicate/type invalid) | ||||
| * `500 signing_unavailable` (Fulcio/KMS outage) | ||||
|  | ||||
| ### 3.2 `GET /verify/referrers?imageDigest=<sha256>` | ||||
|  | ||||
| Checks whether the **image** at digest is signed by **Stella Ops release key**. | ||||
|  | ||||
| Response: | ||||
|  | ||||
| ```json | ||||
| { "trusted": true, "signatures": [ { "type": "cosign", "digest": "sha256:...", "signedBy": "StellaOps Release 2027 Q2" } ] } | ||||
| ``` | ||||
|  | ||||
| > **Note:** This endpoint is also used internally by Signer before issuing signatures. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4) Validation pipeline (hot path) | ||||
|  | ||||
| ```mermaid | ||||
| sequenceDiagram | ||||
|   autonumber | ||||
|   participant Client as Scanner.WebService | ||||
|   participant Auth as Authority (OIDC) | ||||
|   participant Sign as Signer | ||||
|   participant Lic as Licensing Service (cloud) | ||||
|   participant Reg as OCI Registry (Referrers) | ||||
|   participant Ful as Fulcio/KMS | ||||
|  | ||||
|   Client->>Sign: POST /sign/dsse (OpTok + DPoP/mTLS, PoE, request) | ||||
|   Note over Sign: 1) Validate OpTok, audience, scope, DPoP/mTLS binding | ||||
|   Sign->>Lic: /license/introspect(PoE) | ||||
|   Lic-->>Sign: { active, claims: {license_id, plan, valid_release_year, max_version}, exp } | ||||
|   Note over Sign: 2) Enforce plan/version window and revocation | ||||
|  | ||||
|   Sign->>Reg: Verify scannerImageDigest signed (Referrers + cosign) | ||||
|   Reg-->>Sign: OK with signer identity | ||||
|   Note over Sign: 3) Enforce release integrity | ||||
|  | ||||
|   Note over Sign: 4) Enforce quotas (QPS/concurrency/size) | ||||
|   Sign->>Ful: Mint cert (keyless) or sign via KMS | ||||
|   Ful-->>Sign: Cert or signature | ||||
|  | ||||
|   Sign-->>Client: DSSE bundle (+cert chain), policy counters, auditId | ||||
| ``` | ||||
|  | ||||
| **DPoP nonce dance (when enabled for high‑value ops):** | ||||
|  | ||||
| * If DPoP proof lacks a valid nonce, Signer replies `401` with `WWW-Authenticate: DPoP error="use_dpop_nonce", dpop_nonce="<nonce>"`. | ||||
| * Client retries with new proof including the nonce; Signer validates nonce and `jti` uniqueness (Redis TTL cache). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5) Entitlement enforcement (PoE) | ||||
|  | ||||
| * **Accepted forms**: | ||||
|  | ||||
|   * **mTLS PoE**: client presents a **PoE client cert** at TLS handshake; Signer validates chain to **Licensing CA** (CA bundle configured) and calls `/license/introspect` with cert thumbprint + serial. | ||||
|   * **JWT PoE**: `X-PoE` bearer token (DPoP/mTLS‑bound) is validated (sig + `cnf`) locally (Licensing JWKS) and then **introspected** for status and claims. | ||||
|  | ||||
| * **Claims required**: | ||||
|  | ||||
|   * `license_id`, `plan` (free|pro|enterprise|gov), `valid_release_year`, `max_version`, `exp`. | ||||
|   * Optional: `tenant_id`, `customer_id`, `entitlements[]`. | ||||
|  | ||||
| * **Enforcements**: | ||||
|  | ||||
|   * Reject if **revoked**, **expired**, **plan mismatch** or **release outside window** (`stellaops_version` in predicate exceeds `max_version` or release date beyond `valid_release_year`). | ||||
|   * Apply plan **throttles** (QPS/concurrency/artifact bytes) via token‑bucket in Redis keyed by `license_id`. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6) Release integrity (scanner provenance) | ||||
|  | ||||
| * **Input**: `scannerImageDigest` representing the actual Scanner component that produced the artifact. | ||||
|  | ||||
| * **Check**: | ||||
|  | ||||
|   1. Use **OCI Referrers API** to enumerate signatures of that digest. | ||||
|   2. Verify **cosign** signatures against the configured **Stella Ops Release** keyring (keyless Fulcio roots *or* keyful public keys). | ||||
|   3. Optionally require Rekor inclusion for those signatures. | ||||
|  | ||||
| * **Policy**: | ||||
|  | ||||
|   * If not signed by an authorized **Stella Ops Release** identity → **deny**. | ||||
|   * If signed but **release year** > PoE `valid_release_year` → **deny**. | ||||
|  | ||||
| * **Cache**: LRU of digest → verification result (TTL 10–30 min) to avoid registry thrash. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 7) Signing modes | ||||
|  | ||||
| ### 7.1 Keyless (default; Sigstore Fulcio) | ||||
|  | ||||
| * Signer authenticates to **Fulcio** using its on‑prem OIDC identity (client credentials) and requests a **short‑lived cert** (5–10 min). | ||||
| * Generates **ephemeral keypair**, gets cert for the public key, signs DSSE with the **private key**. | ||||
| * DSSE **bundle** includes **certificate chain**; verifiers validate to Fulcio root. | ||||
|  | ||||
| ### 7.2 Keyful (optional; KMS/HSM) | ||||
|  | ||||
| * Signer uses a configured **KMS** key (AWS KMS, GCP KMS, Azure Key Vault, Vault Transit, or HSM). | ||||
| * DSSE bundle includes **key metadata** (kid, cert chain if x509). | ||||
| * Recommended for FIPS/sovereign environments. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 8) Predicates & schema | ||||
|  | ||||
| Supported **predicate types** (extensible): | ||||
|  | ||||
| * `https://stella-ops.org/attestations/sbom/1` (SBOM emissions) | ||||
| * `https://stella-ops.org/attestations/report/1` (final PASS/FAIL reports) | ||||
| * `https://stella-ops.org/attestations/vex-export/1` (Vexer exports; optional) | ||||
|  | ||||
| **Validation**: | ||||
|  | ||||
| * JSON‑Schema per predicate type; **canonical property order**. | ||||
| * `subject[*].digest` must include `sha256`. | ||||
| * `predicate.stellaops_version` must parse and match policy windows. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 9) Quotas & throttling | ||||
|  | ||||
| Per `license_id` (from PoE): | ||||
|  | ||||
| * **QPS** (token bucket), **concurrency** (semaphore), **artifact bytes** (sliding window). | ||||
| * On exceed → `429 plan_throttled` with `Retry-After`. | ||||
| * Free/community plan may also receive **randomized delay** to disincentivize farmed signing. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 10) Storage & caches | ||||
|  | ||||
| * **Redis**: | ||||
|  | ||||
|   * DPoP nonce & `jti` replay cache (TTL ≤ 10 min). | ||||
|   * PoE introspection cache (short TTL, e.g., 60–120 s). | ||||
|   * Release‑verify cache (`scannerImageDigest` → { trusted, ts }). | ||||
|  | ||||
| * **Audit store** (Mongo or Postgres): `signer.audit_events` | ||||
|  | ||||
| ``` | ||||
| { _id, ts, tenantId, installationId, licenseId, customerId, | ||||
|   plan, actor{sub,cnf}, request{predicateType, subjectSha256[], imageDigest}, | ||||
|   poe{type, thumbprint|jwtKid, exp, introspectSnapshot}, | ||||
|   release{digest, signerId, policy}, | ||||
|   mode: "keyless"|"kms", | ||||
|   result: "success"|"deny:<reason>"|"error:<reason>", | ||||
|   bundleSha256? } | ||||
| ``` | ||||
|  | ||||
| * **Config**: Stella Ops release signing keyring, Fulcio roots, Licensing CA bundle. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 11) Security & privacy | ||||
|  | ||||
| * **mTLS** on all Signer endpoints. | ||||
| * **No bearer fallbacks** — DPoP/mTLS enforced for `aud=signer`. | ||||
| * **PoE** is never persisted beyond audit snapshots (minimized fields). | ||||
| * **Secrets**: no long‑lived private keys on disk (keyless) or handled via KMS APIs. | ||||
| * **Input hardening**: schema‑validate predicates; cap payload sizes; zstd/gzip decompression bombs guarded. | ||||
| * **Logging**: redact PoE JWTs, access tokens, DPoP proofs; log only hashes and identifiers. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 12) Metrics & observability | ||||
|  | ||||
| * `signer.requests_total{result}` | ||||
| * `signer.latency_seconds{stage=auth|introspect|release_verify|sign}` | ||||
| * `signer.poe_failures_total{reason}` | ||||
| * `signer.release_verify_failures_total{reason}` | ||||
| * `signer.plan_throttle_total{license_id}` | ||||
| * `signer.bundle_bytes_total` | ||||
| * `signer.keyless_certs_issued_total` / `signer.kms_sign_total` | ||||
| * OTEL traces across stages; correlation id (`auditId`) returned to client. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 13) Configuration (YAML) | ||||
|  | ||||
| ```yaml | ||||
| signer: | ||||
|   listen: "https://0.0.0.0:8443" | ||||
|   authority: | ||||
|     issuer: "https://authority.internal" | ||||
|     jwksUrl: "https://authority.internal/jwks" | ||||
|     require: "dpop"         # "dpop" | "mtls" | ||||
|   poe: | ||||
|     mode: "both"            # "jwt" | "mtls" | "both" | ||||
|     licensing: | ||||
|       introspectUrl: "https://www.stella-ops.org/api/v1/license/introspect" | ||||
|       caBundle: "/etc/ssl/licensing-ca.pem" | ||||
|       cacheTtlSeconds: 90 | ||||
|   release: | ||||
|     referrers: | ||||
|       allowRekorVerified: true | ||||
|       keyrings: | ||||
|         - type: "cosign-keyless" | ||||
|           fulcioRoots: ["/etc/fulcio/root.pem"] | ||||
|           identities: | ||||
|             - san: "mailto:release@stella-ops.org" | ||||
|             - san: "https://sigstore.dev/oidc/stellaops" | ||||
|   signing: | ||||
|     mode: "keyless"         # "keyless" | "kms" | ||||
|     fulcio: | ||||
|       issuer: "https://fulcio.internal" | ||||
|       oidcClientId: "signer" | ||||
|       oidcClientSecretRef: "env:FULCIO_CLIENT_SECRET" | ||||
|       certTtlSeconds: 600 | ||||
|     kms: | ||||
|       provider: "aws-kms" | ||||
|       keyId: "arn:aws:kms:...:key/..." | ||||
|   quotas: | ||||
|     default: | ||||
|       qps: 100 | ||||
|       concurrency: 20 | ||||
|       maxArtifactBytes: 104857600 | ||||
|     free: | ||||
|       qps: 5 | ||||
|       concurrency: 1 | ||||
|       maxArtifactBytes: 1048576 | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 14) Testing matrix | ||||
|  | ||||
| * **Auth & DPoP**: bad `aud`, wrong `jkt`, replayed `jti`, missing nonce, mTLS mismatch. | ||||
| * **PoE**: expired, revoked, plan mismatch, release year gate, max_version gate. | ||||
| * **Release verify**: unsigned digest, wrong signer, Rekor‑absent (when required), referrers unreachable. | ||||
| * **Signing**: Fulcio outage; KMS timeouts; bundle correctness (verifier harness). | ||||
| * **Quotas**: burst above QPS, artifact over size, concurrency overflow. | ||||
| * **Schema**: invalid predicate types/required fields. | ||||
| * **Determinism**: same request → identical DSSE (aside from cert validity period). | ||||
| * **Perf**: P95 end‑to‑end under 120 ms with caches warm (excluding network to Fulcio). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 15) Failure modes & responses | ||||
|  | ||||
| | Failure                 | HTTP | Problem type          | Notes                                        | | ||||
| | ----------------------- | ---- | --------------------- | -------------------------------------------- | | ||||
| | Invalid OpTok / DPoP    | 401  | `invalid_token`       | `WWW-Authenticate` with DPoP nonce if needed | | ||||
| | PoE invalid/revoked     | 403  | `entitlement_denied`  | Include `license_id` (hashed) and reason     | | ||||
| | Scanner image untrusted | 403  | `release_untrusted`   | Include digest and required identity         | | ||||
| | Plan throttle           | 429  | `plan_throttled`      | Include limits and `Retry-After`             | | ||||
| | Artifact too large      | 413  | `artifact_too_large`  | Include cap                                  | | ||||
| | Fulcio/KMS down         | 503  | `signing_unavailable` | Retry‑After with jitter                      | | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 16) Deployment & HA | ||||
|  | ||||
| * Run ≥ 2 replicas; front with L7 LB; **sticky** not required. | ||||
| * Redis for replay/quota caches (HA). | ||||
| * Audit sink (Mongo/Postgres) in primary region; asynchronous write with local fallback buffer. | ||||
| * Fulcio/KMS clients configured with retries/backoff; circuit breakers. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 17) Implementation notes | ||||
|  | ||||
| * **.NET 10** minimal API + Kestrel mTLS; custom DPoP middleware; JWT/JWKS cache. | ||||
| * **Cosign verification** via sigstore libraries; Referrers queries over registry API with retries. | ||||
| * **DSSE** via in‑toto libs; canonical JSON writer for predicates. | ||||
| * **Backpressure** paths: refuse at auth/quota stages before any expensive network calls. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 18) Examples (wire) | ||||
|  | ||||
| **Request (free plan; expect throttle if burst):** | ||||
|  | ||||
| ```http | ||||
| POST /api/v1/signer/sign/dsse HTTP/1.1 | ||||
| Authorization: DPoP <JWT> | ||||
| DPoP: <proof> | ||||
| Content-Type: application/json | ||||
|  | ||||
| ``` | ||||
|  | ||||
| **Error (release untrusted):** | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "type": "https://stella-ops.org/problems/release_untrusted", | ||||
|   "title": "Scanner image not signed by StellaOps", | ||||
|   "status": 403, | ||||
|   "detail": "sha256:abcd... not in trusted keyring", | ||||
|   "instance": "urn:audit:a7c9e3f2-..." | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 19) Roadmap | ||||
|  | ||||
| * **Key Transparency**: optional publication of Signer’s *own* certs to a KT log. | ||||
| * **Attested Build**: SLSA‑style provenance for Signer container itself, checked at startup. | ||||
| * **FIPS mode**: enforce `ES256` + KMS/HSM only; disallow Ed25519. | ||||
| * **Dual attestation**: optional immediate push to **Attestor** (sync mode) with timeout budget, returning Rekor UUID inline. | ||||
|  | ||||
| 
 | ||||
		Reference in New Issue
	
	Block a user