Files
git.stella-ops.org/docs/modules/attestor/architecture.md
master 7b5bdcf4d3 feat(docs): Add comprehensive documentation for Vexer, Vulnerability Explorer, and Zastava modules
- Introduced AGENTS.md, README.md, TASKS.md, and implementation_plan.md for Vexer, detailing mission, responsibilities, key components, and operational notes.
- Established similar documentation structure for Vulnerability Explorer and Zastava modules, including their respective workflows, integrations, and observability notes.
- Created risk scoring profiles documentation outlining the core workflow, factor model, governance, and deliverables.
- Ensured all modules adhere to the Aggregation-Only Contract and maintain determinism and provenance in outputs.
2025-10-30 00:09:39 +02:00

17 KiB
Raw Blame History

component_architecture_attestor.md — StellaOps Attestor (2025Q4)

Derived from Epic19 Attestor Console with provenance hooks aligned to the Export Center bundle workflows scoped in Epic10.

Scope. Implementationready 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 chainoftrust to StellaOps 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 transparencylogged, 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 thirdpartysigned bundles.
  • Attestor does not decide PASS/FAIL; it logs attestations for SBOMs, reports, and export artifacts.
  • Rekor v2 backends may be local (selfhosted) 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 — tilebacked 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 shortlived ratelimit buckets.
  • Licensing Service (optional) — “endorse” call for crosslog publishing when customer optsin.

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 scopesattestor.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.

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.

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.

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: ≥1000 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: "<rekor-uuid>",
      artifact: { sha256: "<sha256>", kind: "sbom|report|vex-export", imageDigest?, subjectUri? },
      bundleSha256: "<sha256>",                           // canonicalized DSSE
      index: <int>,                                       // 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:<sha256>", 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 2448h).
  • audit.ts for timerange queries.

3) Input contract (from Signer)

Attestor accepts only DSSE envelopes that satisfy all of:

  1. mTLS peer certificate maps to signer service (CApinned).
  2. Authority OpTok with aud=attestor, scope=attestor.write, DPoP or mTLS bound.
  3. DSSE envelope is signed by the Signers key (or includes a Fulcioissued cert chain) and chains to configured roots (Fulcio/KMS).
  4. Predicate type is one of StellaOps types (sbom/report/vexexport) with valid schema.
  5. subject[*].digest.sha256 is present and canonicalized.

Wire shape (JSON):

{
  "bundle": { "dsse": { "payloadType": "application/vnd.in-toto+json", "payload": "<b64>", "signatures": [ ... ] },
              "certificateChain": [ "-----BEGIN CERTIFICATE-----..." ],
              "mode": "keyless" },
  "meta": {
    "artifact": { "sha256": "<subject sha256>", "kind": "sbom|report|vex-export", "imageDigest": "sha256:..." },
    "bundleSha256": "<sha256 of canonical dsse>",
    "logPreference": "primary",               // "primary" | "mirror" | "both"
    "archive": true                           // whether Attestor should archive bundle to S3
  }
}

4) APIs

4.1 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:

    {
      "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.2 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.3 Verification (thirdparty 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 callerprovided hash (when given).
  • Response:

    { "ok": true, "uuid": "…", "index": 123, "logURL": "…", "checkedAt": "…" }
    

4.4 Batch submission (optional)

POST /api/v1/rekor/batch accepts an array of submission objects; processes with peritem results.


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 StellaOps cloud /attest/endorse with {uuid, artifactSha256}; store returned endorsement id.

6) Security model

  • mTLS required for submission from Signer (CApinned).

  • 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 wellformed (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.

  • 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/<bundleSha256>.json
      proof/<rekorUuid>.json
      bundle/<artifactSha256>.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.submit_total{result,backend}
  • attestor.submit_latency_seconds{backend}
  • attestor.proof_fetch_total{result}
  • attestor.verify_total{result}
  • attestor.dedupe_hits_total
  • attestor.errors_total{type}

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: validate, rekor.submit, rekor.poll, persist, archive, verify.

Audit:

  • Immutable audit rows (ts, caller, action, hashes, uuid, index, backend, result, latency).

9) Configuration (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"]
  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

10) Endtoend sequences

A) Submit & include (happy path)

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)

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 ≤ 300ms (warm log; local Rekor).
    • Verify P95 ≤ 30ms from cache; ≤ 120ms 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 nonSigner mTLS, wrong aud, DPoP replay, untrusted cert chain, forbidden predicateType.
  • Rekor variants: promisethenproof, proof delayed, mirror dualsubmit, 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/nonretryable with granular mapping.
  • Safety: size caps on bundles; decompress bombs guarded; strict UTF8.
  • CLI integration: stellaops verify attestation <uuid|bundle|artifact> calls /rekor/verify.

15) Optional features

  • Duallog write (primary + mirror) and crosslog proof packaging.
  • Cloud endorsement: send {uuid, artifactSha256} to StellaOps cloud; store returned endorsement id for marketing/chainofcustody.
  • Checkpoint pinning: periodically pin latest Rekor checkpoints to an external audit store for independent monitoring.