Files
git.stella-ops.org/docs/modules/attestor/workflows.md
master f98cea3bcf Add Authority Advisory AI and API Lifecycle Configuration
- Introduced AuthorityAdvisoryAiOptions and related classes for managing advisory AI configurations, including remote inference options and tenant-specific settings.
- Added AuthorityApiLifecycleOptions to control API lifecycle settings, including legacy OAuth endpoint configurations.
- Implemented validation and normalization methods for both advisory AI and API lifecycle options to ensure proper configuration.
- Created AuthorityNotificationsOptions and its related classes for managing notification settings, including ack tokens, webhooks, and escalation options.
- Developed IssuerDirectoryClient and related models for interacting with the issuer directory service, including caching mechanisms and HTTP client configurations.
- Added support for dependency injection through ServiceCollectionExtensions for the Issuer Directory Client.
- Updated project file to include necessary package references for the new Issuer Directory Client library.
2025-11-02 13:50:25 +02:00

17 KiB
Raw Blame History

Attestor Verification Workflows

How StellaOps turns DSSE bundles into verifiable evidence, how the verification API reports outcomes, and how explainability signals surface in UI/CLI flows.

⚠️ 2025-11-01 coordination note: StellaOps.Attestor.WebService is failing to compile until downstream fixes land (Contracts/AttestationBundleContracts.cs null-coalescing update and scope/token variables restored in Program.cs). Verification flows ship in infrastructure/tests, but the WebService hand-off stays blocked — track via ATTESTOR-73-002 (see Attestor task board).

1. Verification flow (API and service contract)

  • Entry point. POST /api/v1/rekor/verify deserialises to AttestorVerificationRequest.
  • Resolution order. The service tries uuid, then canonicalised bundle, then artifactSha256. At least one selector must be present (invalid_query otherwise).
  • Optional proof refresh. refreshProof=true forces a Rekor lookup before returning. Proofs are cached in Mongo.
  • Signature replay. Supplying bundle lets the service recompute the canonical hash and re-run signature checks; omitting the bundle skips those steps but still validates Merkle proofs and cached policy decisions.
  • Auth scopes. Endpoints demand attestor.verify (write scope is also accepted); read-only detail/list APIs require attestor.read at minimum.

1.1 Request properties

Field Type Required Purpose
uuid string optional Rekor V2 UUID to verify and (optionally) refresh.
bundle object optional DSSE envelope (same shape as submission) for signature re-verification.
artifactSha256 string optional Resolve the most recent entry for an attestable artefact digest.
subject string optional Logical subject identifier used for cache/telemetry tagging; defaults to the stored artifact digest.
envelopeId string optional Stable identifier for the DSSE bundle (typically the canonical hash); enables cache lookups.
policyVersion string optional Policy digest/version driving verification; feeds cache keys and observability dimensions.
refreshProof bool optional (default false) Pull the current inclusion proof and checkpoint from Rekor before evaluating.

All selectors are mutually compatible; if more than one is set the service uses the first match (uuidbundleartifactSha256).

1.2 Response schema (AttestorVerificationResult)

Field Type Description
ok bool true when the entry status is included and no issues were recorded.
uuid string Rekor UUID that satisfied the query. Useful for follow-up fetches.
index number (int64) Rekor log index, when supplied by the backend.
logUrl string Fully-qualified Rekor entry URL for operators and auditors.
status string Transparency-log status seen in Mongo (included, pending, failed, …).
checkedAt string (ISO-8601 UTC) Timestamp emitted when the response is created.
issues array[string] Machine-readable explainability codes. Empty when ok=true.

Note: checkedAt is recomputed each call; cache hits do not recycle previous timestamps.

1.3 Success criteria

ok=true requires:

  1. Entry exists and status equals included.
  2. Canonical DSSE hash matches the stored bundle hash.
  3. Signature re-verification (when a bundle is supplied) succeeds.
  4. Inclusion proof validates against the cached or refreshed checkpoint.

