audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories

This commit is contained in:
master
2026-01-07 18:49:59 +02:00
parent 04ec098046
commit 608a7f85c0
866 changed files with 56323 additions and 6231 deletions

View File

@@ -0,0 +1,220 @@
# ADR 0042: CGS Merkle Tree Implementation
## Status
ACCEPTED (2025-12-29)
## Context
The CGS (Canonical Graph Signature) system requires deterministic hash computation for verdicts. We need to decide whether to:
1. Reuse existing `StellaOps.Attestor.ProofChain` Merkle tree builder
2. Build a custom Merkle tree implementation in `VerdictBuilderService`
### Requirements
- **Determinism**: Same evidence must always produce identical CGS hash
- **Order Independence**: VEX document ordering should not affect hash (sorted internally)
- **Cross-Platform**: Identical hash on Windows, macOS, Linux (glibc), Linux (musl), BSD
- **Leaf Composition**: Specific ordering of evidence components (SBOM, VEX sorted, reachability, policy lock)
### Existing ProofChain Merkle Builder
Located at: `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/`
**Pros:**
- Already implements Merkle tree construction
- Tested and proven in production
- Handles parent/child attestation chains
**Cons:**
- Designed for attestation chains, not evidence hashing
- Includes attestation-specific metadata in hash
- Doesn't support custom leaf ordering required for CGS
- Would require modifications that might break existing attestation behavior
## Decision
**Build custom Merkle tree implementation in `VerdictBuilderService`.**
### Rationale
1. **Separation of Concerns**: CGS hash computation has different requirements than attestation chain verification
2. **Full Control Over Determinism**: Custom implementation allows:
- Explicit leaf ordering: SBOM → VEX (sorted) → Reachability → PolicyLock
- VEX document sorting by content hash (not insertion order)
- Culture-invariant string comparison (`StringComparer.Ordinal`)
3. **Simplicity**: ~50 lines of code vs modifying 500+ lines in ProofChain
4. **No Breaking Changes**: Doesn't affect existing attestation infrastructure
### Implementation
```csharp
// VerdictBuilderService.cs
private static string ComputeCgsHash(EvidencePack evidence, PolicyLock policyLock)
{
// Build Merkle tree from evidence components (sorted for determinism)
var leaves = new List<string>
{
ComputeHash(evidence.SbomCanonJson),
ComputeHash(evidence.FeedSnapshotDigest)
};
// Add VEX digests in sorted order (ORDER-CRITICAL for determinism!)
foreach (var vex in evidence.VexCanonJson.OrderBy(v => v, StringComparer.Ordinal))
{
leaves.Add(ComputeHash(vex));
}
// Add reachability if present
if (!string.IsNullOrEmpty(evidence.ReachabilityGraphJson))
{
leaves.Add(ComputeHash(evidence.ReachabilityGraphJson));
}
// Add policy lock hash
var policyLockJson = JsonSerializer.Serialize(policyLock, CanonicalJsonOptions);
leaves.Add(ComputeHash(policyLockJson));
// Build Merkle root
var merkleRoot = BuildMerkleRoot(leaves);
return $"cgs:sha256:{merkleRoot}";
}
private static string BuildMerkleRoot(List<string> leaves)
{
if (leaves.Count == 0)
return ComputeHash("");
if (leaves.Count == 1)
return leaves[0];
var level = leaves.ToList();
while (level.Count > 1)
{
var nextLevel = new List<string>();
for (int i = 0; i < level.Count; i += 2)
{
if (i + 1 < level.Count)
{
// Combine two hashes
var combined = level[i] + level[i + 1];
nextLevel.Add(ComputeHash(combined));
}
else
{
// Odd number of nodes, promote last one
nextLevel.Add(level[i]);
}
}
level = nextLevel;
}
return level[0];
}
private static string ComputeHash(string input)
{
var bytes = Encoding.UTF8.GetBytes(input);
var hashBytes = SHA256.HashData(bytes);
return Convert.ToHexString(hashBytes).ToLowerInvariant();
}
```
## Consequences
### Positive
- ✅ Full control over CGS hash computation logic
- ✅ No risk of breaking existing attestation chains
- ✅ Simple, testable implementation (~50 lines)
- ✅ Explicit ordering guarantees determinism
- ✅ Cross-platform verified (Windows, macOS, Linux, Alpine, Debian)
### Negative
- ⚠️ Code duplication with ProofChain (minimal - different use case)
- ⚠️ Need to maintain separate Merkle tree implementation (low maintenance burden)
### Neutral
- 📝 Custom implementation documented in tests (CgsDeterminismTests.cs)
- 📝 Future: Could extract shared Merkle tree primitives if needed
## Alternatives Considered
### Alternative 1: Modify ProofChain Builder
**Rejected because:**
- Would require adding configuration options to ProofChain
- Risk of breaking existing attestation behavior
- Increased complexity for both use cases
- Tight coupling between verdict and attestation systems
### Alternative 2: Use Third-Party Merkle Tree Library
**Rejected because:**
- External dependency for ~50 lines of code
- Less control over ordering and hash format
- Potential platform-specific issues
- Security review overhead
### Alternative 3: Single-Level Hash (No Merkle Tree)
**Rejected because:**
- Loses incremental verification capability
- Can't prove individual evidence components without full evidence pack
- Less efficient for large evidence packs (can't skip unchanged components)
## Verification
### Test Coverage
File: `src/__Tests/Determinism/CgsDeterminismTests.cs`
1. **Golden File Test**: Known evidence produces expected hash
2. **10-Iteration Stability**: Same input produces identical hash 10 times
3. **VEX Order Independence**: VEX document ordering doesn't affect hash
4. **Reachability Inclusion**: Reachability graph changes hash predictably
5. **Policy Lock Versioning**: Different policy versions produce different hashes
### Cross-Platform Verification
CI/CD Workflow: `.gitea/workflows/cross-platform-determinism.yml`
- ✅ Windows (glibc)
- ✅ macOS (BSD libc)
- ✅ Linux Ubuntu (glibc)
- ✅ Linux Alpine (musl libc)
- ✅ Linux Debian (glibc)
All platforms produce identical CGS hash for same input.
## Migration
No migration required - this is a new feature.
## References
- **Sprint**: `docs/implplan/archived/SPRINT_20251229_001_001_BE_cgs_infrastructure.md`
- **Implementation**: `src/__Libraries/StellaOps.Verdict/VerdictBuilderService.cs`
- **Tests**: `src/__Tests/Determinism/CgsDeterminismTests.cs`
- **ProofChain**: `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/`
## Decision Date
2025-12-29
## Decision Makers
- Backend Team
- Security Team
- Attestation Team (consulted)
## Review Date
2026-06-29 (6 months) - Evaluate if code duplication warrants shared library

View File

@@ -0,0 +1,441 @@
# ADR 0043: Fulcio Keyless Signing with Optional Parameter
## Status
ACCEPTED (2025-12-29)
## Context
StellaOps must support both **cloud-connected** and **air-gapped** deployments. Verdict signing requirements differ:
### Cloud-Connected Deployments
- Access to Fulcio (Sigstore) for keyless signing
- OIDC token available from identity provider
- Ephemeral keys generated per signature
- Transparency log (Rekor) accessible
### Air-Gapped Deployments
- No internet access (Fulcio/Rekor unreachable)
- No OIDC token available
- Must operate without external dependencies
- Long-lived keys managed internally (if signing required)
### Requirements
1. **Single Codebase**: Same `VerdictBuilderService` for both modes
2. **Runtime Configuration**: Deployment mode determined at startup
3. **No Breaking Changes**: Existing air-gap deployments must continue working
4. **Clear Separation**: Signing concerns separate from verdict building logic
## Decision
**Add optional `IDsseSigner? signer` parameter to `VerdictBuilderService` constructor.**
### Rationale
1. **Dependency Injection Friendly**: Signer is injected (or not) based on deployment config
2. **Explicit Air-Gap Mode**: `null` signer clearly indicates air-gapped operation
3. **Single Implementation**: No need for separate `VerdictBuilderService` classes
4. **Production Signing Pipeline**: Even with signer available, production verdicts go through `StellaOps.Signer` service for Proof-of-Entitlement (PoE) validation
### Implementation
```csharp
// VerdictBuilderService.cs
public sealed class VerdictBuilderService : IVerdictBuilder
{
private readonly ILogger<VerdictBuilderService> _logger;
private readonly IDsseSigner? _signer; // Null for air-gap mode
/// <summary>
/// Creates a VerdictBuilderService.
/// </summary>
/// <param name="logger">Logger instance</param>
/// <param name="signer">Optional DSSE signer (e.g., KeylessDsseSigner for Fulcio).
/// Null for air-gapped deployments.</param>
public VerdictBuilderService(
ILogger<VerdictBuilderService> logger,
IDsseSigner? signer = null)
{
_logger = logger;
_signer = signer;
if (_signer == null)
{
_logger.LogInformation("VerdictBuilder initialized without signer (air-gapped mode)");
}
else
{
_logger.LogInformation("VerdictBuilder initialized with signer: {SignerType}",
_signer.GetType().Name);
}
}
private async ValueTask<DsseEnvelope> CreateDsseEnvelopeAsync(
VerdictPayload verdict,
string cgsHash,
CancellationToken ct)
{
var payloadJson = JsonSerializer.Serialize(verdict, CanonicalJsonOptions);
var payloadBytes = Encoding.UTF8.GetBytes(payloadJson);
var payloadBase64 = Convert.ToBase64String(payloadBytes);
if (_signer != null)
{
_logger.LogDebug("Creating signed DSSE envelope with signer");
// Note: Full signing integration requires SigningRequest with ProofOfEntitlement.
// This is typically handled at the API layer (VerdictEndpoints) where caller
// context and entitlement are available.
//
// For production use, verdicts should be signed via the Signer service pipeline
// which handles proof-of-entitlement, caller authentication, and quota enforcement.
}
// Create unsigned envelope (suitable for air-gapped deployments)
// In production, verdicts are signed separately via Signer service after PoE validation
return new DsseEnvelope(
PayloadType: "application/vnd.stellaops.verdict+json",
Payload: payloadBase64,
Signatures: new[]
{
new DsseSignature(
Keyid: $"cgs:{cgsHash}",
Sig: "unsigned:use-signer-service-for-production-signatures"
)
}
);
}
}
```
### Configuration Examples
#### Cloud-Connected Deployment (Fulcio)
```csharp
// Program.cs
services.AddVerdictBuilder(options =>
{
options.SigningMode = VerdictSigningMode.Keyless;
options.FulcioUrl = "https://fulcio.sigstore.dev";
options.OidcIssuer = "https://oauth2.sigstore.dev/auth";
});
// Internal implementation:
services.AddSingleton<IDsseSigner, KeylessDsseSigner>();
services.AddSingleton<IVerdictBuilder, VerdictBuilderService>();
```
#### Air-Gapped Deployment
```csharp
// Program.cs
services.AddVerdictBuilder(options =>
{
options.SigningMode = VerdictSigningMode.AirGap;
// No signer configured
});
// Internal implementation:
// IDsseSigner not registered
services.AddSingleton<IVerdictBuilder, VerdictBuilderService>();
```
#### Long-Lived Key Deployment (Future)
```csharp
// Program.cs
services.AddVerdictBuilder(options =>
{
options.SigningMode = VerdictSigningMode.LongLivedKey;
options.KeyPath = "/etc/stellaops/signing-key.pem";
});
// Internal implementation:
services.AddSingleton<IDsseSigner, LongLivedKeySigner>();
services.AddSingleton<IVerdictBuilder, VerdictBuilderService>();
```
## Consequences
### Positive
-**Single Codebase**: Same service for all deployment modes
-**Clear Intent**: `null` signer explicitly communicates air-gap mode
-**DI Friendly**: Standard dependency injection pattern
-**No Breaking Changes**: Existing air-gap deployments work without modification
-**Future Extensible**: Easy to add new signer implementations
### Negative
- ⚠️ **Runtime Validation**: Can't enforce signer requirement at compile time (must check at runtime)
- ⚠️ **Separation of Concerns**: Verdict building logic includes signing creation (even if unsigned)
- ⚠️ **Documentation Burden**: Developers must understand when to use signer vs null
### Neutral
- 📝 **Production Pipeline**: Even with signer, production signatures go through Signer service for PoE
- 📝 **Testing**: Tests create `VerdictBuilderService(logger, signer: null)` for simplicity
## Alternatives Considered
### Alternative 1: Separate Classes
```csharp
public class VerdictBuilderService : IVerdictBuilder
{
// Air-gap implementation
}
public class SignedVerdictBuilderService : IVerdictBuilder
{
private readonly IDsseSigner _signer;
// Keyless/signed implementation
}
```
**Rejected because:**
- Code duplication for verdict building logic
- Configuration complexity (which class to register?)
- Testing overhead (must test both classes)
- Tight coupling between signing and verdict building
### Alternative 2: Strategy Pattern
```csharp
public interface IVerdictSigningStrategy
{
Task<DsseEnvelope> SignAsync(VerdictPayload payload);
}
public class AirGapSigningStrategy : IVerdictSigningStrategy { }
public class KeylessSigningStrategy : IVerdictSigningStrategy { }
public class VerdictBuilderService
{
private readonly IVerdictSigningStrategy _signingStrategy;
}
```
**Rejected because:**
- Over-engineering for simple null check
- Additional abstraction layer for minimal benefit
- More difficult to understand for developers
### Alternative 3: Configuration Flag
```csharp
public class VerdictBuilderService
{
private readonly VerdictBuilderOptions _options;
public VerdictBuilderService(IOptions<VerdictBuilderOptions> options)
{
_options = options.Value;
if (_options.EnableSigning)
{
// Initialize signer
}
}
}
```
**Rejected because:**
- Hides signer dependency (not visible in constructor)
- Requires options even when not needed
- Less DI-friendly (can't inject mock signer for testing)
### Alternative 4: Builder Pattern
```csharp
var verdictBuilder = VerdictBuilderService
.Create()
.WithLogger(logger)
.WithKeylessSigning(fulcioClient)
.Build();
```
**Rejected because:**
- Not compatible with DI containers
- Verbose API for simple configuration
- Testing complexity (must build every time)
## Implementation Notes
### Signer Interface
```csharp
// StellaOps.Signer.Core.IDsseSigner
public interface IDsseSigner
{
/// <summary>
/// Signs a payload and returns DSSE envelope.
/// </summary>
Task<DsseEnvelope> SignAsync(
byte[] payload,
string payloadType,
SigningOptions options,
CancellationToken ct = default);
}
```
### Keyless Signer Implementation
Location: `src/Signer/__Libraries/StellaOps.Signer.Keyless/KeylessDsseSigner.cs`
**Dependencies:**
- `IFulcioClient` - Ephemeral certificate issuance
- `IAmbientOidcTokenProvider` - OIDC token acquisition
- `IRekorClient` - Transparency log submission (optional)
**Workflow:**
1. Generate ephemeral key pair (ECDSA P-256)
2. Acquire OIDC token from ambient provider
3. Request certificate from Fulcio (binds identity to public key)
4. Sign payload with ephemeral private key
5. Create DSSE envelope with signature + certificate
6. Submit to Rekor transparency log (if configured)
7. Discard ephemeral private key
### Air-Gap Mode
When `signer == null`:
1. Create DSSE envelope structure
2. Set `keyid` to `cgs:{hash}` (content-addressed identifier)
3. Set `sig` to `"unsigned:use-signer-service-for-production-signatures"`
4. Log warning if envelope is used in production context
## Security Considerations
### Proof-of-Entitlement (PoE)
**Critical**: Even when `IDsseSigner` is available, production verdict signing must go through the **Signer service pipeline** for:
1. **Caller Authentication**: Verify caller has permission to sign verdicts
2. **Proof-of-Entitlement**: Validate caller owns/operates the artifacts being assessed
3. **Quota Enforcement**: Rate-limit signing operations per tenant
4. **Audit Logging**: Record who signed what, when, and why
**VerdictBuilderService Role**: Creates unsigned envelopes (or test signatures)
**Signer Service Role**: Applies production signatures with PoE validation
### Separation of Concerns
```
┌─────────────────────────────────────────────────────────────┐
│ VerdictBuilderService │
│ - Computes CGS hash │
│ - Builds verdict payload │
│ - Creates unsigned DSSE envelope │
│ - Returns VerdictResult │
└──────────────────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Signer Service (Production Pipeline) │
│ - Validates Proof-of-Entitlement (PoE) │
│ - Authenticates caller │
│ - Enforces quotas │
│ - Signs verdict with Fulcio/Long-lived key │
│ - Submits to Rekor transparency log │
│ - Returns signed DSSE envelope │
└─────────────────────────────────────────────────────────────┘
```
## Testing Strategy
### Unit Tests
```csharp
// Test with null signer (air-gap mode)
[Fact]
public async Task BuildAsync_WithoutSigner_CreatesUnsignedEnvelope()
{
var service = new VerdictBuilderService(logger, signer: null);
var result = await service.BuildAsync(evidence, policyLock, ct);
result.Dsse.Signatures[0].Sig.Should().StartWith("unsigned:");
}
// Test with mock signer (cloud mode)
[Fact]
public async Task BuildAsync_WithSigner_UsesSignerForProduction()
{
var mockSigner = new Mock<IDsseSigner>();
var service = new VerdictBuilderService(logger, mockSigner.Object);
// Note: Current implementation creates unsigned envelope
// Production signing happens via Signer service pipeline
}
```
### Integration Tests
```csharp
[Fact]
public async Task EndToEnd_VerdictSigning_WithFulcio()
{
// Arrange: Full production pipeline
var fulcioClient = new FulcioClient(fulcioUrl);
var oidcProvider = new AmbientOidcTokenProvider();
var signer = new KeylessDsseSigner(fulcioClient, oidcProvider);
var signerService = new SignerService(signer, poeValidator, quotaEnforcer);
var verdictBuilder = new VerdictBuilderService(logger, signer);
// Act: Build verdict → Sign with PoE → Verify
var verdict = await verdictBuilder.BuildAsync(evidence, policyLock, ct);
var signedEnvelope = await signerService.SignVerdictAsync(verdict, proofOfEntitlement, ct);
// Assert
signedEnvelope.Signatures.Should().HaveCount(1);
signedEnvelope.Signatures[0].Sig.Should().NotStartWith("unsigned:");
}
```
## Migration
No migration required - this is a new feature.
**Backward Compatibility**: Existing code that creates `VerdictBuilderService` without signer parameter will use default `signer: null` (air-gap mode).
## Monitoring
### Metrics
- `verdict_builder.signing_mode{mode="airgap"}` - Count of air-gap verdicts
- `verdict_builder.signing_mode{mode="keyless"}` - Count of keyless verdicts
- `verdict_builder.unsigned_envelopes_created` - Count of unsigned envelopes
### Alerts
- **Warning**: High volume of unsigned verdicts in production (should go through Signer service)
- **Error**: Signer initialization failed in cloud deployment
- **Critical**: OIDC token acquisition failed (blocks keyless signing)
## References
- **Sprint**: `docs/implplan/archived/SPRINT_20251229_001_001_BE_cgs_infrastructure.md`
- **Implementation**: `src/__Libraries/StellaOps.Verdict/VerdictBuilderService.cs`
- **Signer Interface**: `src/Signer/StellaOps.Signer/StellaOps.Signer.Core/IDsseSigner.cs`
- **Keyless Implementation**: `src/Signer/__Libraries/StellaOps.Signer.Keyless/KeylessDsseSigner.cs`
- **ADR 0042**: CGS Merkle Tree Implementation
## Decision Date
2025-12-29
## Decision Makers
- Backend Team
- Security Team
- DevOps Team (air-gap deployment experts)
## Review Date
2026-06-29 (6 months) - Evaluate if PoE integration needs tighter coupling

View File

@@ -0,0 +1,205 @@
# ADR 0044: Binary Delta Signatures for Backport Detection
## Status
ACCEPTED (2026-01-03)
## Context
Vulnerability scanners today rely on version string comparison to determine if a package is vulnerable. However, Linux distributions (RHEL, Debian, Ubuntu, SUSE, Alpine) routinely **backport** security fixes into older versions without bumping the upstream version number.
### The Problem
**Example:** OpenSSL 1.0.1e on RHEL 6 has Heartbleed (CVE-2014-0160) patched, but upstream says `1.0.1e < 1.0.1g` (the fix version), so scanners flag it as vulnerable. This creates:
1. **False positives** - Patched systems flagged as vulnerable
2. **Alert fatigue** - Security teams waste time investigating non-issues
3. **Compliance failures** - Audit reports show phantom vulnerabilities
4. **Trust erosion** - Users distrust scanner results
### Current Mitigations
1. **Distro-specific advisory feeds** (DSA, RHSA, USN) - Incomplete coverage
2. **VEX statements from vendors** - Requires vendor participation, often delayed
3. **Manual triage** - Doesn't scale
4. **OVAL feeds** - OS packages only, not application binaries
### Requirements
- **Binary-level detection**: Examine compiled code, not version strings
- **Cryptographic proof**: Hash-based evidence that fix is present
- **Offline operation**: Work in air-gapped environments
- **Multi-architecture**: Support x86-64, ARM64, and other ISAs
- **Deterministic**: Same binary → same signature across platforms
- **LTO resilience**: Handle Link-Time Optimization changes
## Decision
**Implement binary delta signature matching using normalized code comparison.**
### Architecture
```
┌────────────────────────────────────────────────────────────────────────────┐
│ Delta Signature Pipeline │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ Binary Disassembly Normalization Signature │
│ ───────────► ───────────────► ──────────────► ─────────────► │
│ ELF/PE/MachO Iced (x86) or Zero addresses SHA-256 + │
│ B2R2 (ARM/MIPS) Canonicalize NOPs CFG hash + │
│ Normalize PLT/GOT Chunk hashes │
│ │
└────────────────────────────────────────────────────────────────────────────┘
```
### Disassembly Engine Selection
**Chosen: Plugin-based architecture with Iced (primary) + B2R2 (fallback)**
| Engine | Strengths | Weaknesses |
|--------|-----------|------------|
| **Iced** | Fastest x86/x86-64, MIT license, pure C# | x86 only |
| **B2R2** | Multi-arch (ARM, MIPS, RISC-V), IR lifting, MIT license | F# (requires wrapper) |
**Rationale:**
- Iced for performance-critical x86/x86-64 path (90%+ of scanned binaries)
- B2R2 for ARM64, MIPS, RISC-V when needed
- Plugin architecture allows adding engines without core changes
### Normalization Strategy
To compare binaries compiled by different toolchains/versions, we normalize:
1. **Zero absolute addresses** - Remove PC-relative and RIP-relative variance
2. **Canonicalize NOPs** - Collapse multi-byte NOPs (0x90, 0x0F1F, etc.) to single NOP
3. **Normalize PLT/GOT** - Replace dynamic linking stubs with symbolic tokens
4. **Zero relocations** - Remove relocation target variance
5. **Normalize jump tables** - Convert absolute offsets to relative
**Recipe versioning**: Every signature includes the normalization recipe ID and version. Changing normalization behavior requires a version bump.
### Signature Components
```json
{
"schema": "stellaops.deltasig.v1",
"cve": "CVE-2014-0160",
"package": { "name": "openssl", "soname": "libssl.so.1.0.0" },
"target": { "arch": "x86_64", "abi": "gnu" },
"normalization": { "recipeId": "stellaops.normalize.x64.v1", "version": "1.0.0" },
"signatureState": "patched",
"symbols": [
{
"name": "tls1_process_heartbeat",
"hashAlg": "sha256",
"hashHex": "abc123...",
"sizeBytes": 1234,
"cfgBbCount": 15,
"cfgEdgeHash": "def456...",
"chunks": [
{ "offset": 0, "size": 2048, "hashHex": "..." },
{ "offset": 2048, "size": 2048, "hashHex": "..." }
]
}
]
}
```
### Matching Strategy
1. **Exact match** - Full normalized hash matches patched or vulnerable signature
2. **Chunk match** - ≥70% of chunks match (handles LTO modifications)
3. **CFG match** - Control flow graph structure matches (catches recompilations)
### VEX Evidence Emission
When a binary is confirmed patched via delta signature:
```json
{
"result": "patched",
"cveIds": ["CVE-2014-0160"],
"confidence": 0.95,
"symbolMatches": [
{ "symbolName": "tls1_process_heartbeat", "state": "patched", "exactMatch": true }
],
"justification": "vulnerable_code_not_present",
"summary": "Binary confirmed PATCHED with 95% confidence. 1 symbol(s) matched patched signatures exactly."
}
```
This evidence feeds into VEX candidate generation with full audit trail.
## Alternatives Considered
### 1. Source Code Comparison
**Rejected**: Requires source access, doesn't work for closed-source binaries, compile options affect behavior.
### 2. Debug Symbol Matching
**Rejected**: Symbols often stripped in production, doesn't prove code content.
### 3. File Hash Matching
**Rejected**: Entire binary must match exactly; any rebuild invalidates signature.
### 4. YARA Rules
**Rejected**: Pattern-based, high false positive rate, doesn't provide cryptographic proof.
### 5. Single Disassembly Engine (B2R2 only)
**Rejected**: Performance critical; Iced is 3-5x faster for x86/x86-64 which is 90%+ of scanned binaries.
## Consequences
### Positive
1. **Eliminate false positives** for backported security fixes
2. **Cryptographic proof** of patch status (auditable, reproducible)
3. **Offline operation** with signature packs
4. **Multi-architecture** support for modern infrastructure
5. **VEX integration** for automated triage
### Negative
1. **Signature authoring required** - Must create signatures for each CVE/package
2. **Normalization limits** - Extreme compiler optimizations may defeat matching
3. **Storage overhead** - Signature database growth
4. **Compute cost** - Disassembly + normalization per binary
### Mitigations
- **Signature federation** - Share signatures across organizations
- **Chunk matching** - Resilient to LTO and PGO changes
- **Priority authoring** - Focus on high-severity CVEs first
- **Incremental scanning** - Cache analysis results
## Implementation
### Sprint: SPRINT_20260102_001_BE
| Component | Status | Notes |
|-----------|--------|-------|
| Disassembly.Abstractions | DONE | Plugin interface, models |
| Disassembly.Iced | DONE | x86/x86-64 support |
| Disassembly.B2R2 | DONE | Multi-arch support |
| Normalization | DONE | X64 + ARM64 pipelines |
| DeltaSig | DONE | Generator + matcher |
| Persistence | DONE | PostgreSQL schema |
| CLI | DONE | extract, author, sign, verify, match, pack, inspect |
| Scanner integration | DONE | DeltaSigAnalyzer, IBinaryVulnerabilityService |
| VEX emission | DONE | DeltaSignatureEvidence, DeltaSigVexEmitter |
### Test Coverage
- 74 unit tests for DeltaSig library
- 45 unit tests for Normalization
- 24 unit tests for Disassembly
- 11 property tests (FsCheck) for normalization idempotency
- 14 golden tests for known CVEs (Heartbleed, Log4Shell, POODLE)
- 25 unit tests for VEX evidence emission
## References
- [Binary Diff Signatures Advisory](../product-advisories/30-Dec-2025%20-%20Binary%20Diff%20Signatures%20for%20Patch%20Detection.md)
- [B2R2 GitHub](https://github.com/B2R2-org/B2R2)
- [Iced GitHub](https://github.com/icedland/iced)
- [OpenVEX Specification](https://github.com/openvex/spec)
- [CVE-2014-0160 (Heartbleed)](https://nvd.nist.gov/vuln/detail/CVE-2014-0160)

View File

@@ -0,0 +1,165 @@
# RFC: StellaOps.Authority.Plugin.Ldap
**Status:** Accepted Auth Guild, Security Guild, DevEx Docs sign-off (2025-11-03)
**Authors:** Plugin Team 4 (Auth Libraries & Identity Providers)
**Related initiatives:** PLG7 backlog, CORE5 event handlers, DOC4 developer guide
> Review log captured in `docs/notes/2025-11-03-authority-plugin-ldap-review.md`. Decisions below reflect consensus from Auth Guild, Security Guild, and DevEx Docs walkthroughs held on 2025-11-03.
## 0. Review Summary (2025-11-03)
- Confirmed plugin charter: deliver offline-friendly LDAP credential verification with deterministic caching and audit parity with the Standard plugin.
- Resolved open questions:
- **Client provisioning audit trail:** plugin always mirrors credential lifecycle metadata (actor, timestamp, hashed secret reference) into Authoritys Mongo store even when LDAP writes succeed; directory writes remain optional and secrets never persist locally.
- **Mutual TLS support:** LDAPS client certificates are required for regulated installations; configuration gains optional client certificate bindings with secret-provider integration and deterministic trust-store selection.
- **Group-to-role mapping:** mappings accept static DN dictionaries and deterministic `regex` matchers; regex captures project to canonical roles via substitution (documented contract for policy automation).
- Follow-up implementation issues filed in `StellaOps.Authority.Plugin.Standard/TASKS.md` (see Section 11) to track scaffolding, mutual TLS enablement, audit mirror, and mapping enhancements.
## 1. Problem Statement
Many on-prem StellaOps deployments rely on existing LDAP/Active Directory domains for workforce identity. The current Standard Mongo-backed plugin requires duplicating users and secrets, which increases operational overhead and violates corporate policy in some regulated environments. We need a sovereign, offline-friendly LDAP plugin that:
- Supports password grant and bootstrap provisioning flows without storing credentials in Mongo.
- Enforces StellaOps security policies (lockout, password policy hints, audit logging) while delegating credential validation to LDAP.
- Operates deterministically in offline or partially connected environments by caching directory metadata when necessary.
## 2. Goals
- Provide a first-party `StellaOps.Authority.Plugin.Ldap` plugin advertising `password` and optional `clientProvisioning` capabilities at launch.
- Support username/password authentication against LDAP bind operations with configurable DN templates.
- Allow optional bootstrap seeding of service accounts by writing into LDAP (guarded behind explicit configuration) or by mapping to pre-existing entries.
- Surface directory-derived claims (groups, attributes) for downstream authorization via `IClaimsEnricher`.
- Integrate with Authority lockout telemetry and structured logging without persisting secrets locally.
## 3. Non-Goals
- Implement multi-factor authentication out of the box (future enhancement once TOTP/WebAuthn strategy is finalised).
- Provide write-heavy directory management (e.g., user creation workflows) beyond optional bootstrap service account seeding.
- Replace the Standard plugin; both must remain supported and selectable per environment.
## 4. Key Constraints & Assumptions
- Offline-first posture: deployments may operate without outbound internet and with intermittent directory connectivity (e.g., read-only replicas). The plugin must tolerate transient LDAP connectivity failures and degrade gracefully.
- Deterministic behaviour: identical configuration and directory state must yield identical token issuance results. Cached metadata (e.g., group lookups) must have defined expiration.
- Security: No plaintext credential storage; TLS must be enforced for LDAP connections unless explicitly overridden for air-gapped lab environments.
## 5. High-Level Architecture
1. **Configuration binding** (`ldap.yaml`): defines server endpoints, bind strategy, claim mapping, and optional bootstrap overrides.
2. **Connection factory**: pooled LDAP connections using a resilient client (preferred dependency: `Novell.Directory.Ldap.NETStandard`).
3. **Credential validator** (`IUserCredentialStore`): performs bind-as-user flow with optional fallback bind using service account when directories disallow anonymous search.
4. **Claims enricher** (`IClaimsEnricher`): queries group membership/attributes and projects them into canonical roles/claims.
5. **Optional client provisioning / bootstrap** (`IClientProvisioningStore` + `IUserCredentialStore.UpsertUserAsync`): maintains machine/service principals either in Mongo (metadata) or via LDAP entries based on configuration. Capabilities are only advertised when the manifest requests them, configuration enables them, **and** the plug-in proves the bind identity can add/delete entries beneath the configured containers; otherwise the feature is automatically downgraded so read-only deployments remain safe.
6. **Health checks**: periodic LDAP `whoami` or `search` probes surfaced through `AuthorityPluginHealthResult`.
```
Authority Host
├── Plugin Manifest (ldap)
├── Registrar → registers ConnectionFactory, LdapCredentialStore, LdapClaimsEnricher
├── Password Grant Handler → CredentialStore.VerifyPasswordAsync → LDAP Bind
└── Claims Pipeline → ClaimsEnricher.EnrichAsync → LDAP group lookup
```
## 6. Configuration Schema (Draft)
```yaml
connection:
host: "ldaps://ldap.example.internal"
port: 636
useStartTls: false
validateCertificates: true
clientCertificate:
pfxPath: "file:/etc/stellaops/certs/ldap-client.pfx"
passwordSecret: "file:/etc/stellaops/secrets/ldap-client-pfx.txt"
sendChain: true
trustStore:
mode: "system" # system | bundle
bundlePath: "file:/etc/stellaops/trust/ldap-root.pem"
bindDn: "cn=stellaops-bind,ou=service,dc=example,dc=internal"
bindPasswordSecret: "file:/etc/stellaops/secrets/ldap-bind.txt"
searchBase: "dc=example,dc=internal"
usernameAttribute: "uid"
userDnFormat: "uid={username},ou=people,dc=example,dc=internal" # optional template
security:
requireTls: true
allowInsecureWithEnvToggle: false # requires STELLAOPS_LDAP_ALLOW_INSECURE=true
allowedCipherSuites: [] # optional allow-list
referralChasing: false
lockout:
useAuthorityPolicies: true # reuse Authority lockout counters
directoryLockoutAttribute: "pwdAccountLockedTime"
claims:
groupAttribute: "memberOf"
groupToRoleMap:
"cn=stellaops-admins,ou=groups,dc=example,dc=internal": "operators"
"cn=stellaops-read,ou=groups,dc=example,dc=internal": "auditors"
regexMappings:
- pattern: "^cn=stellaops-(?P<role>[a-z-]+),ou=groups,dc=example,dc=internal$"
roleFormat: "{role}" # yields operators/investigate/etc.
extraAttributes:
displayName: "displayName"
email: "mail"
clientProvisioning:
enabled: false
containerDn: "ou=service,dc=example,dc=internal"
secretAttribute: "userPassword"
auditMirror:
enabled: true
collectionName: "ldap_client_provisioning"
health:
probeIntervalSeconds: 60
timeoutSeconds: 5
```
## 7. Capability Mapping
| Capability | Implementation Notes |
|------------|---------------------|
| `password` | Bind-as-user validation with Authority lockout integration. Mandatory. Requires TLS and optionally client certificate binding per regulated install posture. |
| `clientProvisioning` | Optional; when enabled, creates/updates LDAP entries for machine clients **and** mirrors lifecycle metadata into Mongo for audit parity. |
| `bootstrap` | Exposed only when bootstrap manifest provides service account credentials AND directory write permissions are confirmed during startup; always records audit mirror entries. |
| `mfa` | Not supported in MVP. Future iteration may integrate TOTP attributes or external MFA providers. |
## 8. Operational Considerations
- **Offline cache:** provide optional Mongo cache for group membership to keep `/ready` responsive if LDAP is temporarily unreachable. Cache entries must include TTL and invalidation hooks.
- **Secrets management:** accept `file:` and environment variable references; integrate with existing `StellaOps.Configuration` secret providers.
- **Mutual TLS & trust anchors:** support client certificate authentication with deterministic trust-store selection (`system` vs bundled file) to satisfy regulated deployments; surface validation outcomes via health endpoints.
- **Audit mirror:** write deterministic Mongo records capturing provisioning operations (actor, LDAP DN, operation type, hashed secret reference) to align with Authority audit policy even when LDAP is authoritative.
- **Observability:** emit structured logs with event IDs (`LDAP_BIND_START`, `LDAP_BIND_FAILURE`, `LDAP_GROUP_LOOKUP`), counters for success/failure, and latency histograms.
- **Throttling:** reuse Authority rate-limiting middleware; add per-connection throttles to avoid saturating directory servers during brute-force attacks.
## 9. Security & Compliance
- Enforce TLS (`ldaps://` or STARTTLS) by default. Provide explicit `allowInsecure` flag gated behind environment variable for lab/testing only.
- Support optional mutual TLS (client cert authentication) with secret-backed PFX loader and deterministic trust bundle selection.
- Support password hash migration by detecting directory lockout attributes and surfacing `RequiresPasswordReset` when policies demand changes.
- Log distinguished names only at `Debug` level to avoid leaking sensitive structure in default logs.
- Coordinate with Security Guild for penetration testing before GA; incorporate audit log entries for bind attempts and provisioning changes.
## 10. Testing Strategy
- **Unit tests:** mock LDAP connections to validate DN formatting, error mapping, and capability negotiation.
- **Integration tests:** run against an ephemeral OpenLDAP container (seeded via LDIF fixtures) within CI. Include mutual TLS handshake verification (valid/expired certs) and offline cache regression (disconnect LDAP mid-test).
- **Determinism tests:** feed identical LDIF snapshots and configuration to ensure output tokens/claims remain stable across runs.
- **Smoke tests:** `dotnet test` harness plus manual `dotnet run` scenario verifying `/token` password grants, `/internal/users` bootstrap with LDAP-backed store, and Mongo audit mirror entries.
## 11. Implementation Plan
1. Scaffold `StellaOps.Authority.Plugin.Ldap` project + companion test project (net10.0, `<IsAuthorityPlugin>` true).
2. Implement configuration binding/validation, including secret-backed client certificate + trust-store options and `allowInsecureWithEnvToggle`.
3. Build connection factory + credential store with bind logic, TLS enforcement, and deterministic retry policies.
4. Implement claims enricher with regex mapping support and optional Mongo-backed cache layer.
5. Add client provisioning store with LDAP write toggles and Mongo audit mirror (`ldap_client_provisioning` collection).
6. Wire health checks, telemetry, and structured audit events (bind attempts, provisioning, cache fallbacks).
7. Deliver bootstrap validation that inspects directory permissions and logs deterministic capability summary.
8. Extend developer guide and samples with LDAP configuration guidance; include mutual TLS and regex mapping examples.
9. Update docs/TASKS trackers and release notes entry; ensure CI coverage (unit, integration with OpenLDAP, determinism, smoke tests).
## 12. Resolved Questions
- **Audit mirror:** Client provisioning always persists lifecycle metadata in Mongo for audit parity; LDAP remains the credential source of truth.
- **Mutual TLS:** Plugin must support optional client certificate authentication with secret-backed key material and deterministic trust-store selection.
- **Group mapping:** Provide deterministic regex mapping support to translate directory DNs into Authority roles/scopes without custom scripts.
## 13. Timeline (Tentative)
- **Week 1:** RFC review & sign-off.
- **Week 2-3:** Implementation & unit tests.
- **Week 4:** Integration tests + documentation updates.
- **Week 5:** Security review, release candidate packaging.
## 14. Approval
- **Auth Guild Lead:** ✅ Approved 2025-11-03 (see review log).
- **Security Guild Representative:** ✅ Approved 2025-11-03 (see review log).
- **DevEx Docs:** ✅ Approved 2025-11-03 (see review log).
---
Please add comments inline or via PR review. Once approved, track execution under PLG7.