feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration

- Add RateLimitConfig for configuration management with YAML binding support.
- Introduce RateLimitDecision to encapsulate the result of rate limit checks.
- Implement RateLimitMetrics for OpenTelemetry metrics tracking.
- Create RateLimitMiddleware for enforcing rate limits on incoming requests.
- Develop RateLimitService to orchestrate instance and environment rate limit checks.
- Add RateLimitServiceCollectionExtensions for dependency injection registration.
This commit is contained in:
master
2025-12-17 18:02:37 +02:00
parent 394b57f6bf
commit 8bbfe4d2d2
211 changed files with 47179 additions and 1590 deletions

View File

@@ -0,0 +1,137 @@
// -----------------------------------------------------------------------------
// IdGenerationBenchmarks.cs
// Sprint: SPRINT_0501_0001_0001_proof_evidence_chain_master
// Task: PROOF-MASTER-0005
// Description: Benchmarks for content-addressed ID generation
// -----------------------------------------------------------------------------
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
namespace StellaOps.Bench.ProofChain.Benchmarks;
/// <summary>
/// Benchmarks for content-addressed ID generation operations.
/// Target: Evidence ID generation < 50μs for 10KB payload.
/// </summary>
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 10)]
public class IdGenerationBenchmarks
{
private byte[] _smallPayload = null!;
private byte[] _mediumPayload = null!;
private byte[] _largePayload = null!;
private string _canonicalJson = null!;
private Dictionary<string, object> _bundleData = null!;
[GlobalSetup]
public void Setup()
{
// Small: 1KB
_smallPayload = new byte[1024];
RandomNumberGenerator.Fill(_smallPayload);
// Medium: 10KB
_mediumPayload = new byte[10 * 1024];
RandomNumberGenerator.Fill(_mediumPayload);
// Large: 100KB
_largePayload = new byte[100 * 1024];
RandomNumberGenerator.Fill(_largePayload);
// Canonical JSON for bundle ID generation
_bundleData = new Dictionary<string, object>
{
["statements"] = Enumerable.Range(0, 5).Select(i => new
{
statementId = $"sha256:{Guid.NewGuid():N}",
predicateType = "evidence.stella/v1",
predicate = new { index = i, data = Convert.ToBase64String(_smallPayload) }
}).ToList(),
["signatures"] = new[]
{
new { keyId = "key-1", algorithm = "ES256" },
new { keyId = "key-2", algorithm = "ES256" }
}
};
_canonicalJson = JsonSerializer.Serialize(_bundleData, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
});
}
/// <summary>
/// Baseline: Generate evidence ID from small (1KB) payload.
/// Target: < 20μs
/// </summary>
[Benchmark(Baseline = true)]
public string GenerateEvidenceId_Small()
{
return GenerateContentAddressedId(_smallPayload, "evidence");
}
/// <summary>
/// Generate evidence ID from medium (10KB) payload.
/// Target: < 50μs
/// </summary>
[Benchmark]
public string GenerateEvidenceId_Medium()
{
return GenerateContentAddressedId(_mediumPayload, "evidence");
}
/// <summary>
/// Generate evidence ID from large (100KB) payload.
/// Target: < 200μs
/// </summary>
[Benchmark]
public string GenerateEvidenceId_Large()
{
return GenerateContentAddressedId(_largePayload, "evidence");
}
/// <summary>
/// Generate proof bundle ID from JSON content.
/// Target: < 500μs
/// </summary>
[Benchmark]
public string GenerateProofBundleId()
{
return GenerateContentAddressedId(Encoding.UTF8.GetBytes(_canonicalJson), "bundle");
}
/// <summary>
/// Generate SBOM entry ID (includes PURL formatting).
/// Target: < 30μs
/// </summary>
[Benchmark]
public string GenerateSbomEntryId()
{
var digest = "sha256:" + Convert.ToHexString(SHA256.HashData(_smallPayload)).ToLowerInvariant();
var purl = "pkg:npm/%40scope/package@1.0.0";
return $"{digest}:{purl}";
}
/// <summary>
/// Generate reasoning ID with timestamp.
/// Target: < 25μs
/// </summary>
[Benchmark]
public string GenerateReasoningId()
{
var timestamp = DateTimeOffset.UtcNow.ToString("O");
var input = Encoding.UTF8.GetBytes($"reasoning:{timestamp}:{_canonicalJson}");
var hash = SHA256.HashData(input);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
private static string GenerateContentAddressedId(byte[] content, string prefix)
{
var hash = SHA256.HashData(content);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
}

View File

@@ -0,0 +1,199 @@
// -----------------------------------------------------------------------------
// ProofSpineAssemblyBenchmarks.cs
// Sprint: SPRINT_0501_0001_0001_proof_evidence_chain_master
// Task: PROOF-MASTER-0005
// Description: Benchmarks for proof spine assembly and Merkle tree operations
// -----------------------------------------------------------------------------
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
namespace StellaOps.Bench.ProofChain.Benchmarks;
/// <summary>
/// Benchmarks for proof spine assembly operations.
/// Target: Spine assembly (5 items) < 5ms.
/// </summary>
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 10)]
public class ProofSpineAssemblyBenchmarks
{
private List<byte[]> _evidenceItems = null!;
private List<byte[]> _merkleLeaves = null!;
private byte[] _reasoning = null!;
private byte[] _vexVerdict = null!;
[Params(1, 5, 10, 50)]
public int EvidenceCount { get; set; }
[GlobalSetup]
public void Setup()
{
// Generate evidence items of varying sizes
_evidenceItems = Enumerable.Range(0, 100)
.Select(i =>
{
var data = new byte[1024 + (i * 100)]; // 1KB to ~10KB
RandomNumberGenerator.Fill(data);
return data;
})
.ToList();
// Merkle tree leaves
_merkleLeaves = Enumerable.Range(0, 100)
.Select(_ =>
{
var leaf = new byte[32];
RandomNumberGenerator.Fill(leaf);
return leaf;
})
.ToList();
// Reasoning and verdict
_reasoning = new byte[2048];
RandomNumberGenerator.Fill(_reasoning);
_vexVerdict = new byte[512];
RandomNumberGenerator.Fill(_vexVerdict);
}
/// <summary>
/// Assemble proof spine from evidence items.
/// Target: < 5ms for 5 items.
/// </summary>
[Benchmark]
public ProofSpineResult AssembleSpine()
{
var evidence = _evidenceItems.Take(EvidenceCount).ToList();
return AssembleProofSpine(evidence, _reasoning, _vexVerdict);
}
/// <summary>
/// Build Merkle tree from leaves.
/// Target: < 1ms for 100 leaves.
/// </summary>
[Benchmark]
public byte[] BuildMerkleTree()
{
return ComputeMerkleRoot(_merkleLeaves.Take(EvidenceCount).ToList());
}
/// <summary>
/// Generate deterministic bundle ID from spine.
/// Target: < 500μs.
/// </summary>
[Benchmark]
public string GenerateBundleId()
{
var spine = AssembleProofSpine(
_evidenceItems.Take(EvidenceCount).ToList(),
_reasoning,
_vexVerdict);
return ComputeBundleId(spine);
}
/// <summary>
/// Verify spine determinism (same inputs = same output).
/// </summary>
[Benchmark]
public bool VerifyDeterminism()
{
var evidence = _evidenceItems.Take(EvidenceCount).ToList();
var spine1 = AssembleProofSpine(evidence, _reasoning, _vexVerdict);
var spine2 = AssembleProofSpine(evidence, _reasoning, _vexVerdict);
return spine1.BundleId == spine2.BundleId;
}
#region Implementation
private static ProofSpineResult AssembleProofSpine(
List<byte[]> evidence,
byte[] reasoning,
byte[] vexVerdict)
{
// 1. Generate evidence IDs
var evidenceIds = evidence
.OrderBy(e => Convert.ToHexString(SHA256.HashData(e))) // Deterministic ordering
.Select(e => SHA256.HashData(e))
.ToList();
// 2. Build Merkle tree
var merkleRoot = ComputeMerkleRoot(evidenceIds);
// 3. Compute reasoning ID
var reasoningId = SHA256.HashData(reasoning);
// 4. Compute verdict ID
var verdictId = SHA256.HashData(vexVerdict);
// 5. Assemble bundle content
var bundleContent = new List<byte>();
bundleContent.AddRange(merkleRoot);
bundleContent.AddRange(reasoningId);
bundleContent.AddRange(verdictId);
// 6. Compute bundle ID
var bundleId = SHA256.HashData(bundleContent.ToArray());
return new ProofSpineResult
{
BundleId = $"sha256:{Convert.ToHexString(bundleId).ToLowerInvariant()}",
MerkleRoot = merkleRoot,
EvidenceIds = evidenceIds.Select(e => $"sha256:{Convert.ToHexString(e).ToLowerInvariant()}").ToList()
};
}
private static byte[] ComputeMerkleRoot(List<byte[]> leaves)
{
if (leaves.Count == 0)
return SHA256.HashData(Array.Empty<byte>());
if (leaves.Count == 1)
return leaves[0];
var currentLevel = leaves.ToList();
while (currentLevel.Count > 1)
{
var nextLevel = new List<byte[]>();
for (int i = 0; i < currentLevel.Count; i += 2)
{
if (i + 1 < currentLevel.Count)
{
// Hash pair
var combined = new byte[currentLevel[i].Length + currentLevel[i + 1].Length];
currentLevel[i].CopyTo(combined, 0);
currentLevel[i + 1].CopyTo(combined, currentLevel[i].Length);
nextLevel.Add(SHA256.HashData(combined));
}
else
{
// Odd node - promote
nextLevel.Add(currentLevel[i]);
}
}
currentLevel = nextLevel;
}
return currentLevel[0];
}
private static string ComputeBundleId(ProofSpineResult spine)
{
return spine.BundleId;
}
#endregion
}
/// <summary>
/// Result of proof spine assembly.
/// </summary>
public sealed class ProofSpineResult
{
public required string BundleId { get; init; }
public required byte[] MerkleRoot { get; init; }
public required List<string> EvidenceIds { get; init; }
}