Any deviation records at least one issue and flips ok to false. Consumers must inspect issues rather than inferring from status alone.

2. Verification report schema

AttestorVerificationResult carries the flattened summary shown above. When callers request the detailed report (GET /api/v1/rekor/entries/{uuid}?refresh=true or via SDK) they receive a VerificationReport shaped as follows:

{
  "overallStatus": "pass",
  "succeeded": true,
  "policy": { ... },
  "issuer": { ... },
  "freshness": { ... },
  "signatures": { ... },
  "transparency": { ... },
  "issues": [ "bundle_hash_mismatch" ]
}
Field Type Description
overallStatus string (pass, warn, fail, skipped) Aggregated verdict derived from the individual section statuses.
succeeded bool Convenience flag; true when overallStatus ∈ {pass, warn}.
policy object Results from policy evaluation (see below).
issuer object Identity/result of the signing entity.
freshness object Age analysis relative to policy settings.
signatures object Signature validation summary.
transparency object Inclusion proof / checkpoint evaluation summary.
issues array[string] De-duplicated set drawn from the sections; order is deterministic and stable.

2.1 policy

Field Description
status Section verdict (pass, warn, fail, skipped).
policyId / policyVersion DSL identifier and revision used for evaluation.
verdict Policy outcome (allow, challenge, deny, etc.).
issues Policy-specific explainability codes (e.g., policy_rule_blocked).
attributes Key/value map emitted by the policy for downstream observability (e.g., applicable rules, matched waivers).

2.2 issuer

Field Description
status Result of issuer validation.
mode Signing mode detected (keyless, kms, unknown).
issuer Distinguished name / issuer URI recorded during signing.
subjectAlternativeName SAN pulled from the Fulcio certificate (keyless) or recorded KMS identity.
keyId Logical key identifier associated with the signature.
issues Issuer-specific issues (e.g., issuer_trust_root_mismatch, signer_mode_unsupported:kid).

2.3 freshness

Field Description
status fail when the attestation exceeds verification.freshnessMaxAgeMinutes; warn when only the warning threshold is hit.
createdAt Timestamp embedded in the attestation metadata.
evaluatedAt Server-side timestamp used for age calculations.
age ISO8601 duration of evaluatedAt - createdAt.
maxAge Policy-driven ceiling (null when unchecked).
issues freshness_max_age_exceeded, freshness_warning, etc.

2.4 signatures

Field Description
status Signature validation verdict.
bundleProvided true when canonical DSSE bytes were supplied.
totalSignatures Count observed in the DSSE envelope.
verifiedSignatures Number of signatures that validated against trusted keys.
requiredSignatures Policy / configuration minimum enforced.
issues Signature codes such as bundle_payload_invalid_base64, signature_invalid, signer_mode_unknown.

2.5 transparency

Field Description
status Inclusion proof / checkpoint verdict.
proofPresent Whether a proof document was available.
checkpointPresent Indicates the Rekor checkpoint existed and parsed.
inclusionPathPresent true when the Merkle path array contained nodes.
issues Merkle/rekor codes (proof_missing, proof_leafhash_mismatch, checkpoint_missing, proof_root_mismatch).

2.6 Issue catalogue (non-exhaustive)

Code Trigger Notes
bundle_hash_mismatch Canonical DSSE hash differs from stored value. Often indicates tampering or inconsistent canonicalisation.
bundle_payload_invalid_base64 DSSE payload cannot be base64-decoded. Validate producer pipeline; the attestation is unusable.
signature_invalid At least one signature failed cryptographic verification. Consider checking key rotation / revocation status.
signer_mode_unknown / signer_mode_unsupported:<mode> Signing mode not configured for this installation. Update attestorOptions.security.signerIdentity.mode.
issuer_trust_root_mismatch Certificate chain does not terminate in configured Fulcio/KMS roots. Check Fulcio bundle / KMS configuration.
freshness_max_age_exceeded Attestation older than permitted maximum. Regenerate attestation or extend policy window.
proof_missing No inclusion proof stored or supplied. When running offline, import bundles with proofs or allow warn-level policies.
proof_root_mismatch Rebuilt Merkle root differs from checkpoint. Proof may be stale or log compromised; escalate.
checkpoint_missing No Rekor checkpoint available. Configure RequireCheckpoint=false to downgrade severity.

