419 lines
16 KiB
Markdown
419 lines
16 KiB
Markdown
# 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` (Excititor 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.
|
||
|