# component_architecture_attestor.md — **Stella Ops Attestor** (2025Q4) > Derived from Epic 19 – Attestor Console with provenance hooks aligned to the Export Center bundle workflows scoped in Epic 10. > **Scope.** Implementation‑ready architecture for the **Attestor**: the service that **submits** DSSE envelopes to **Rekor v2**, retrieves/validates inclusion proofs, caches results, and exposes verification APIs. It accepts DSSE **only** from the **Signer** over mTLS, enforces chain‑of‑trust to Stella Ops roots, and returns `{uuid, index, proof, logURL}` to calling services (Scanner.WebService for SBOMs; backend for final reports; Excititor exports when configured). --- ## 0) Mission & boundaries **Mission.** Turn a signed DSSE envelope from the Signer into a **transparency‑logged, verifiable fact** with a durable, replayable proof (Merkle inclusion + (optional) checkpoint anchoring). Provide **fast verification** for downstream consumers and a stable retrieval interface for UI/CLI. **Boundaries.** * Attestor **does not sign**; it **must not** accept unsigned or third‑party‑signed bundles. * Attestor **does not decide PASS/FAIL**; it logs attestations for SBOMs, reports, and export artifacts. * Rekor v2 backends may be **local** (self‑hosted) or **remote**; Attestor handles both with retries, backoff, and idempotency. --- ## 1) Topology & dependencies **Process shape:** single stateless service `stellaops/attestor` behind mTLS. **Dependencies:** * **Signer** (caller) — authenticated via **mTLS** and **Authority** OpToks. * **Rekor v2** — tile‑backed transparency log endpoint(s). * **MinIO (S3)** — optional archive store for DSSE envelopes & verification bundles. * **MongoDB** — local cache of `{uuid, index, proof, artifactSha256, bundleSha256}`; job state; audit. * **Redis** — dedupe/idempotency keys and short‑lived rate‑limit buckets. * **Licensing Service (optional)** — “endorse” call for cross‑log publishing when customer opts‑in. Trust boundary: **Only the Signer** is allowed to call submission endpoints; enforced by **mTLS peer cert allowlist** + `aud=attestor` OpTok. --- ### Roles, identities & scopes - **Subjects** — immutable digests for artifacts (container images, SBOMs, reports) referenced in DSSE envelopes. - **Issuers** — authenticated builders/scanners/policy engines signing evidence; tracked with mode (`keyless`, `kms`, `hsm`, `fido2`) and tenant scope. - **Consumers** — Scanner, Export Center, CLI, Console, Policy Engine that verify proofs using Attestor APIs. - **Authority scopes** — `attestor.write`, `attestor.verify`, `attestor.read`, and administrative scopes for key management; all calls mTLS/DPoP-bound. ### Supported predicate types - `StellaOps.BuildProvenance@1` - `StellaOps.SBOMAttestation@1` - `StellaOps.ScanResults@1` - `StellaOps.PolicyEvaluation@1` - `StellaOps.VEXAttestation@1` - `StellaOps.RiskProfileEvidence@1` Each predicate embeds subject digests, issuer metadata, policy context, materials, and optional transparency hints. Unsupported predicates return `422 predicate_unsupported`. > **Golden fixtures:** Deterministic JSON statements for each predicate live in `src/Attestor/StellaOps.Attestor.Types/samples`. They are kept stable by the `StellaOps.Attestor.Types.Tests` project so downstream docs and contracts can rely on them without drifting. ### Envelope & signature model - DSSE envelopes canonicalised (stable JSON ordering) prior to hashing. - Signature modes: keyless (Fulcio cert chain), keyful (KMS/HSM), hardware (FIDO2/WebAuthn). Multiple signatures allowed. - Rekor entry stores bundle hash, certificate chain, and optional witness endorsements. - Archive CAS retains original envelope plus metadata for offline verification. - Envelope serializer emits **compact** (canonical, minified) and **expanded** (annotated, indented) JSON variants off the same canonical byte stream so hashing stays deterministic while humans get context. - Payload handling supports **optional compression** (`gzip`, `brotli`) with compression metadata recorded in the expanded view and digesting always performed over the uncompressed bytes. - Expanded envelopes surface **detached payload references** (URI, digest, media type, size) so large artifacts can live in CAS/object storage while the canonical payload remains embedded for verification. - Payload previews auto-render JSON or UTF-8 text in the expanded output to simplify triage in air-gapped and offline review flows. ### Verification pipeline overview 1. Fetch envelope (from request, cache, or storage) and validate DSSE structure. 2. Verify signature(s) against configured trust roots; evaluate issuer policy. 3. Retrieve or acquire inclusion proof from Rekor (primary + optional mirror). 4. Validate Merkle proof against checkpoint; optionally verify witness endorsement. 5. Return cached verification bundle including policy verdict and timestamps. ### Rekor Inclusion Proof Verification (SPRINT_3000_0001_0001) The Attestor implements RFC 6962-compliant Merkle inclusion proof verification for Rekor transparency log entries: **Components:** - `MerkleProofVerifier` — Verifies Merkle audit paths per RFC 6962 Section 2.1.1 - `CheckpointSignatureVerifier` — Parses and verifies Rekor checkpoint signatures (ECDSA/Ed25519) - `RekorVerificationOptions` — Configuration for public keys, offline mode, and checkpoint caching **Verification Flow:** 1. Parse checkpoint body (origin, tree size, root hash) 2. Verify checkpoint signature against Rekor public key 3. Compute leaf hash from canonicalized entry 4. Walk Merkle path from leaf to root using RFC 6962 interior node hashing 5. Compare computed root with checkpoint root hash (constant-time) **Offline Mode:** - Bundled checkpoints can be used in air-gapped environments - `EnableOfflineMode` and `OfflineCheckpointBundlePath` configuration options - `AllowOfflineWithoutSignature` for fully disconnected scenarios (reduced security) **Metrics:** - `attestor.rekor_inclusion_verify_total` — Verification attempts by result - `attestor.rekor_checkpoint_verify_total` — Checkpoint signature verifications - `attestor.rekor_offline_verify_total` — Offline mode verifications - `attestor.rekor_checkpoint_cache_hits/misses` — Checkpoint cache performance ### UI & CLI touchpoints - Console: Evidence browser, verification report, chain-of-custody graph, issuer/key management, attestation workbench, bulk verification views. - CLI: `stella attest sign|verify|list|fetch|key` with offline verification and export bundle support. - SDKs expose sign/verify primitives for build pipelines. ### Performance & observability targets - Throughput goal: ≥1 000 envelopes/minute per worker with cached verification. - Metrics: `attestor_submission_total`, `attestor_verify_seconds`, `attestor_rekor_latency_seconds`, `attestor_cache_hit_ratio`. - Logs include `tenant`, `issuer`, `subjectDigest`, `rekorUuid`, `proofStatus`; traces cover submission → Rekor → cache → response path. --- ## 2) Data model (Mongo) Database: `attestor` **Collections & schemas** * `entries` ``` { _id: "", artifact: { sha256: "", kind: "sbom|report|vex-export", imageDigest?, subjectUri? }, bundleSha256: "", // canonicalized DSSE index: , // log index/sequence if provided by backend proof: { // inclusion proof checkpoint: { origin, size, rootHash, timestamp }, inclusion: { leafHash, path[] } // Merkle path (tiles) }, log: { url, logId? }, createdAt, status: "included|pending|failed", signerIdentity: { mode: "keyless|kms", issuer, san?, kid? } } ``` * `dedupe` ``` { key: "bundle:", rekorUuid, createdAt, ttlAt } // idempotency key ``` * `audit` ``` { _id, ts, caller: { cn, mTLSThumbprint, sub, aud }, // from mTLS + OpTok action: "submit|verify|fetch", artifactSha256, bundleSha256, rekorUuid?, index?, result, latencyMs, backend } ``` Indexes: * `entries` on `artifact.sha256`, `bundleSha256`, `createdAt`, and `{status:1, createdAt:-1}`. * `dedupe.key` unique (TTL 24–48h). * `audit.ts` for time‑range queries. --- ## 2.1) Content-Addressed Identifier Formats The ProofChain library (`StellaOps.Attestor.ProofChain`) defines canonical content-addressed identifiers for all proof chain components. These IDs ensure determinism, tamper-evidence, and reproducibility. ### Identifier Types | ID Type | Format | Source | Example | |---------|--------|--------|---------| | **ArtifactID** | `sha256:<64-hex>` | Container manifest or binary hash | `sha256:a1b2c3d4e5f6...` | | **SBOMEntryID** | `:[@]` | SBOM hash + component PURL | `sha256:91f2ab3c:pkg:npm/lodash@4.17.21` | | **EvidenceID** | `sha256:` | Canonical evidence JSON | `sha256:e7f8a9b0c1d2...` | | **ReasoningID** | `sha256:` | Canonical reasoning JSON | `sha256:f0e1d2c3b4a5...` | | **VEXVerdictID** | `sha256:` | Canonical VEX verdict JSON | `sha256:d4c5b6a7e8f9...` | | **ProofBundleID** | `sha256:` | Merkle root of bundle components | `sha256:1a2b3c4d5e6f...` | | **GraphRevisionID** | `grv_sha256:` | Merkle root of graph state | `grv_sha256:9f8e7d6c5b4a...` | ### Canonicalization (RFC 8785) All JSON-based IDs use RFC 8785 (JCS) canonicalization: - UTF-8 encoding - Lexicographically sorted keys - No whitespace (minified) - No volatile fields (timestamps, random values excluded) **Implementation:** `StellaOps.Attestor.ProofChain.Json.Rfc8785JsonCanonicalizer` ### Merkle Tree Construction ProofBundleID and GraphRevisionID use deterministic binary Merkle trees: - SHA-256 hash function - Lexicographically sorted leaf inputs - Standard binary tree construction (pair-wise hashing) - Odd leaves promoted to next level **Implementation:** `StellaOps.Attestor.ProofChain.Merkle.DeterministicMerkleTreeBuilder` ### ID Generation Interface ```csharp // Core interface for ID generation public interface IContentAddressedIdGenerator { EvidenceId GenerateEvidenceId(EvidencePredicate predicate); ReasoningId GenerateReasoningId(ReasoningPredicate predicate); VexVerdictId GenerateVexVerdictId(VexPredicate predicate); ProofBundleId GenerateProofBundleId(SbomEntryId sbom, EvidenceId[] evidence, ReasoningId reasoning, VexVerdictId verdict); GraphRevisionId GenerateGraphRevisionId(GraphState state); } ``` ### Predicate Types The ProofChain library defines DSSE predicates for each attestation type: | Predicate | Type URI | Purpose | |-----------|----------|---------| | `EvidencePredicate` | `stellaops.org/evidence/v1` | Scan evidence (findings, reachability) | | `ReasoningPredicate` | `stellaops.org/reasoning/v1` | Exploitability reasoning | | `VexPredicate` | `stellaops.org/vex-verdict/v1` | VEX status determination | | `ProofSpinePredicate` | `stellaops.org/proof-spine/v1` | Complete proof bundle | **Reference:** `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/` --- ## 3) Input contract (from Signer) **Attestor accepts only** DSSE envelopes that satisfy all of: 1. **mTLS** peer certificate maps to `signer` service (CA‑pinned). 2. **Authority** OpTok with `aud=attestor`, `scope=attestor.write`, DPoP or mTLS bound. 3. DSSE envelope is **signed by the Signer’s key** (or includes a **Fulcio‑issued** cert chain) and **chains to configured roots** (Fulcio/KMS). 4. **Predicate type** is one of Stella Ops types (sbom/report/vex‑export) with valid schema. 5. `subject[*].digest.sha256` is present and canonicalized. **Wire shape (JSON):** ```json { "bundle": { "dsse": { "payloadType": "application/vnd.in-toto+json", "payload": "", "signatures": [ ... ] }, "certificateChain": [ "-----BEGIN CERTIFICATE-----..." ], "mode": "keyless" }, "meta": { "artifact": { "sha256": "", "kind": "sbom|report|vex-export", "imageDigest": "sha256:..." }, "bundleSha256": "", "logPreference": "primary", // "primary" | "mirror" | "both" "archive": true // whether Attestor should archive bundle to S3 } } ``` --- ## 4) APIs ### 4.1 Signing `POST /api/v1/attestations:sign` *(mTLS + OpTok required)* * **Purpose**: Deterministically wrap Stella Ops payloads in DSSE envelopes before Rekor submission. Reuses the submission rate limiter and honours caller tenancy/audience scopes. * **Body**: ```json { "keyId": "signing-key-id", "payloadType": "application/vnd.in-toto+json", "payload": "", "mode": "keyless|keyful|kms", "certificateChain": ["-----BEGIN CERTIFICATE-----..."], "artifact": { "sha256": "", "kind": "sbom|report|vex-export", "imageDigest": "sha256:...", "subjectUri": "oci://..." }, "logPreference": "primary|mirror|both", "archive": true } ``` * **Behaviour**: * Resolve the signing key from `attestor.signing.keys[]` (includes algorithm, provider, and optional KMS version). * Compute DSSE pre‑authentication encoding, sign with the resolved provider (default EC, BouncyCastle Ed25519, or File‑KMS ES256), and add static + request certificate chains. * Canonicalise the resulting bundle, derive `bundleSha256`, and mirror the request meta shape used by `/api/v1/rekor/entries`. * Emit `attestor.sign_total{result,algorithm,provider}` and `attestor.sign_latency_seconds{algorithm,provider}` metrics and append an audit row (`action=sign`). * **Response 200**: ```json { "bundle": { "dsse": { "payloadType": "...", "payload": "...", "signatures": [{ "keyid": "signing-key-id", "sig": "..." }] }, "certificateChain": ["..."], "mode": "kms" }, "meta": { "artifact": { "sha256": "...", "kind": "sbom" }, "bundleSha256": "...", "logPreference": "primary", "archive": true }, "key": { "keyId": "signing-key-id", "algorithm": "ES256", "mode": "kms", "provider": "kms", "signedAt": "2025-11-01T12:34:56Z" } } ``` * **Errors**: `400 key_not_found`, `400 payload_missing|payload_invalid_base64|artifact_sha_missing`, `400 mode_not_allowed`, `403 client_certificate_required`, `401 invalid_token`, `500 signing_failed`. ### 4.2 Submission `POST /api/v1/rekor/entries` *(mTLS + OpTok required)* * **Body**: as above. * **Behavior**: * Verify caller (mTLS + OpTok). * Validate DSSE bundle (signature, cert chain to Fulcio/KMS; DSSE structure; payloadType allowed). * Idempotency: compute `bundleSha256`; check `dedupe`. If present, return existing `rekorUuid`. * Submit canonicalized bundle to Rekor v2 (primary or mirror according to `logPreference`). * Retrieve **inclusion proof** (blocking until inclusion or up to `proofTimeoutMs`); if backend returns promise only, return `status=pending` and retry asynchronously. * Persist `entries` record; archive DSSE to S3 if `archive=true`. * **Response 200**: ```json { "uuid": "…", "index": 123456, "proof": { "checkpoint": { "origin": "rekor@site", "size": 987654, "rootHash": "…", "timestamp": "…" }, "inclusion": { "leafHash": "…", "path": ["…","…"] } }, "logURL": "https://rekor…/api/v2/log/…/entries/…", "status": "included" } ``` * **Errors**: `401 invalid_token`, `403 not_signer|chain_untrusted`, `409 duplicate_bundle` (with existing `uuid`), `502 rekor_unavailable`, `504 proof_timeout`. ### 4.3 Proof retrieval `GET /api/v1/rekor/entries/{uuid}` * Returns `entries` row (refreshes proof from Rekor if stale/missing). * Accepts `?refresh=true` to force backend query. ### 4.4 Verification (third‑party or internal) `POST /api/v1/rekor/verify` * **Body** (one of): * `{ "uuid": "…" }` * `{ "bundle": { …DSSE… } }` * `{ "artifactSha256": "…" }` *(looks up most recent entry)* * **Checks**: 1. **Bundle signature** → cert chain to Fulcio/KMS roots configured. 2. **Inclusion proof** → recompute leaf hash; verify Merkle path against checkpoint root. 3. Optionally verify **checkpoint** against local trust anchors (if Rekor signs checkpoints). 4. Confirm **subject.digest** matches caller‑provided hash (when given). 5. Fetch **transparency witness** statement when enabled; cache results and downgrade status to WARN when endorsements are missing or mismatched. * **Response**: ```json { "ok": true, "uuid": "…", "index": 123, "logURL": "…", "checkedAt": "…" } ``` ### 4.5 Bulk verification `POST /api/v1/rekor/verify:bulk` enqueues a verification job containing up to `quotas.bulk.maxItemsPerJob` items. Each item mirrors the single verification payload (uuid | artifactSha256 | subject+envelopeId, optional policyVersion/refreshProof). The handler persists a MongoDB job document (`bulk_jobs` collection) and returns `202 Accepted` with a job descriptor and polling URL. `GET /api/v1/rekor/verify:bulk/{jobId}` returns progress and per-item results (subject/uuid, status, issues, cached verification report if available). Jobs are tenant- and subject-scoped; only the initiating principal can read their progress. **Worker path:** `BulkVerificationWorker` claims queued jobs (`status=queued → running`), executes items sequentially through the cached verification service, updates progress counters, and records metrics: - `attestor.bulk_jobs_total{status}` – completed/failed jobs - `attestor.bulk_job_duration_seconds{status}` – job runtime - `attestor.bulk_items_total{status}` – per-item outcomes (`succeeded`, `verification_failed`, `exception`) The worker honours `bulkVerification.itemDelayMilliseconds` for throttling and reschedules persistence conflicts with optimistic version checks. Results hydrate the verification cache; failed items record the error reason without aborting the overall job. --- ## 5) Rekor v2 driver (backend) * **Canonicalization**: DSSE envelopes are **normalized** (stable JSON ordering, no insignificant whitespace) before hashing and submission. * **Transport**: HTTP/2 with retries (exponential backoff, jitter), budgeted timeouts. * **Idempotency**: if backend returns “already exists,” map to existing `uuid`. * **Proof acquisition**: * In synchronous mode, poll the log for inclusion up to `proofTimeoutMs`. * In asynchronous mode, return `pending` and schedule a **proof fetcher** job (Mongo job doc + backoff). * **Mirrors/dual logs**: * When `logPreference="both"`, submit to primary and mirror; store **both** UUIDs (primary canonical). * Optional **cloud endorsement**: POST to the Stella Ops cloud `/attest/endorse` with `{uuid, artifactSha256}`; store returned endorsement id. --- ## 6) Security model * **mTLS required** for submission from **Signer** (CA‑pinned). * **Authority token** with `aud=attestor` and DPoP/mTLS binding must be presented; Attestor verifies both. * **Bundle acceptance policy**: * DSSE signature must chain to the configured **Fulcio** (keyless) or **KMS/HSM** roots. * SAN (Subject Alternative Name) must match **Signer identity** policy (e.g., `urn:stellaops:signer` or pinned OIDC issuer). * Predicate `predicateType` must be on allowlist (sbom/report/vex-export). * `subject.digest.sha256` values must be present and well‑formed (hex). * **No public submission** path. **Never** accept bundles from untrusted clients. * **Client certificate allowlists**: optional `security.mtls.allowedSubjects` / `allowedThumbprints` tighten peer identity checks beyond CA pinning. * **Rate limits**: token-bucket per caller derived from `quotas.perCaller` (QPS/burst) returns `429` + `Retry-After` when exceeded. * **Scope enforcement**: API separates `attestor.write`, `attestor.verify`, and `attestor.read` policies; verification/list endpoints accept read or verify scopes while submission endpoints remain write-only. * **Request hygiene**: JSON content-type is mandatory (415 returned otherwise); DSSE payloads are capped (default 2 MiB), certificate chains limited to six entries, and signatures to six per envelope to mitigate parsing abuse. * **Redaction**: Attestor never logs secret material; DSSE payloads **should** be public by design (SBOMs/reports). If customers require redaction, enforce policy at Signer (predicate minimization) **before** Attestor. --- ## 7) Storage & archival * **Entries** in Mongo provide a local ledger keyed by `rekorUuid` and **artifact sha256** for quick reverse lookups. * **S3 archival** (if enabled): ``` s3://stellaops/attest/ dsse/.json proof/.json bundle/.zip # optional verification bundle ``` * **Verification bundles** (zip): * DSSE (`*.dsse.json`), proof (`*.proof.json`), `chain.pem` (certs), `README.txt` with verification steps & hashes. --- ## 8) Observability & audit **Metrics** (Prometheus): * `attestor.sign_total{result,algorithm,provider}` * `attestor.sign_latency_seconds{algorithm,provider}` * `attestor.submit_total{result,backend}` * `attestor.submit_latency_seconds{backend}` * `attestor.proof_fetch_total{subject,issuer,policy,result,attestor.log.backend}` * `attestor.verify_total{subject,issuer,policy,result}` * `attestor.verify_latency_seconds{subject,issuer,policy,result}` * `attestor.dedupe_hits_total` * `attestor.errors_total{type}` SLO guardrails: * `attestor.verify_latency_seconds` P95 ≤ 2 s per policy. * `attestor.verify_total{result="failed"}` ≤ 1 % of `attestor.verify_total` over 30 min rolling windows. **Correlation**: * HTTP callers may supply `X-Correlation-Id`; Attestor will echo the header and push `CorrelationId` into the log scope for cross-service tracing. **Tracing**: * Spans: `attestor.sign`, `validate`, `rekor.submit`, `rekor.poll`, `persist`, `archive`, `attestor.verify`, `attestor.verify.refresh_proof`. **Audit**: * Immutable `audit` rows (ts, caller, action, hashes, uuid, index, backend, result, latency). --- ## 9) Configuration (YAML) ```yaml attestor: listen: "https://0.0.0.0:8444" security: mtls: caBundle: /etc/ssl/signer-ca.pem requireClientCert: true authority: issuer: "https://authority.internal" jwksUrl: "https://authority.internal/jwks" requireSenderConstraint: "dpop" # or "mtls" signerIdentity: mode: ["keyless","kms"] fulcioRoots: ["/etc/fulcio/root.pem"] allowedSANs: ["urn:stellaops:signer"] kmsKeys: ["kms://cluster-kms/stellaops-signer"] submissionLimits: maxPayloadBytes: 2097152 maxCertificateChainEntries: 6 maxSignatures: 6 signing: preferredProviders: ["kms","bouncycastle.ed25519","default"] kms: enabled: true rootPath: "/var/lib/stellaops/kms" password: "${ATTESTOR_KMS_PASSWORD}" keys: - keyId: "kms-primary" algorithm: ES256 mode: kms provider: "kms" providerKeyId: "kms-primary" kmsVersionId: "v1" - keyId: "ed25519-offline" algorithm: Ed25519 mode: keyful provider: "bouncycastle.ed25519" materialFormat: base64 materialPath: "/etc/stellaops/keys/ed25519.key" certificateChain: - "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----" rekor: primary: url: "https://rekor-v2.internal" proofTimeoutMs: 15000 pollIntervalMs: 250 maxAttempts: 60 mirror: enabled: false url: "https://rekor-v2.mirror" mongo: uri: "mongodb://mongo/attestor" s3: enabled: true endpoint: "http://minio:9000" bucket: "stellaops" prefix: "attest/" objectLock: "governance" redis: url: "redis://redis:6379/2" quotas: perCaller: qps: 50 burst: 100 ``` **Notes:** * `signing.preferredProviders` defines the resolution order when multiple providers support the requested algorithm. Omit to fall back to registration order. * File-backed KMS (`signing.kms`) is required when at least one key uses `mode: kms`; the password should be injected via secret store or environment. * For keyful providers, supply inline `material` or `materialPath` plus `materialFormat` (`pem` (default), `base64`, or `hex`). KMS keys ignore these fields and require `kmsVersionId`. * `certificateChain` entries are appended to returned bundles so offline verifiers do not need to dereference external stores. --- ## 10) End‑to‑end sequences **A) Submit & include (happy path)** ```mermaid sequenceDiagram autonumber participant SW as Scanner.WebService participant SG as Signer participant AT as Attestor participant RK as Rekor v2 SW->>SG: POST /sign/dsse (OpTok+PoE) SG-->>SW: DSSE bundle (+certs) SW->>AT: POST /rekor/entries (mTLS + OpTok) AT->>AT: Validate DSSE (chain to Fulcio/KMS; signer identity) AT->>RK: submit(bundle) RK-->>AT: {uuid, index?} AT->>RK: poll inclusion until proof or timeout RK-->>AT: inclusion proof (checkpoint + path) AT-->>SW: {uuid, index, proof, logURL} ``` **B) Verify by artifact digest (CLI)** ```mermaid sequenceDiagram autonumber participant CLI as stellaops verify participant SW as Scanner.WebService participant AT as Attestor CLI->>SW: GET /catalog/artifacts/{id} SW-->>CLI: {artifactSha256, rekor: {uuid}} CLI->>AT: POST /rekor/verify { uuid } AT-->>CLI: { ok: true, index, logURL } ``` --- ## 11) Failure modes & responses | Condition | Return | Details | | | | ------------------------------------- | ----------------------- | --------------------------------------------------------- | -------- | ------------ | | mTLS/OpTok invalid | `401 invalid_token` | Include `WWW-Authenticate` DPoP challenge when applicable | | | | Bundle not signed by trusted identity | `403 chain_untrusted` | DSSE accepted only from Signer identities | | | | Duplicate bundle | `409 duplicate_bundle` | Return existing `uuid` (idempotent) | | | | Rekor unreachable/timeout | `502 rekor_unavailable` | Retry with backoff; surface `Retry-After` | | | | Inclusion proof timeout | `202 accepted` | `status=pending`, background job continues to fetch proof | | | | Archive failure | `207 multi-status` | Entry recorded; archive will retry asynchronously | | | | Verification mismatch | `400 verify_failed` | Include reason: chain | leafHash | rootMismatch | --- ## 12) Performance & scale * Stateless; scale horizontally. * **Targets**: * Submit+proof P95 ≤ **300 ms** (warm log; local Rekor). * Verify P95 ≤ **30 ms** from cache; ≤ **120 ms** with live proof fetch. * 1k submissions/minute per replica sustained. * **Hot caches**: `dedupe` (bundle hash → uuid), recent `entries` by artifact sha256. --- ## 13) Testing matrix * **Happy path**: valid DSSE, inclusion within timeout. * **Idempotency**: resubmit same `bundleSha256` → same `uuid`. * **Security**: reject non‑Signer mTLS, wrong `aud`, DPoP replay, untrusted cert chain, forbidden predicateType. * **Rekor variants**: promise‑then‑proof, proof delayed, mirror dual‑submit, mirror failure. * **Verification**: corrupt leaf path, wrong root, tampered bundle. * **Throughput**: soak test with 10k submissions; latency SLOs, zero drops. --- ## 14) Implementation notes * Language: **.NET 10** minimal API; `HttpClient` with **sockets handler** tuned for HTTP/2. * JSON: **canonical writer** for DSSE payload hashing. * Crypto: use **BouncyCastle**/**System.Security.Cryptography**; PEM parsing for cert chains. * Rekor client: pluggable driver; treat backend errors as retryable/non‑retryable with granular mapping. * Safety: size caps on bundles; decompress bombs guarded; strict UTF‑8. * CLI integration: `stellaops verify attestation ` calls `/rekor/verify`. --- ## 15) Optional features * **Dual‑log** write (primary + mirror) and **cross‑log proof** packaging. * **Cloud endorsement**: send `{uuid, artifactSha256}` to Stella Ops cloud; store returned endorsement id for marketing/chain‑of‑custody. * **Checkpoint pinning**: periodically pin latest Rekor checkpoints to an external audit store for independent monitoring. --- ## 16) Observability (stub) - Runbook + dashboard placeholder for offline import: `operations/observability.md`, `operations/dashboards/attestor-observability.json`. - Metrics to surface: signing latency p95/p99, verification failure rate, transparency log submission lag, key rotation age, queue backlog, attestation bundle size histogram. - Health endpoints: `/health/liveness`, `/health/readiness`, `/status`; verification probe `/api/attestations/verify` once demo bundle is available (see runbook). - Alert hints: signing latency > 1s p99, verification failure spikes, tlog submission lag >10s, key rotation age over policy threshold, backlog above configured threshold.