Downstream consumers (UI, CLI, policy studio) should render human-readable messages but must retain the exact issue codes for automation and audit replay.

3. Explainability signals

  1. Canonicalisation. The service replays DSSE canonicalisation to derive bundleSha256. Failures surface as bundle_hash_mismatch or decoding errors.
  2. Signature checks. Mode-aware handling:
    • kms (HMAC) compares against configured shared secrets.
    • keyless rebuilds the certificate chain, enforces Fulcio roots, SAN allow-lists, and verifies with the leaf certificate.
    • Unknown modes emit signer_mode_unknown / signer_mode_unsupported:<mode>.
  3. Proof acquisition. When refreshProof is requested the Rekor backend may contribute a textual issue (Proof refresh failed: …) without stopping evaluation.
  4. Merkle validation. Structured helper ensures leaf hash, path orientation, and checkpoint root are consistent; each validation failure has a discrete issue code.
  5. Observability. The meter attestor.verify_total increments with result=ok|failed; structured logs and traces carry the same issues vector for UI/CLI drill-down.

All issues are appended in detection order to simplify chronological replay in the Consoles chain-of-custody view.

3. Issue catalogue

Code Trigger Operator guidance
bundle_hash_mismatch Canonicalised DSSE hash differs from stored bundle hash. Re-download artefact; investigate tampering or submission races.
bundle_payload_invalid_base64 Payload could not be base64-decoded. Ensure bundle transport preserved payload; capture original DSSE for forensics.
signature_invalid_kms HMAC verification failed for mode=kms. Confirm shared secret alignment with Signer; rotate keys if drift detected.
signer_mode_unknown Entry lacks signer mode metadata and bundle omitted it. Re-ingest bundle or inspect submission pipeline metadata.
signer_mode_unsupported:<mode> Signer mode is unsupported by the verifier. Add support or block unsupported issuers in policy.
kms_key_missing No configured KMS secrets to verify mode=kms. Populate security:signerIdentity:kmsKeys in Attestor config before retry.
signature_invalid_base64 One or more signatures were not valid base64. Bundle corruption; capture raw payload and re-submit.
certificate_chain_missing mode=keyless bundle lacked any certificates. Ensure Signer attaches Fulcio chain; review submission pipeline.
certificate_chain_invalid Certificates could not be parsed. Fetch original DSSE bundle for repair; confirm certificate encoding.
certificate_chain_untrusted[:detail] Chain failed custom-root validation. Import correct Fulcio roots or investigate potential impersonation.
certificate_san_untrusted Leaf SAN not in configured allow-list. Update allow-list or revoke offending issuer.
signature_invalid No signature validated with supplied public keys. Treat as tampering; trigger incident response.
proof_missing No Merkle proof stored for the entry. Re-run with refreshProof=true; check Rekor availability.
bundle_hash_decode_failed Stored bundle hash could not be decoded. Verify Mongo record integrity; re-enqueue submission if necessary.
proof_inclusion_missing Inclusion section absent from proof. Retry proof refresh; inspect Rekor health.
proof_leafhash_decode_failed Leaf hash malformed. Replay submission; inspect Rekor data corruption.
proof_leafhash_mismatch Leaf hash differs from canonical bundle hash. Raises tamper alert; reconcile Rekor entry vs stored bundle.
proof_path_decode_failed Inclusion path entry malformed. Same action as above; likely Rekor data corruption.
proof_path_orientation_missing Inclusion path lacks left/right marker. File Rekor bug; fallback to mirror log if configured.
checkpoint_missing Proof lacks checkpoint metadata. Retry refresh; ensure Rekor is configured to return checkpoints.
checkpoint_root_decode_failed Checkpoint root hash malformed. Investigate Rekor/mirror integrity before trusting log.
proof_root_mismatch Computed root hash != checkpoint root. Critical alert; assume inclusion proof compromised.
Proof refresh failed: … Rekor fetch threw an exception. Message includes upstream error; surface alongside telemetry for debugging.

