docs consolidation
This commit is contained in:
360
docs/modules/evidence/unified-model.md
Normal file
360
docs/modules/evidence/unified-model.md
Normal file
@@ -0,0 +1,360 @@
|
||||
# Unified Evidence Model
|
||||
|
||||
> **Module:** `StellaOps.Evidence.Core`
|
||||
> **Status:** Production
|
||||
> **Owner:** Platform Guild
|
||||
|
||||
## Overview
|
||||
|
||||
The Unified Evidence Model provides a standardized interface (`IEvidence`) and implementation (`EvidenceRecord`) for representing evidence across all StellaOps modules. This enables:
|
||||
|
||||
- **Cross-module evidence linking**: Evidence from Scanner, Attestor, Excititor, and Policy modules share a common contract.
|
||||
- **Content-addressed verification**: Evidence records are immutable and verifiable via deterministic hashing.
|
||||
- **Unified storage**: A single `IEvidenceStore` interface abstracts persistence across modules.
|
||||
- **Cryptographic attestation**: Multiple signatures from different signers (internal, vendor, CI, operator) can vouch for evidence.
|
||||
|
||||
## Core Types
|
||||
|
||||
### IEvidence Interface
|
||||
|
||||
```csharp
|
||||
public interface IEvidence
|
||||
{
|
||||
string SubjectNodeId { get; } // Content-addressed subject
|
||||
EvidenceType EvidenceType { get; } // Type discriminator
|
||||
string EvidenceId { get; } // Computed hash identifier
|
||||
ReadOnlyMemory<byte> Payload { get; } // Canonical JSON payload
|
||||
IReadOnlyList<EvidenceSignature> Signatures { get; }
|
||||
EvidenceProvenance Provenance { get; }
|
||||
string? ExternalPayloadCid { get; } // For large payloads
|
||||
string PayloadSchemaVersion { get; }
|
||||
}
|
||||
```
|
||||
|
||||
### EvidenceType Enum
|
||||
|
||||
The platform supports these evidence types:
|
||||
|
||||
| Type | Value | Description | Example Payload |
|
||||
|------|-------|-------------|-----------------|
|
||||
| `Reachability` | 1 | Call graph analysis | Paths, confidence, graph digest |
|
||||
| `Scan` | 2 | Vulnerability finding | CVE, severity, affected package |
|
||||
| `Policy` | 3 | Policy evaluation | Rule ID, verdict, inputs |
|
||||
| `Artifact` | 4 | SBOM entry metadata | PURL, digest, build info |
|
||||
| `Vex` | 5 | VEX statement | Status, justification, impact |
|
||||
| `Epss` | 6 | EPSS score | Score, percentile, model date |
|
||||
| `Runtime` | 7 | Runtime observation | eBPF/ETW traces, call frames |
|
||||
| `Provenance` | 8 | Build provenance | SLSA attestation, builder info |
|
||||
| `Exception` | 9 | Applied exception | Exception ID, reason, expiry |
|
||||
| `Guard` | 10 | Guard/gate analysis | Gate type, condition, bypass |
|
||||
| `Kev` | 11 | KEV status | In-KEV flag, added date |
|
||||
| `License` | 12 | License analysis | SPDX ID, compliance status |
|
||||
| `Dependency` | 13 | Dependency metadata | Graph edge, version range |
|
||||
| `Custom` | 100 | User-defined | Schema-versioned custom payload |
|
||||
|
||||
### EvidenceRecord
|
||||
|
||||
The concrete implementation with deterministic identity:
|
||||
|
||||
```csharp
|
||||
public sealed record EvidenceRecord : IEvidence
|
||||
{
|
||||
public static EvidenceRecord Create(
|
||||
string subjectNodeId,
|
||||
EvidenceType evidenceType,
|
||||
ReadOnlyMemory<byte> payload,
|
||||
EvidenceProvenance provenance,
|
||||
string payloadSchemaVersion,
|
||||
IReadOnlyList<EvidenceSignature>? signatures = null,
|
||||
string? externalPayloadCid = null);
|
||||
|
||||
public bool VerifyIntegrity();
|
||||
}
|
||||
```
|
||||
|
||||
**EvidenceId Computation:**
|
||||
|
||||
The `EvidenceId` is a SHA-256 hash of the canonicalized fields using versioned prefixing:
|
||||
|
||||
```
|
||||
EvidenceId = "evidence:" + CanonJson.HashVersionedPrefixed("IEvidence", "v1", {
|
||||
SubjectNodeId,
|
||||
EvidenceType,
|
||||
PayloadHash,
|
||||
Provenance.GeneratorId,
|
||||
Provenance.GeneratorVersion,
|
||||
Provenance.GeneratedAt (ISO 8601)
|
||||
})
|
||||
```
|
||||
|
||||
### EvidenceSignature
|
||||
|
||||
Cryptographic attestation by a signer:
|
||||
|
||||
```csharp
|
||||
public sealed record EvidenceSignature
|
||||
{
|
||||
public required string SignerId { get; init; }
|
||||
public required string Algorithm { get; init; } // ES256, RS256, EdDSA
|
||||
public required string SignatureBase64 { get; init; }
|
||||
public required DateTimeOffset SignedAt { get; init; }
|
||||
public SignerType SignerType { get; init; }
|
||||
public IReadOnlyList<string>? CertificateChain { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
**SignerType Values:**
|
||||
- `Internal` (0): StellaOps service
|
||||
- `Vendor` (1): External vendor/supplier
|
||||
- `CI` (2): CI/CD pipeline
|
||||
- `Operator` (3): Human operator
|
||||
- `TransparencyLog` (4): Rekor/transparency log
|
||||
- `Scanner` (5): Security scanner
|
||||
- `PolicyEngine` (6): Policy engine
|
||||
- `Unknown` (255): Unclassified
|
||||
|
||||
### EvidenceProvenance
|
||||
|
||||
Generation context:
|
||||
|
||||
```csharp
|
||||
public sealed record EvidenceProvenance
|
||||
{
|
||||
public required string GeneratorId { get; init; }
|
||||
public required string GeneratorVersion { get; init; }
|
||||
public required DateTimeOffset GeneratedAt { get; init; }
|
||||
public string? CorrelationId { get; init; }
|
||||
public Guid? TenantId { get; init; }
|
||||
// ... additional fields
|
||||
}
|
||||
```
|
||||
|
||||
## Adapters
|
||||
|
||||
Adapters convert module-specific evidence types to the unified `IEvidence` interface:
|
||||
|
||||
### Available Adapters
|
||||
|
||||
| Adapter | Source Module | Source Type | Target Evidence Types |
|
||||
|---------|---------------|-------------|----------------------|
|
||||
| `EvidenceBundleAdapter` | Scanner | `EvidenceBundle` | Reachability, Vex, Provenance, Scan |
|
||||
| `EvidenceStatementAdapter` | Attestor | `EvidenceStatement` (in-toto) | Scan |
|
||||
| `ProofSegmentAdapter` | Scanner | `ProofSegment` | Varies by segment type |
|
||||
| `VexObservationAdapter` | Excititor | `VexObservation` | Vex, Provenance |
|
||||
| `ExceptionApplicationAdapter` | Policy | `ExceptionApplication` | Exception |
|
||||
|
||||
### Adapter Interface
|
||||
|
||||
```csharp
|
||||
public interface IEvidenceAdapter<TSource>
|
||||
{
|
||||
IReadOnlyList<IEvidence> Convert(
|
||||
TSource source,
|
||||
string subjectNodeId,
|
||||
EvidenceProvenance provenance);
|
||||
|
||||
bool CanConvert(TSource source);
|
||||
}
|
||||
```
|
||||
|
||||
### Using Adapters
|
||||
|
||||
Adapters use **input DTOs** to avoid circular dependencies:
|
||||
|
||||
```csharp
|
||||
// Using VexObservationAdapter
|
||||
var adapter = new VexObservationAdapter();
|
||||
|
||||
var input = new VexObservationInput
|
||||
{
|
||||
ObservationId = "obs-001",
|
||||
ProviderId = "nvd",
|
||||
StreamId = "cve-feed",
|
||||
// ... other fields from VexObservation
|
||||
};
|
||||
|
||||
var provenance = new EvidenceProvenance
|
||||
{
|
||||
GeneratorId = "excititor-ingestor",
|
||||
GeneratorVersion = "1.0.0",
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
if (adapter.CanConvert(input))
|
||||
{
|
||||
IReadOnlyList<IEvidence> records = adapter.Convert(
|
||||
input,
|
||||
subjectNodeId: "sha256:abc123",
|
||||
provenance);
|
||||
}
|
||||
```
|
||||
|
||||
## Evidence Store
|
||||
|
||||
### IEvidenceStore Interface
|
||||
|
||||
```csharp
|
||||
public interface IEvidenceStore
|
||||
{
|
||||
Task<EvidenceRecord> StoreAsync(
|
||||
EvidenceRecord record,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<IReadOnlyList<EvidenceRecord>> StoreBatchAsync(
|
||||
IEnumerable<EvidenceRecord> records,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<EvidenceRecord?> GetByIdAsync(
|
||||
string evidenceId,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<IReadOnlyList<EvidenceRecord>> GetBySubjectAsync(
|
||||
string subjectNodeId,
|
||||
EvidenceType? evidenceType = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<IReadOnlyList<EvidenceRecord>> GetByTypeAsync(
|
||||
EvidenceType evidenceType,
|
||||
int limit = 100,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<bool> ExistsAsync(
|
||||
string evidenceId,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<bool> DeleteAsync(
|
||||
string evidenceId,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
### Implementations
|
||||
|
||||
- **`InMemoryEvidenceStore`**: Thread-safe in-memory store for testing and development.
|
||||
- **`PostgresEvidenceStore`** (planned): Production store with tenant isolation and indexing.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Creating Evidence
|
||||
|
||||
```csharp
|
||||
var provenance = new EvidenceProvenance
|
||||
{
|
||||
GeneratorId = "scanner-service",
|
||||
GeneratorVersion = "2.1.0",
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
TenantId = tenantId
|
||||
};
|
||||
|
||||
// Serialize payload to canonical JSON
|
||||
var payloadBytes = CanonJson.Canonicalize(new
|
||||
{
|
||||
cveId = "CVE-2024-1234",
|
||||
severity = "HIGH",
|
||||
affectedPackage = "pkg:npm/lodash@4.17.20"
|
||||
});
|
||||
|
||||
var evidence = EvidenceRecord.Create(
|
||||
subjectNodeId: "sha256:abc123def456...",
|
||||
evidenceType: EvidenceType.Scan,
|
||||
payload: payloadBytes,
|
||||
provenance: provenance,
|
||||
payloadSchemaVersion: "scan/v1");
|
||||
```
|
||||
|
||||
### Storing and Retrieving
|
||||
|
||||
```csharp
|
||||
var store = new InMemoryEvidenceStore();
|
||||
|
||||
// Store
|
||||
await store.StoreAsync(evidence);
|
||||
|
||||
// Retrieve by ID
|
||||
var retrieved = await store.GetByIdAsync(evidence.EvidenceId);
|
||||
|
||||
// Retrieve all evidence for a subject
|
||||
var allForSubject = await store.GetBySubjectAsync(
|
||||
"sha256:abc123def456...",
|
||||
evidenceType: EvidenceType.Scan);
|
||||
|
||||
// Verify integrity
|
||||
bool isValid = retrieved!.VerifyIntegrity();
|
||||
```
|
||||
|
||||
### Cross-Module Evidence Linking
|
||||
|
||||
```csharp
|
||||
// Scanner produces evidence bundle
|
||||
var bundle = scanner.ProduceEvidenceBundle(target);
|
||||
|
||||
// Convert to unified evidence
|
||||
var adapter = new EvidenceBundleAdapter();
|
||||
var evidenceRecords = adapter.Convert(bundle, subjectNodeId, provenance);
|
||||
|
||||
// Store all records
|
||||
await store.StoreBatchAsync(evidenceRecords);
|
||||
|
||||
// Later, any module can query by subject
|
||||
var allEvidence = await store.GetBySubjectAsync(subjectNodeId);
|
||||
|
||||
// Filter by type
|
||||
var reachabilityEvidence = allEvidence
|
||||
.Where(e => e.EvidenceType == EvidenceType.Reachability);
|
||||
var vexEvidence = allEvidence
|
||||
.Where(e => e.EvidenceType == EvidenceType.Vex);
|
||||
```
|
||||
|
||||
## Schema Versioning
|
||||
|
||||
Each evidence type payload has a schema version (`PayloadSchemaVersion`) for forward compatibility:
|
||||
|
||||
- `scan/v1`: Initial scan evidence schema
|
||||
- `reachability/v1`: Reachability evidence schema
|
||||
- `vex-statement/v1`: VEX statement evidence schema
|
||||
- `proof-segment/v1`: Proof segment evidence schema
|
||||
- `exception-application/v1`: Exception application schema
|
||||
|
||||
Consumers should check `PayloadSchemaVersion` before deserializing payloads to handle schema evolution.
|
||||
|
||||
## Integration Patterns
|
||||
|
||||
### Module Integration
|
||||
|
||||
Each module that produces evidence should:
|
||||
|
||||
1. Create an adapter if converting from module-specific types
|
||||
2. Use `EvidenceRecord.Create()` for new evidence
|
||||
3. Store evidence via `IEvidenceStore`
|
||||
4. Include provenance with generator identification
|
||||
|
||||
### Verification Flow
|
||||
|
||||
```
|
||||
1. Retrieve evidence by SubjectNodeId
|
||||
2. Call VerifyIntegrity() to check EvidenceId
|
||||
3. Verify signatures against known trust roots
|
||||
4. Deserialize and validate payload against schema
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The `StellaOps.Evidence.Core.Tests` project includes:
|
||||
|
||||
- **111 unit tests** covering:
|
||||
- EvidenceRecord creation and hash computation
|
||||
- InMemoryEvidenceStore CRUD operations
|
||||
- All adapter conversions (VexObservation, ExceptionApplication, ProofSegment)
|
||||
- Edge cases and error handling
|
||||
|
||||
Run tests:
|
||||
```bash
|
||||
dotnet test src/__Libraries/StellaOps.Evidence.Core.Tests/
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Proof Chain Architecture](../attestor/proof-chain.md)
|
||||
- [Evidence Bundle Design](../scanner/evidence-bundle.md)
|
||||
- [VEX Observation Model](../excititor/vex-observation.md)
|
||||
- [Policy Exceptions](../policy/exceptions.md)
|
||||
Reference in New Issue
Block a user