View File

@@ -0,0 +1,265 @@
// -----------------------------------------------------------------------------
// VerificationPipelineBenchmarks.cs
// Sprint: SPRINT_0501_0001_0001_proof_evidence_chain_master
// Task: PROOF-MASTER-0005
// Description: Benchmarks for verification pipeline operations
// -----------------------------------------------------------------------------
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
namespace StellaOps.Bench.ProofChain.Benchmarks;
/// <summary>
/// Benchmarks for verification pipeline operations.
/// Target: Full verification < 50ms typical.
/// </summary>
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 10)]
public class VerificationPipelineBenchmarks
{
private TestProofBundle _bundle = null!;
private byte[] _dsseEnvelope = null!;
private List<byte[]> _merkleProof = null!;
[GlobalSetup]
public void Setup()
{
// Create a realistic test bundle
var statements = Enumerable.Range(0, 5)
.Select(i => new TestStatement
{
StatementId = GenerateId(),
PredicateType = "evidence.stella/v1",
Payload = GenerateRandomBytes(1024)
})
.ToList();
var envelopes = statements.Select(s => new TestEnvelope
{
PayloadType = "application/vnd.in-toto+json",
Payload = s.Payload,
Signature = GenerateRandomBytes(64),
KeyId = "test-key-1"
}).ToList();
_bundle = new TestProofBundle
{
BundleId = GenerateId(),
Statements = statements,
Envelopes = envelopes,
MerkleRoot = GenerateRandomBytes(32),
LogIndex = 12345,
InclusionProof = Enumerable.Range(0, 10).Select(_ => GenerateRandomBytes(32)).ToList()
};
// DSSE envelope for signature verification
_dsseEnvelope = JsonSerializer.SerializeToUtf8Bytes(new
{
payloadType = "application/vnd.in-toto+json",
payload = Convert.ToBase64String(GenerateRandomBytes(1024)),
signatures = new[]
{
new { keyid = "key-1", sig = Convert.ToBase64String(GenerateRandomBytes(64)) }
}
});
// Merkle proof (typical depth ~20 for large trees)
_merkleProof = Enumerable.Range(0, 20)
.Select(_ => GenerateRandomBytes(32))
.ToList();
}
/// <summary>
/// DSSE signature verification (crypto operation).
/// Target: < 5ms per envelope.
/// </summary>
[Benchmark]
public bool VerifyDsseSignature()
{
// Simulate signature verification (actual crypto would use ECDsa)
foreach (var envelope in _bundle.Envelopes)
{
var payloadHash = SHA256.HashData(envelope.Payload);
// In real impl, verify signature against public key
_ = SHA256.HashData(envelope.Signature);
}
return true;
}
/// <summary>
/// ID recomputation verification.
/// Target: < 2ms per bundle.
/// </summary>
[Benchmark]
public bool VerifyIdRecomputation()
{
foreach (var statement in _bundle.Statements)
{
var recomputedId = $"sha256:{Convert.ToHexString(SHA256.HashData(statement.Payload)).ToLowerInvariant()}";
if (!statement.StatementId.Equals(recomputedId, StringComparison.OrdinalIgnoreCase))
{
// IDs won't match in this benchmark, but we simulate the work
}
}
return true;
}
/// <summary>
/// Merkle proof verification.
/// Target: < 1ms per proof.
/// </summary>
[Benchmark]
public bool VerifyMerkleProof()
{
var leafHash = SHA256.HashData(_bundle.Statements[0].Payload);
var current = leafHash;
foreach (var sibling in _merkleProof)
{
var combined = new byte[64];
if (current[0] < sibling[0])
{
current.CopyTo(combined, 0);
sibling.CopyTo(combined, 32);
}
else
{
sibling.CopyTo(combined, 0);
current.CopyTo(combined, 32);
}
current = SHA256.HashData(combined);
}
return current.SequenceEqual(_bundle.MerkleRoot);
}
/// <summary>
/// Rekor inclusion proof verification (simulated).
/// Target: < 10ms (cached STH).
/// </summary>
[Benchmark]
public bool VerifyRekorInclusion()
{
// Simulate Rekor verification:
// 1. Verify entry hash
var entryHash = SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(_bundle));
// 2. Verify inclusion proof against STH
return VerifyMerkleProof();
}
/// <summary>
/// Trust anchor key lookup.
/// Target: < 500μs.
/// </summary>
[Benchmark]
public bool VerifyKeyTrust()
{
// Simulate trust anchor lookup
var trustedKeys = new HashSet<string> { "test-key-1", "test-key-2", "test-key-3" };
foreach (var envelope in _bundle.Envelopes)
{
if (!trustedKeys.Contains(envelope.KeyId))
return false;
}
return true;
}
/// <summary>
/// Full verification pipeline.
/// Target: < 50ms typical.
/// </summary>
[Benchmark]
public VerificationResult FullVerification()
{
var steps = new List<StepResult>();
// Step 1: DSSE signatures
var dsseValid = VerifyDsseSignature();
steps.Add(new StepResult { Step = "dsse", Passed = dsseValid });
// Step 2: ID recomputation
var idsValid = VerifyIdRecomputation();
steps.Add(new StepResult { Step = "ids", Passed = idsValid });
// Step 3: Merkle proof
var merkleValid = VerifyMerkleProof();
steps.Add(new StepResult { Step = "merkle", Passed = merkleValid });
// Step 4: Rekor inclusion
var rekorValid = VerifyRekorInclusion();
steps.Add(new StepResult { Step = "rekor", Passed = rekorValid });
// Step 5: Trust anchor
var trustValid = VerifyKeyTrust();
steps.Add(new StepResult { Step = "trust", Passed = trustValid });
return new VerificationResult
{
IsValid = steps.All(s => s.Passed),
Steps = steps
};
}
#region Helpers
private static string GenerateId()
{
var hash = GenerateRandomBytes(32);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
private static byte[] GenerateRandomBytes(int length)
{
var bytes = new byte[length];
RandomNumberGenerator.Fill(bytes);
return bytes;
}
#endregion
}
#region Test Types
internal sealed class TestProofBundle
{
public required string BundleId { get; init; }
public required List<TestStatement> Statements { get; init; }
public required List<TestEnvelope> Envelopes { get; init; }
public required byte[] MerkleRoot { get; init; }
public required long LogIndex { get; init; }
public required List<byte[]> InclusionProof { get; init; }
}
internal sealed class TestStatement
{
public required string StatementId { get; init; }
public required string PredicateType { get; init; }
public required byte[] Payload { get; init; }
}
internal sealed class TestEnvelope
{
public required string PayloadType { get; init; }
public required byte[] Payload { get; init; }
public required byte[] Signature { get; init; }
public required string KeyId { get; init; }
}
internal sealed class VerificationResult
{
public required bool IsValid { get; init; }
public required List<StepResult> Steps { get; init; }
}
internal sealed class StepResult
{
public required string Step { get; init; }
public required bool Passed { get; init; }
}
#endregion