701 lines
19 KiB
Markdown
701 lines
19 KiB
Markdown
# 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*
|