release orchestrator v1 draft and build fixes
This commit is contained in:
600
docs/architecture/EVIDENCE_PIPELINE_ARCHITECTURE.md
Normal file
600
docs/architecture/EVIDENCE_PIPELINE_ARCHITECTURE.md
Normal file
@@ -0,0 +1,600 @@
|
||||
# Evidence Pipeline Architecture - Consolidation Guide
|
||||
|
||||
**Version**: 1.0
|
||||
**Status**: Reference Architecture
|
||||
**Last Updated**: 2026-01-11
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes how existing Stella Ops components integrate to form the complete evidence pipeline. The key insight is that **most components already exist** - this guide shows how to wire them together.
|
||||
|
||||
---
|
||||
|
||||
## Component Map
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ EVIDENCE PIPELINE │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌──────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ Concelier │───▶│ CveSymbolMapping │───▶│ ReachabilityJob │ │
|
||||
│ │ (CVE Data) │ │ Service │ │ Executor │ │
|
||||
│ └─────────────┘ └──────────────────┘ └──────────┬──────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ │ ▼ │
|
||||
│ │ │ ┌───────────────────────┐ │
|
||||
│ │ │ │ ReachabilityAnalyzer │ │
|
||||
│ │ │ │ (BFS Call Graph) │ │
|
||||
│ │ │ └──────────┬────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ │ ▼ │
|
||||
│ │ │ ┌───────────────────────┐ │
|
||||
│ │ │ │ ReachabilityStack │ │
|
||||
│ │ │ │ Evaluator │ │
|
||||
│ │ │ │ (3-Layer Verdict) │ │
|
||||
│ │ │ └──────────┬────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ │ ┌───────────────┼───────────────┐ │
|
||||
│ │ │ ▼ ▼ ▼ │
|
||||
│ │ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
|
||||
│ │ │ │ Layer 1 │ │ Layer 2 │ │ Layer 3 │ │
|
||||
│ │ │ │ Static │ │ Binary │ │ Runtime │ │
|
||||
│ │ │ └────┬────┘ └────┬─────┘ └────┬────┘ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ ▼ ▼ ▼ │
|
||||
│ │ │ ┌──────────────────────────────────────┐ │
|
||||
│ │ │ │ EvidenceBundle │ │
|
||||
│ │ │ │ - ReachabilityEvidence │ │
|
||||
│ │ │ │ - PatchDiffEvidence │ │
|
||||
│ │ │ │ - RuntimeObservationEvidence │ │
|
||||
│ │ │ └─────────────────┬────────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ │ ▼ │
|
||||
│ │ │ ┌──────────────────────────────────────┐ │
|
||||
│ │ │ │ ReachabilityWitnessDsseBuilder │ │
|
||||
│ │ │ │ (in-toto Attestation) │ │
|
||||
│ │ │ └─────────────────┬────────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ │ ▼ │
|
||||
│ │ │ ┌──────────────────────────────────────┐ │
|
||||
│ │ │ │ EvidenceDbContext │ │
|
||||
│ │ │ │ (Postgres) │ │
|
||||
│ │ │ └─────────────────┬────────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ │ ▼ │
|
||||
│ │ │ ┌──────────────────────────────────────┐ │
|
||||
│ │ └───▶│ VexStatusDeterminer │ │
|
||||
│ │ │ (Verdict → VEX) │ │
|
||||
│ │ └─────────────────┬────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ │ ▼ │
|
||||
│ │ ┌──────────────────────────────────────┐ │
|
||||
│ └────────────────────────▶│ VexHub │ │
|
||||
│ │ (VEX Documents) │ │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Existing Components Reference
|
||||
|
||||
### Layer 1: Static Call Graph Analysis
|
||||
|
||||
**Already Implemented** - Wire together existing pieces.
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `ReachabilityAnalyzer` | `Scanner.CallGraph/Analysis/ReachabilityAnalyzer.cs` | BFS from entrypoints to sinks |
|
||||
| `CallGraphSnapshot` | `Scanner.CallGraph/CallGraphSnapshot.cs` | Immutable graph representation |
|
||||
| `DotNetCallGraphExtractor` | `Scanner.CallGraph/Extraction/DotNet/` | .NET-specific extraction |
|
||||
| `DotNetReachabilityLifter` | `Scanner.Reachability/Lifters/` | Lift to union model |
|
||||
| `ReachabilityUnionGraph` | `Scanner.Reachability/ReachabilityUnionSchemas.cs` | Unified multi-lang graph |
|
||||
|
||||
**Integration Point**:
|
||||
```csharp
|
||||
// In ReachabilityEvidenceJobExecutor
|
||||
var snapshot = await _callGraphCache.GetOrComputeAsync(imageDigest, ct);
|
||||
var result = _analyzer.Analyze(snapshot, new ReachabilityAnalysisOptions
|
||||
{
|
||||
ExplicitSinks = sinks.Select(s => s.CanonicalId).ToImmutableArray()
|
||||
});
|
||||
|
||||
var layer1 = new ReachabilityLayer1
|
||||
{
|
||||
IsReachable = result.ReachableSinkIds.Length > 0,
|
||||
Confidence = ConfidenceLevel.High,
|
||||
Paths = result.Paths.Select(ToLayer1Path).ToImmutableArray(),
|
||||
ReachingEntrypoints = result.Paths.Select(p => p.EntrypointId).Distinct().ToImmutableArray(),
|
||||
AnalysisMethod = "BFS"
|
||||
};
|
||||
```
|
||||
|
||||
### Layer 2: Binary Resolution
|
||||
|
||||
**Partially Implemented** - Add patch verification.
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `HeuristicScanner` | `Scanner.Analyzers.Native/HeuristicScanner.cs` | Binary format detection |
|
||||
| `NativeAnalyzer` | `Scanner.Analyzers.Native/Plugin/` | Native binary analysis |
|
||||
| B2R2 (dependency) | NuGet | Binary lifting/IR |
|
||||
|
||||
**New Component Needed**:
|
||||
```csharp
|
||||
// IBinaryDiffService implementation using B2R2
|
||||
public sealed class B2R2BinaryDiffService : IBinaryDiffService
|
||||
{
|
||||
public async Task<PatchDiffResult> DiffAsync(
|
||||
Stream vulnerableBinary,
|
||||
Stream patchedBinary,
|
||||
IReadOnlyList<string> targetSymbols,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Use B2R2.FrontEnd.BinFile to load binaries
|
||||
// Use B2R2.MiddleEnd.BinGraph for CFG comparison
|
||||
// Compare function IRs for targeted symbols
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Layer 3: Runtime Gating
|
||||
|
||||
**Models Exist** - Adapters needed.
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `RuntimeEvidence` | `Scanner.Analyzers.Native/RuntimeCapture/RuntimeEvidence.cs` | Evidence model |
|
||||
| `RuntimeLoadEvent` | Same file | Individual load event |
|
||||
| `RuntimeCaptureSession` | Same file | Session container |
|
||||
| `RuntimeCaptureOptions` | `RuntimeCaptureOptions.cs` | Configuration |
|
||||
|
||||
**Existing Model** (no changes needed):
|
||||
```csharp
|
||||
// Already defined in RuntimeEvidence.cs
|
||||
public sealed record RuntimeEvidence(
|
||||
IReadOnlyList<RuntimeCaptureSession> Sessions,
|
||||
IReadOnlyList<RuntimeLibrarySummary> UniqueLibraries,
|
||||
IReadOnlyList<RuntimeDependencyEdge> RuntimeEdges);
|
||||
```
|
||||
|
||||
**New Adapter Needed**:
|
||||
```csharp
|
||||
// TetragonAdapter - implements IRuntimeCaptureAdapter
|
||||
public sealed class TetragonAdapter : IRuntimeCaptureAdapter
|
||||
{
|
||||
private readonly TetragonClient _client;
|
||||
|
||||
public string Platform => "linux";
|
||||
public string Method => "ebpf";
|
||||
|
||||
public async IAsyncEnumerable<RuntimeLoadEvent> StreamEventsAsync(
|
||||
string sessionId,
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
await foreach (var evt in _client.StreamAsync(sessionId, ct))
|
||||
{
|
||||
yield return new RuntimeLoadEvent(
|
||||
Timestamp: evt.Time.ToDateTime(),
|
||||
ProcessId: (int)evt.Process.Pid,
|
||||
ThreadId: 0, // Tetragon doesn't track thread
|
||||
LoadType: MapLoadType(evt),
|
||||
RequestedPath: evt.Args?.FirstOrDefault()?.StringArg ?? "",
|
||||
ResolvedPath: null,
|
||||
LoadAddress: null,
|
||||
Success: true,
|
||||
ErrorCode: null,
|
||||
CallerModule: evt.Process.Binary,
|
||||
CallerAddress: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verdict Evaluation
|
||||
|
||||
**Already Implemented** - Use as-is.
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `ReachabilityStackEvaluator` | `Scanner.Reachability/Stack/ReachabilityStackEvaluator.cs` | 3-layer verdict |
|
||||
| `ReachabilityVerdict` | Same file | Verdict enum |
|
||||
| `ReachabilityStack` | `Scanner.Reachability/Stack/ReachabilityStack.cs` | Stack model |
|
||||
|
||||
**Verdict Logic** (already implemented):
|
||||
```csharp
|
||||
public ReachabilityVerdict DeriveVerdict(
|
||||
ReachabilityLayer1 layer1,
|
||||
ReachabilityLayer2 layer2,
|
||||
ReachabilityLayer3 layer3)
|
||||
{
|
||||
// L1 not reachable → Unreachable
|
||||
// L2 not resolved → Unreachable
|
||||
// L3 gated → Unreachable
|
||||
// All clear → Exploitable
|
||||
// Mixed → LikelyExploitable or PossiblyExploitable
|
||||
}
|
||||
```
|
||||
|
||||
### Evidence Storage
|
||||
|
||||
**Already Implemented** - Use as-is.
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `EvidenceBundle` | `Evidence.Bundle/EvidenceBundle.cs` | Container |
|
||||
| `ReachabilityEvidence` | `Evidence.Bundle/ReachabilityEvidence.cs` | Reachability proof |
|
||||
| `EvidenceDbContext` | `Evidence.Persistence/EfCore/Context/` | Postgres |
|
||||
| `IEvidenceStore` | `Evidence.Core/IEvidenceStore.cs` | Store interface |
|
||||
|
||||
### DSSE Attestation
|
||||
|
||||
**Already Implemented** - Wire to Authority for real signing.
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `ReachabilityWitnessDsseBuilder` | `Scanner.Reachability/Attestation/` | Build in-toto statements |
|
||||
| `ReachabilityWitnessStatement` | Same dir | Statement model |
|
||||
| `IDsseEnvelopeSigner` | `Scanner.Worker/Processing/Surface/` | Signing interface |
|
||||
| `DeterministicDsseEnvelopeSigner` | Same file | Fallback signer |
|
||||
|
||||
**Integration**:
|
||||
```csharp
|
||||
var statement = _dsseBuilder.BuildStatement(
|
||||
graph: richGraph,
|
||||
graphHash: graphHash,
|
||||
subjectDigest: imageDigest,
|
||||
graphCasUri: casUri,
|
||||
policyHash: null,
|
||||
sourceCommit: commit);
|
||||
|
||||
var statementBytes = _dsseBuilder.SerializeStatement(statement);
|
||||
var envelope = await _signer.SignAsync(
|
||||
payloadType: "https://stella.ops/reachabilityWitness/v1",
|
||||
content: statementBytes,
|
||||
suggestedKind: "reachability",
|
||||
merkleRoot: graphHash,
|
||||
view: null,
|
||||
ct);
|
||||
```
|
||||
|
||||
### VEX Integration
|
||||
|
||||
**Partially Implemented** - Add verdict bridge.
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `VexHubDbContext` | `VexHub/` | VEX storage |
|
||||
| `VexLens` | `VexLens/` | VEX analysis |
|
||||
| `TriageEffectiveVex` | `Scanner.WebService/` | Effective VEX |
|
||||
|
||||
**New Bridge Needed**:
|
||||
```csharp
|
||||
public sealed class VexStatusDeterminer : IVexStatusDeterminer
|
||||
{
|
||||
public VexStatus DetermineStatus(ReachabilityVerdict verdict) => verdict switch
|
||||
{
|
||||
ReachabilityVerdict.Exploitable => VexStatus.Affected,
|
||||
ReachabilityVerdict.LikelyExploitable => VexStatus.Affected,
|
||||
ReachabilityVerdict.PossiblyExploitable => VexStatus.UnderInvestigation,
|
||||
ReachabilityVerdict.Unreachable => VexStatus.NotAffected,
|
||||
ReachabilityVerdict.Unknown => VexStatus.UnderInvestigation,
|
||||
_ => VexStatus.UnderInvestigation
|
||||
};
|
||||
|
||||
public VexJustification BuildJustification(
|
||||
ReachabilityStack stack,
|
||||
IReadOnlyList<string> evidenceUris)
|
||||
{
|
||||
var detail = stack.Verdict switch
|
||||
{
|
||||
ReachabilityVerdict.Unreachable =>
|
||||
"Vulnerable code is not reachable from application entrypoints.",
|
||||
ReachabilityVerdict.Exploitable =>
|
||||
$"Vulnerable code is reachable via {stack.StaticCallGraph.Paths.Length} call path(s).",
|
||||
_ => "Reachability analysis completed."
|
||||
};
|
||||
|
||||
return new VexJustification
|
||||
{
|
||||
Category = stack.Verdict == ReachabilityVerdict.Unreachable
|
||||
? JustificationCategory.CodeNotReachable
|
||||
: JustificationCategory.RequiresDependentCode,
|
||||
Detail = detail,
|
||||
EvidenceReferences = evidenceUris
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Sequence
|
||||
|
||||
### 1. Scan Trigger → Reachability Analysis
|
||||
|
||||
```
|
||||
1. Scanner.WebService receives POST /api/reachability/analyze
|
||||
└─ { imageDigest, cveId, purl }
|
||||
|
||||
2. ReachabilityEvidenceJob queued to Scanner.Queue
|
||||
└─ Job contains: imageDigest, cveId, purl, options
|
||||
|
||||
3. Scanner.Worker picks up job
|
||||
├─ ICveSymbolMappingService.GetSinksForCveAsync(cveId, purl)
|
||||
│ └─ Returns: [VulnerableSymbol{name: "JndiLookup.lookup", ...}]
|
||||
│
|
||||
├─ ICallGraphCache.GetOrComputeAsync(imageDigest)
|
||||
│ └─ Returns: CallGraphSnapshot (nodes, edges, entrypoints, sinks)
|
||||
│
|
||||
├─ ReachabilityAnalyzer.Analyze(snapshot, options{ExplicitSinks})
|
||||
│ └─ Returns: ReachabilityAnalysisResult (paths, reachableSinks)
|
||||
│
|
||||
├─ BuildLayer1 from analysis result
|
||||
│ └─ Layer1{IsReachable, Confidence, Paths, ReachingEntrypoints}
|
||||
│
|
||||
├─ ReachabilityStackEvaluator.Evaluate(findingId, symbol, L1, L2, L3)
|
||||
│ └─ Returns: ReachabilityStack with Verdict
|
||||
│
|
||||
└─ IEvidenceStore.StoreAsync(stack.ToEvidenceBundle())
|
||||
└─ Returns: EvidenceBundleId
|
||||
|
||||
4. Optional: Emit VEX
|
||||
├─ IVexStatusDeterminer.DetermineStatus(verdict)
|
||||
├─ IVexStatusDeterminer.BuildJustification(stack, [evidenceUri])
|
||||
└─ VexHub.EmitAsync(vexDocument)
|
||||
```
|
||||
|
||||
### 2. Runtime Observation → Layer 3 Update
|
||||
|
||||
```
|
||||
1. RuntimeEvidenceCollector monitors container
|
||||
├─ TetragonAdapter.StartSessionAsync(options)
|
||||
└─ TetragonAdapter.StreamEventsAsync(sessionId)
|
||||
└─ Yields: RuntimeLoadEvent[]
|
||||
|
||||
2. RuntimeEvidenceCorrelator processes events
|
||||
├─ Match loaded libraries to vulnerable symbols
|
||||
├─ Check if sink functions were called
|
||||
└─ Build RuntimeObservation
|
||||
|
||||
3. Update ReachabilityStack Layer 3
|
||||
├─ Layer3{IsGated: false/true, Outcome, Conditions}
|
||||
└─ Re-evaluate verdict with ReachabilityStackEvaluator
|
||||
|
||||
4. Store updated evidence
|
||||
└─ IEvidenceStore.UpdateAsync(bundleId, updatedStack)
|
||||
```
|
||||
|
||||
### 3. Patch Verification → Layer 2 Update
|
||||
|
||||
```
|
||||
1. PatchVerificationJob triggered for "distro claims fixed"
|
||||
├─ Download vulnerable binary from upstream
|
||||
├─ Download patched binary from distro
|
||||
└─ Get target symbols from CVE mapping
|
||||
|
||||
2. IBinaryDiffService.DiffAsync(vulnerable, patched, symbols)
|
||||
└─ Returns: PatchDiffResult{IsPatched, ChangedFunctions, SimilarityScore}
|
||||
|
||||
3. Update ReachabilityStack Layer 2
|
||||
├─ Layer2{IsResolved: !IsPatched, Resolution, Reason}
|
||||
└─ Re-evaluate verdict
|
||||
|
||||
4. Store patch diff evidence
|
||||
└─ EvidenceBundle += PatchDiffEvidence
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema Integration
|
||||
|
||||
### Existing Tables Used
|
||||
|
||||
```sql
|
||||
-- CVE-Symbol Mapping (exists in reachability schema)
|
||||
reachability.cve_symbol_mappings
|
||||
reachability.vulnerable_symbols
|
||||
reachability.patch_analysis
|
||||
|
||||
-- Evidence Storage (exists in evidence schema)
|
||||
evidence.evidence_bundles
|
||||
evidence.evidence_records
|
||||
evidence.attestations
|
||||
|
||||
-- VEX (exists in vex schema)
|
||||
vex.documents
|
||||
vex.statements
|
||||
vex.analysis
|
||||
```
|
||||
|
||||
### New Tables Needed
|
||||
|
||||
```sql
|
||||
-- Runtime observations
|
||||
CREATE TABLE reachability.runtime_observations (
|
||||
observation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
scan_id UUID NOT NULL,
|
||||
image_digest TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
symbol_name TEXT,
|
||||
observed_at TIMESTAMPTZ NOT NULL,
|
||||
load_type TEXT,
|
||||
process_id INTEGER,
|
||||
correlated_cve_id TEXT,
|
||||
correlated_finding_id UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- VEX-Evidence linking
|
||||
CREATE TABLE reachability.vex_evidence_links (
|
||||
link_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
vex_document_id UUID NOT NULL,
|
||||
evidence_bundle_id UUID NOT NULL,
|
||||
evidence_type TEXT NOT NULL,
|
||||
linked_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Scanner.Worker appsettings.json
|
||||
|
||||
```json
|
||||
{
|
||||
"Reachability": {
|
||||
"EnableL2Analysis": false,
|
||||
"EnableL3Analysis": false,
|
||||
"MaxPathsPerSink": 5,
|
||||
"MaxDepth": 256,
|
||||
"CacheGraphs": true,
|
||||
"GraphCacheTtlMinutes": 60
|
||||
},
|
||||
"RuntimeCapture": {
|
||||
"Adapter": "tetragon",
|
||||
"SessionDurationSeconds": 300,
|
||||
"EventBufferSize": 10000
|
||||
},
|
||||
"BinaryDiff": {
|
||||
"MaxBinarySizeMb": 100,
|
||||
"TimeoutSeconds": 120
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tetragon TracingPolicy
|
||||
|
||||
```yaml
|
||||
apiVersion: cilium.io/v1alpha1
|
||||
kind: TracingPolicy
|
||||
metadata:
|
||||
name: stella-library-loads
|
||||
spec:
|
||||
kprobes:
|
||||
- call: "do_dlopen"
|
||||
syscall: false
|
||||
args:
|
||||
- index: 0
|
||||
type: "string"
|
||||
- call: "load_elf_binary"
|
||||
syscall: false
|
||||
args:
|
||||
- index: 0
|
||||
type: "file"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```csharp
|
||||
// Test Layer 1 analysis
|
||||
[Fact]
|
||||
public void ReachabilityAnalyzer_FindsPath_WhenSinkReachable()
|
||||
{
|
||||
var snapshot = CreateGraphWithPath("entry", "sink");
|
||||
var result = _analyzer.Analyze(snapshot, new() { ExplicitSinks = ["sink"] });
|
||||
|
||||
Assert.Single(result.Paths);
|
||||
Assert.Equal("entry", result.Paths[0].EntrypointId);
|
||||
Assert.Equal("sink", result.Paths[0].SinkId);
|
||||
}
|
||||
|
||||
// Test verdict evaluation
|
||||
[Theory]
|
||||
[InlineData(true, true, false, ReachabilityVerdict.Exploitable)]
|
||||
[InlineData(true, true, true, ReachabilityVerdict.Unreachable)]
|
||||
[InlineData(false, true, false, ReachabilityVerdict.Unreachable)]
|
||||
public void StackEvaluator_DeterminesVerdict_Correctly(
|
||||
bool l1Reachable, bool l2Resolved, bool l3Gated, ReachabilityVerdict expected)
|
||||
{
|
||||
var layer1 = new ReachabilityLayer1 { IsReachable = l1Reachable, ... };
|
||||
var layer2 = new ReachabilityLayer2 { IsResolved = l2Resolved, ... };
|
||||
var layer3 = new ReachabilityLayer3 { IsGated = l3Gated, ... };
|
||||
|
||||
var verdict = _evaluator.DeriveVerdict(layer1, layer2, layer3);
|
||||
|
||||
Assert.Equal(expected, verdict);
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```csharp
|
||||
// Test end-to-end reachability job
|
||||
[Fact]
|
||||
public async Task ReachabilityJob_ProducesEvidence_ForKnownCve()
|
||||
{
|
||||
var job = new ReachabilityEvidenceJob(
|
||||
JobId: Guid.NewGuid().ToString("N"),
|
||||
ImageDigest: "sha256:test123",
|
||||
CveId: "CVE-2021-44228",
|
||||
Purl: "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
|
||||
SourceCommit: null,
|
||||
Options: new(),
|
||||
QueuedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var stack = await _executor.ExecuteAsync(job, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(stack);
|
||||
Assert.Equal("CVE-2021-44228:pkg:maven/...", stack.FindingId);
|
||||
Assert.NotNull(stack.StaticCallGraph);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
### Metrics
|
||||
|
||||
```csharp
|
||||
// Meter: stella.reachability
|
||||
Counter<long> reachability_jobs_total { status = "completed|failed" }
|
||||
Histogram<double> reachability_analysis_duration_seconds
|
||||
Counter<long> reachability_verdicts_total { verdict = "exploitable|unreachable|..." }
|
||||
Gauge<long> runtime_capture_sessions_active
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
```
|
||||
[INFO] ReachabilityJob started: jobId={jobId} cveId={cveId} image={digest}
|
||||
[INFO] CVE sinks resolved: cveId={cveId} sinkCount={count}
|
||||
[INFO] Call graph loaded: nodes={nodes} edges={edges} entrypoints={eps}
|
||||
[INFO] Reachability analysis complete: reachable={bool} pathCount={count}
|
||||
[INFO] Verdict determined: verdict={verdict} findingId={findingId}
|
||||
[INFO] Evidence stored: bundleId={bundleId}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Path
|
||||
|
||||
### Phase 1: Enable L1 Only
|
||||
- Deploy with `EnableL2Analysis: false`, `EnableL3Analysis: false`
|
||||
- Validate with known CVEs (Log4Shell, Spring4Shell)
|
||||
- Monitor verdict accuracy
|
||||
|
||||
### Phase 2: Add L2 (Binary Resolution)
|
||||
- Enable patch verification for distro packages
|
||||
- Deploy B2R2BinaryDiffService
|
||||
- Set `EnableL2Analysis: true`
|
||||
|
||||
### Phase 3: Add L3 (Runtime)
|
||||
- Deploy Tetragon in cluster
|
||||
- Enable RuntimeEvidenceCollector
|
||||
- Set `EnableL3Analysis: true`
|
||||
|
||||
### Phase 4: Full VEX Automation
|
||||
- Enable VexStatusDeterminer auto-population
|
||||
- Configure VEX refresh triggers
|
||||
- Monitor VEX accuracy vs manual triage
|
||||
Reference in New Issue
Block a user