docs consolidation, big sln build fixes, new advisories and sprints/tasks
This commit is contained in:
700
docs/modules/facet/architecture.md
Normal file
700
docs/modules/facet/architecture.md
Normal file
@@ -0,0 +1,700 @@
|
||||
# Facet Sealing Architecture
|
||||
|
||||
> **Ownership:** Scanner Guild, Policy Guild
|
||||
> **Audience:** Service owners, platform engineers, security architects
|
||||
> **Related:** [Platform Architecture](../platform/architecture-overview.md), [Scanner Architecture](../scanner/architecture.md), [Replay Architecture](../replay/architecture.md), [Policy Engine](../policy/architecture.md)
|
||||
|
||||
This dossier describes the Facet Sealing subsystem, which provides cryptographically sealed manifests for logical slices of container images, enabling fine-grained drift detection, per-facet quota enforcement, and deterministic change tracking.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
A **Facet** is a declared logical slice of a container image representing a cohesive set of files with shared characteristics:
|
||||
|
||||
| Facet Type | Description | Examples |
|
||||
|------------|-------------|----------|
|
||||
| `os` | Operating system packages | `/var/lib/dpkg/**`, `/var/lib/rpm/**` |
|
||||
| `lang/<ecosystem>` | Language-specific dependencies | `node_modules/**`, `site-packages/**`, `vendor/**` |
|
||||
| `binary` | Native binaries and shared libraries | `/usr/bin/*`, `/lib/**/*.so*` |
|
||||
| `config` | Configuration files | `/etc/**`, `*.conf`, `*.yaml` |
|
||||
| `custom` | User-defined patterns | Project-specific paths |
|
||||
|
||||
Each facet can be individually **sealed** (cryptographic snapshot) and monitored for **drift** (changes between seals).
|
||||
|
||||
---
|
||||
|
||||
## 2. System Landscape
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Scanner["Scanner Services"]
|
||||
FE[FacetExtractor]
|
||||
FH[FacetHasher]
|
||||
MB[MerkleBuilder]
|
||||
end
|
||||
|
||||
subgraph Storage["Facet Storage"]
|
||||
FS[(PostgreSQL<br/>facet_seals)]
|
||||
FC[(CAS<br/>facet_manifests)]
|
||||
end
|
||||
|
||||
subgraph Policy["Policy & Enforcement"]
|
||||
DC[DriftCalculator]
|
||||
QE[QuotaEnforcer]
|
||||
AV[AdmissionValidator]
|
||||
end
|
||||
|
||||
subgraph Signing["Attestation"]
|
||||
DS[DSSE Signer]
|
||||
AT[Attestor]
|
||||
end
|
||||
|
||||
subgraph CLI["CLI & Integration"]
|
||||
SealCmd[stella seal]
|
||||
DriftCmd[stella drift]
|
||||
VexCmd[stella vex gen]
|
||||
Zastava[Zastava Webhook]
|
||||
end
|
||||
|
||||
FE --> FH
|
||||
FH --> MB
|
||||
MB --> DS
|
||||
DS --> FS
|
||||
DS --> FC
|
||||
FS --> DC
|
||||
DC --> QE
|
||||
QE --> AV
|
||||
AV --> Zastava
|
||||
SealCmd --> FE
|
||||
DriftCmd --> DC
|
||||
VexCmd --> DC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Core Data Models
|
||||
|
||||
### 3.1 FacetDefinition
|
||||
|
||||
Declares a facet with its extraction patterns and quota constraints:
|
||||
|
||||
```csharp
|
||||
public sealed record FacetDefinition
|
||||
{
|
||||
public required string FacetId { get; init; } // e.g., "os", "lang/node", "binary"
|
||||
public required FacetType Type { get; init; } // OS, LangNode, LangPython, Binary, Config, Custom
|
||||
public required ImmutableArray<string> IncludeGlobs { get; init; }
|
||||
public ImmutableArray<string> ExcludeGlobs { get; init; } = [];
|
||||
public FacetQuota? Quota { get; init; }
|
||||
}
|
||||
|
||||
public enum FacetType
|
||||
{
|
||||
OS,
|
||||
LangNode,
|
||||
LangPython,
|
||||
LangGo,
|
||||
LangRust,
|
||||
LangJava,
|
||||
LangDotNet,
|
||||
Binary,
|
||||
Config,
|
||||
Custom
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 FacetManifest
|
||||
|
||||
Per-facet file manifest with Merkle root:
|
||||
|
||||
```csharp
|
||||
public sealed record FacetManifest
|
||||
{
|
||||
public required string FacetId { get; init; }
|
||||
public required FacetType Type { get; init; }
|
||||
public required ImmutableArray<FacetFileEntry> Files { get; init; }
|
||||
public required string MerkleRoot { get; init; } // SHA-256 hex
|
||||
public required int FileCount { get; init; }
|
||||
public required long TotalBytes { get; init; }
|
||||
public required DateTimeOffset ExtractedAt { get; init; }
|
||||
public required string ExtractorVersion { get; init; }
|
||||
}
|
||||
|
||||
public sealed record FacetFileEntry
|
||||
{
|
||||
public required string Path { get; init; } // Normalized POSIX path
|
||||
public required string ContentHash { get; init; } // SHA-256 hex
|
||||
public required long Size { get; init; }
|
||||
public required string Mode { get; init; } // POSIX mode string "0644"
|
||||
public required DateTimeOffset ModTime { get; init; } // Normalized to UTC
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 FacetSeal
|
||||
|
||||
DSSE-signed seal combining manifest with metadata:
|
||||
|
||||
```csharp
|
||||
public sealed record FacetSeal
|
||||
{
|
||||
public required Guid SealId { get; init; }
|
||||
public required string ImageRef { get; init; } // registry/repo:tag@sha256:...
|
||||
public required string ImageDigest { get; init; } // sha256:...
|
||||
public required FacetManifest Manifest { get; init; }
|
||||
public required DateTimeOffset SealedAt { get; init; }
|
||||
public required string SealedBy { get; init; } // Identity/service
|
||||
public required FacetQuota? AppliedQuota { get; init; }
|
||||
public required DsseEnvelope Envelope { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 FacetQuota
|
||||
|
||||
Per-facet change budget:
|
||||
|
||||
```csharp
|
||||
public sealed record FacetQuota
|
||||
{
|
||||
public required string FacetId { get; init; }
|
||||
public double MaxChurnPercent { get; init; } = 5.0; // 0-100
|
||||
public int MaxChangedFiles { get; init; } = 50;
|
||||
public int MaxAddedFiles { get; init; } = 25;
|
||||
public int MaxRemovedFiles { get; init; } = 10;
|
||||
public QuotaAction OnExceed { get; init; } = QuotaAction.Warn;
|
||||
}
|
||||
|
||||
public enum QuotaAction
|
||||
{
|
||||
Warn, // Log warning, allow admission
|
||||
Block, // Reject admission
|
||||
RequireVex // Require VEX justification before admission
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 FacetDrift
|
||||
|
||||
Drift calculation result between two seals:
|
||||
|
||||
```csharp
|
||||
public sealed record FacetDrift
|
||||
{
|
||||
public required string FacetId { get; init; }
|
||||
public required Guid BaselineSealId { get; init; }
|
||||
public required Guid CurrentSealId { get; init; }
|
||||
public required ImmutableArray<DriftEntry> Added { get; init; }
|
||||
public required ImmutableArray<DriftEntry> Removed { get; init; }
|
||||
public required ImmutableArray<DriftEntry> Modified { get; init; }
|
||||
public required DriftScore Score { get; init; }
|
||||
public required QuotaVerdict QuotaVerdict { get; init; }
|
||||
}
|
||||
|
||||
public sealed record DriftEntry
|
||||
{
|
||||
public required string Path { get; init; }
|
||||
public string? OldHash { get; init; }
|
||||
public string? NewHash { get; init; }
|
||||
public long? OldSize { get; init; }
|
||||
public long? NewSize { get; init; }
|
||||
public DriftCause Cause { get; init; } = DriftCause.Unknown;
|
||||
}
|
||||
|
||||
public enum DriftCause
|
||||
{
|
||||
Unknown,
|
||||
PackageUpdate,
|
||||
ConfigChange,
|
||||
BinaryRebuild,
|
||||
NewDependency,
|
||||
RemovedDependency,
|
||||
SecurityPatch
|
||||
}
|
||||
|
||||
public sealed record DriftScore
|
||||
{
|
||||
public required int TotalChanges { get; init; }
|
||||
public required double ChurnPercent { get; init; }
|
||||
public required int AddedCount { get; init; }
|
||||
public required int RemovedCount { get; init; }
|
||||
public required int ModifiedCount { get; init; }
|
||||
}
|
||||
|
||||
public sealed record QuotaVerdict
|
||||
{
|
||||
public required bool Passed { get; init; }
|
||||
public required ImmutableArray<QuotaViolation> Violations { get; init; }
|
||||
public required QuotaAction RecommendedAction { get; init; }
|
||||
}
|
||||
|
||||
public sealed record QuotaViolation
|
||||
{
|
||||
public required string QuotaField { get; init; } // e.g., "MaxChurnPercent"
|
||||
public required double Limit { get; init; }
|
||||
public required double Actual { get; init; }
|
||||
public required string Message { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Component Architecture
|
||||
|
||||
### 4.1 FacetExtractor
|
||||
|
||||
Extracts file entries from container images based on facet definitions:
|
||||
|
||||
```csharp
|
||||
public interface IFacetExtractor
|
||||
{
|
||||
Task<FacetManifest> ExtractAsync(
|
||||
string imageRef,
|
||||
FacetDefinition definition,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<ImmutableArray<FacetManifest>> ExtractAllAsync(
|
||||
string imageRef,
|
||||
ImmutableArray<FacetDefinition> definitions,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
Implementation notes:
|
||||
- Uses existing `ISurfaceReader` for container layer traversal
|
||||
- Normalizes paths to POSIX format (forward slashes, no trailing slashes)
|
||||
- Computes SHA-256 content hashes for each file
|
||||
- Normalizes timestamps to UTC, mode to POSIX string
|
||||
- Sorts files lexicographically for deterministic ordering
|
||||
|
||||
### 4.2 FacetHasher
|
||||
|
||||
Computes Merkle tree for facet file entries:
|
||||
|
||||
```csharp
|
||||
public interface IFacetHasher
|
||||
{
|
||||
FacetMerkleResult ComputeMerkle(ImmutableArray<FacetFileEntry> files);
|
||||
}
|
||||
|
||||
public sealed record FacetMerkleResult
|
||||
{
|
||||
public required string Root { get; init; }
|
||||
public required ImmutableArray<string> LeafHashes { get; init; }
|
||||
public required ImmutableArray<MerkleProofNode> Proof { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
Implementation notes:
|
||||
- Leaf hash = SHA-256(path || contentHash || size || mode)
|
||||
- Binary Merkle tree with lexicographic leaf ordering
|
||||
- Empty facet produces well-known empty root hash
|
||||
- Proof enables verification of individual file membership
|
||||
|
||||
### 4.3 FacetSealStore
|
||||
|
||||
PostgreSQL storage for sealed facet manifests:
|
||||
|
||||
```sql
|
||||
-- Core seal storage
|
||||
CREATE TABLE facet_seals (
|
||||
seal_id UUID PRIMARY KEY,
|
||||
tenant TEXT NOT NULL,
|
||||
image_ref TEXT NOT NULL,
|
||||
image_digest TEXT NOT NULL,
|
||||
facet_id TEXT NOT NULL,
|
||||
facet_type TEXT NOT NULL,
|
||||
merkle_root TEXT NOT NULL,
|
||||
file_count INTEGER NOT NULL,
|
||||
total_bytes BIGINT NOT NULL,
|
||||
sealed_at TIMESTAMPTZ NOT NULL,
|
||||
sealed_by TEXT NOT NULL,
|
||||
quota_json JSONB,
|
||||
manifest_cas TEXT NOT NULL, -- CAS URI to full manifest
|
||||
dsse_envelope JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT uq_facet_seal UNIQUE (tenant, image_digest, facet_id)
|
||||
);
|
||||
|
||||
CREATE INDEX ix_facet_seals_image ON facet_seals (tenant, image_digest);
|
||||
CREATE INDEX ix_facet_seals_merkle ON facet_seals (merkle_root);
|
||||
|
||||
-- Drift history
|
||||
CREATE TABLE facet_drift_history (
|
||||
drift_id UUID PRIMARY KEY,
|
||||
tenant TEXT NOT NULL,
|
||||
baseline_seal_id UUID NOT NULL REFERENCES facet_seals(seal_id),
|
||||
current_seal_id UUID NOT NULL REFERENCES facet_seals(seal_id),
|
||||
facet_id TEXT NOT NULL,
|
||||
drift_score_json JSONB NOT NULL,
|
||||
quota_verdict_json JSONB NOT NULL,
|
||||
computed_at TIMESTAMPTZ NOT NULL,
|
||||
|
||||
CONSTRAINT uq_drift_pair UNIQUE (baseline_seal_id, current_seal_id)
|
||||
);
|
||||
```
|
||||
|
||||
### 4.4 DriftCalculator
|
||||
|
||||
Computes drift between baseline and current seals:
|
||||
|
||||
```csharp
|
||||
public interface IDriftCalculator
|
||||
{
|
||||
Task<FacetDrift> CalculateAsync(
|
||||
Guid baselineSealId,
|
||||
Guid currentSealId,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<ImmutableArray<FacetDrift>> CalculateAllAsync(
|
||||
string imageDigestBaseline,
|
||||
string imageDigestCurrent,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
Implementation notes:
|
||||
- Retrieves manifests from CAS via seal metadata
|
||||
- Performs set difference operations on file paths
|
||||
- Detects modifications via content hash comparison
|
||||
- Attributes drift causes where determinable (e.g., package manager metadata)
|
||||
|
||||
### 4.5 QuotaEnforcer
|
||||
|
||||
Evaluates drift against quota constraints:
|
||||
|
||||
```csharp
|
||||
public interface IQuotaEnforcer
|
||||
{
|
||||
QuotaVerdict Evaluate(FacetDrift drift, FacetQuota quota);
|
||||
|
||||
Task<ImmutableArray<QuotaVerdict>> EvaluateAllAsync(
|
||||
ImmutableArray<FacetDrift> drifts,
|
||||
ImmutableDictionary<string, FacetQuota> quotas,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.6 AdmissionValidator
|
||||
|
||||
Zastava webhook integration for admission control:
|
||||
|
||||
```csharp
|
||||
public interface IFacetAdmissionValidator
|
||||
{
|
||||
Task<AdmissionResult> ValidateAsync(
|
||||
AdmissionRequest request,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public sealed record AdmissionResult
|
||||
{
|
||||
public required bool Allowed { get; init; }
|
||||
public string? Message { get; init; }
|
||||
public ImmutableArray<QuotaViolation> Violations { get; init; } = [];
|
||||
public string? RequiredVexStatement { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. DSSE Envelope Structure
|
||||
|
||||
Facet seals use DSSE (Dead Simple Signing Envelope) for cryptographic binding:
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.stellaops.facet-seal.v1+json",
|
||||
"payload": "<base64url-encoded canonical JSON of FacetSeal>",
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "sha256:abc123...",
|
||||
"sig": "<base64url-encoded signature>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Payload structure (canonical JSON, RFC 8785):
|
||||
```json
|
||||
{
|
||||
"_type": "https://stellaops.io/FacetSeal/v1",
|
||||
"facetId": "os",
|
||||
"facetType": "OS",
|
||||
"imageDigest": "sha256:abc123...",
|
||||
"imageRef": "registry.example.com/app:v1.2.3",
|
||||
"manifest": {
|
||||
"extractedAt": "2026-01-05T10:00:00.000Z",
|
||||
"extractorVersion": "1.0.0",
|
||||
"fileCount": 1234,
|
||||
"files": [
|
||||
{
|
||||
"contentHash": "sha256:...",
|
||||
"mode": "0644",
|
||||
"modTime": "2026-01-01T00:00:00.000Z",
|
||||
"path": "/etc/os-release",
|
||||
"size": 256
|
||||
}
|
||||
],
|
||||
"merkleRoot": "sha256:def456...",
|
||||
"totalBytes": 1048576
|
||||
},
|
||||
"quota": {
|
||||
"maxAddedFiles": 25,
|
||||
"maxChangedFiles": 50,
|
||||
"maxChurnPercent": 5.0,
|
||||
"maxRemovedFiles": 10,
|
||||
"onExceed": "Warn"
|
||||
},
|
||||
"sealId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"sealedAt": "2026-01-05T10:05:00.000Z",
|
||||
"sealedBy": "scanner-worker-01"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Default Facet Definitions
|
||||
|
||||
Standard facet definitions applied when no custom configuration is provided:
|
||||
|
||||
```yaml
|
||||
# Default facet configuration
|
||||
facets:
|
||||
- facetId: os
|
||||
type: OS
|
||||
includeGlobs:
|
||||
- /var/lib/dpkg/**
|
||||
- /var/lib/rpm/**
|
||||
- /var/lib/pacman/**
|
||||
- /var/lib/apk/**
|
||||
- /var/cache/apt/**
|
||||
- /etc/apt/**
|
||||
- /etc/yum.repos.d/**
|
||||
excludeGlobs:
|
||||
- "**/*.log"
|
||||
quota:
|
||||
maxChurnPercent: 5.0
|
||||
maxChangedFiles: 100
|
||||
onExceed: Warn
|
||||
|
||||
- facetId: lang/node
|
||||
type: LangNode
|
||||
includeGlobs:
|
||||
- "**/node_modules/**"
|
||||
- "**/package.json"
|
||||
- "**/package-lock.json"
|
||||
- "**/yarn.lock"
|
||||
- "**/pnpm-lock.yaml"
|
||||
quota:
|
||||
maxChurnPercent: 10.0
|
||||
maxChangedFiles: 500
|
||||
onExceed: RequireVex
|
||||
|
||||
- facetId: lang/python
|
||||
type: LangPython
|
||||
includeGlobs:
|
||||
- "**/site-packages/**"
|
||||
- "**/dist-packages/**"
|
||||
- "**/requirements.txt"
|
||||
- "**/Pipfile.lock"
|
||||
- "**/poetry.lock"
|
||||
quota:
|
||||
maxChurnPercent: 10.0
|
||||
maxChangedFiles: 200
|
||||
onExceed: Warn
|
||||
|
||||
- facetId: lang/go
|
||||
type: LangGo
|
||||
includeGlobs:
|
||||
- "**/go.mod"
|
||||
- "**/go.sum"
|
||||
- "**/vendor/**"
|
||||
quota:
|
||||
maxChurnPercent: 15.0
|
||||
maxChangedFiles: 100
|
||||
onExceed: Warn
|
||||
|
||||
- facetId: binary
|
||||
type: Binary
|
||||
includeGlobs:
|
||||
- /usr/bin/*
|
||||
- /usr/sbin/*
|
||||
- /bin/*
|
||||
- /sbin/*
|
||||
- /usr/lib/**/*.so*
|
||||
- /lib/**/*.so*
|
||||
- /usr/local/bin/*
|
||||
excludeGlobs:
|
||||
- "**/*.py"
|
||||
- "**/*.sh"
|
||||
quota:
|
||||
maxChurnPercent: 2.0
|
||||
maxChangedFiles: 20
|
||||
onExceed: Block
|
||||
|
||||
- facetId: config
|
||||
type: Config
|
||||
includeGlobs:
|
||||
- /etc/**
|
||||
- "**/*.conf"
|
||||
- "**/*.cfg"
|
||||
- "**/*.ini"
|
||||
- "**/*.yaml"
|
||||
- "**/*.yml"
|
||||
- "**/*.json"
|
||||
excludeGlobs:
|
||||
- /etc/passwd
|
||||
- /etc/shadow
|
||||
- /etc/group
|
||||
- "**/*.log"
|
||||
quota:
|
||||
maxChurnPercent: 20.0
|
||||
maxChangedFiles: 50
|
||||
onExceed: Warn
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Integration Points
|
||||
|
||||
### 7.1 Scanner Integration
|
||||
|
||||
Scanner invokes facet extraction during scan:
|
||||
|
||||
```csharp
|
||||
// In ScanOrchestrator
|
||||
var facetDefs = await _facetConfigLoader.LoadAsync(scanRequest.FacetConfig, ct);
|
||||
var manifests = await _facetExtractor.ExtractAllAsync(imageRef, facetDefs, ct);
|
||||
|
||||
foreach (var manifest in manifests)
|
||||
{
|
||||
var seal = await _facetSealer.SealAsync(manifest, scanRequest, ct);
|
||||
await _facetSealStore.SaveAsync(seal, ct);
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 CLI Integration
|
||||
|
||||
```bash
|
||||
# Seal all facets for an image
|
||||
stella seal myregistry.io/app:v1.2.3 --output seals.json
|
||||
|
||||
# Seal specific facets
|
||||
stella seal myregistry.io/app:v1.2.3 --facet os --facet lang/node
|
||||
|
||||
# Check drift between two image versions
|
||||
stella drift myregistry.io/app:v1.2.3 myregistry.io/app:v1.2.4 --format json
|
||||
|
||||
# Generate VEX from drift
|
||||
stella vex gen --from-drift myregistry.io/app:v1.2.3 myregistry.io/app:v1.2.4
|
||||
```
|
||||
|
||||
### 7.3 Zastava Webhook Integration
|
||||
|
||||
```csharp
|
||||
// In FacetAdmissionValidator
|
||||
public async Task<AdmissionResult> ValidateAsync(AdmissionRequest request, CancellationToken ct)
|
||||
{
|
||||
// Find baseline seal (latest approved)
|
||||
var baseline = await _sealStore.GetLatestApprovedAsync(request.ImageRef, ct);
|
||||
if (baseline is null)
|
||||
return AdmissionResult.Allowed("No baseline seal found, skipping facet check");
|
||||
|
||||
// Extract current facets
|
||||
var currentManifests = await _extractor.ExtractAllAsync(request.ImageRef, _defaultFacets, ct);
|
||||
|
||||
// Calculate drift for each facet
|
||||
var drifts = new List<FacetDrift>();
|
||||
foreach (var manifest in currentManifests)
|
||||
{
|
||||
var baselineSeal = baseline.FirstOrDefault(s => s.FacetId == manifest.FacetId);
|
||||
if (baselineSeal is not null)
|
||||
{
|
||||
var drift = await _driftCalculator.CalculateAsync(baselineSeal, manifest, ct);
|
||||
drifts.Add(drift);
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate quotas
|
||||
var violations = new List<QuotaViolation>();
|
||||
QuotaAction maxAction = QuotaAction.Warn;
|
||||
|
||||
foreach (var drift in drifts)
|
||||
{
|
||||
var verdict = _quotaEnforcer.Evaluate(drift, drift.AppliedQuota);
|
||||
if (!verdict.Passed)
|
||||
{
|
||||
violations.AddRange(verdict.Violations);
|
||||
if (verdict.RecommendedAction > maxAction)
|
||||
maxAction = verdict.RecommendedAction;
|
||||
}
|
||||
}
|
||||
|
||||
return maxAction switch
|
||||
{
|
||||
QuotaAction.Block => AdmissionResult.Denied(violations),
|
||||
QuotaAction.RequireVex => AdmissionResult.RequiresVex(violations),
|
||||
_ => AdmissionResult.Allowed(violations)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Observability
|
||||
|
||||
### 8.1 Metrics
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| `facet_seal_total` | Counter | `tenant`, `facet_type`, `status` | Total seals created |
|
||||
| `facet_seal_duration_seconds` | Histogram | `facet_type` | Time to create seal |
|
||||
| `facet_drift_score` | Gauge | `tenant`, `facet_id`, `image` | Current drift score |
|
||||
| `facet_quota_violations_total` | Counter | `tenant`, `facet_id`, `quota_field` | Quota violations |
|
||||
| `facet_admission_decisions_total` | Counter | `tenant`, `decision`, `facet_id` | Admission decisions |
|
||||
|
||||
### 8.2 Traces
|
||||
|
||||
```
|
||||
facet.extract - Facet file extraction from image
|
||||
facet.hash - Merkle tree computation
|
||||
facet.seal - DSSE signing
|
||||
facet.drift.compute - Drift calculation
|
||||
facet.quota.evaluate - Quota enforcement
|
||||
facet.admission - Admission validation
|
||||
```
|
||||
|
||||
### 8.3 Logs
|
||||
|
||||
Structured log fields:
|
||||
- `facetId`: Facet identifier
|
||||
- `imageRef`: Container image reference
|
||||
- `imageDigest`: Image content digest
|
||||
- `merkleRoot`: Facet Merkle root
|
||||
- `driftScore`: Computed drift percentage
|
||||
- `quotaVerdict`: Pass/fail status
|
||||
|
||||
---
|
||||
|
||||
## 9. Security Considerations
|
||||
|
||||
1. **Signature Verification**: All seals must be DSSE-signed with keys managed by Authority service
|
||||
2. **Tenant Isolation**: Seals are scoped to tenants; cross-tenant access is prohibited
|
||||
3. **Immutability**: Once created, seals cannot be modified; only superseded by new seals
|
||||
4. **Audit Trail**: All seal operations are logged with correlation IDs
|
||||
5. **Key Rotation**: Signing keys support rotation; old signatures remain valid with archived keys
|
||||
|
||||
---
|
||||
|
||||
## 10. References
|
||||
|
||||
- [DSSE Specification](https://github.com/secure-systems-lab/dsse)
|
||||
- [RFC 8785 - JSON Canonicalization](https://tools.ietf.org/html/rfc8785)
|
||||
- [Scanner Architecture](../scanner/architecture.md)
|
||||
- [Attestor Architecture](../attestor/architecture.md)
|
||||
- [Policy Engine Architecture](../policy/architecture.md)
|
||||
- [Replay Architecture](../replay/architecture.md)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-01-05*
|
||||
Reference in New Issue
Block a user