Future explainability flags must follow the same pattern: short, lowercase codes with optional suffix payload (code:detail).

4. Worked examples

4.1 Successful verification

{
  "ok": true,
  "uuid": "0192fdb4-a82b-7f90-b894-6fd1dd918b85",
  "index": 73421,
  "logUrl": "https://rekor.stellaops.test/api/v2/log/entries/0192fdb4a82b7f90b8946fd1dd918b85",
  "status": "included",
  "checkedAt": "2025-11-01T17:06:52.182394Z",
  "issues": []
}

This mirrors the happy-path asserted in AttestorVerificationServiceTests.VerifyAsync_ReturnsOk_ForExistingUuid, which replays the entire submission→verification loop.

4.2 Tampered bundle

{
  "ok": false,
  "uuid": "0192fdb4-a82b-7f90-b894-6fd1dd918b85",
  "index": 73421,
  "logUrl": "https://rekor.stellaops.test/api/v2/log/entries/0192fdb4a82b7f90b8946fd1dd918b85",
  "status": "included",
  "checkedAt": "2025-11-01T17:09:05.443218Z",
  "issues": [
    "bundle_hash_mismatch",
    "signature_invalid"
  ]
}

Derived from AttestorVerificationServiceTests.VerifyAsync_FlagsTamperedBundle, which flips the DSSE payload and expects both issues to surface. CLI and Console consumers should display these codes verbatim and provide remediation tips from the table above.

5. Validating the documentation

  • Run dotnet test src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Tests to exercise the scenarios behind the examples.
  • API integrators can curl the verify endpoint and compare responses with the JSON above.
  • UI/CLI teams should ensure explainability tooltips and runbooks reference the same issue catalogue.

Keeping the documentation aligned with the test suite guarantees explainability remains deterministic and audit-friendly.

6. Offline bundles & air-gapped verification

StellaOps Attestor now supports packaging attestations for sealed environments and rehydrating them without calling Rekor:

  • Export bundles. POST /api/v1/attestations:export accepts either a list of Rekor UUIDs or filter criteria (subject, type, issuer, scope, createdAfter|Before, limit, continuationToken) and returns an attestor.bundle.v1 document. Each item contains the attestation entry, canonical DSSE payload (base64), optional proof payload, and metadata. Responses include a continuationToken so callers can page through large result sets (limits default to 100 and are capped at 200). JSON content is required and requests are gated by the attestor.read scope.
  • Import bundles. POST /api/v1/attestations:import ingests the bundle document, upserts attestation metadata, and restores the canonical DSSE/proof into the configured archive store. The S3 archive integration must be enabled; the response reports how many entries were imported versus updated, any skipped items, and issue codes (bundle_payload_invalid_base64, bundle_hash_mismatch, archive_disabled, …).
  • Offline verification. When replaying verification without log connectivity, submit the DSSE bundle and set offline=true on POST /api/v1/rekor/verify. The service reuses imported proofs when present and surfaces deterministic explainability codes (proof_missing, proof_inclusion_missing, …) instead of attempting Rekor fetches.

Tests AttestorBundleServiceTests.ExportAsync_AppliesFiltersAndContinuation, AttestationBundleEndpointsTests, AttestorVerificationServiceTests.VerifyAsync_OfflineSkipsProofRefreshWhenMissing, and AttestorVerificationServiceTests.VerifyAsync_OfflineUsesImportedProof exercise the exporter/importer, API contracts, and the offline verification path with and without witness data.