up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-28 00:45:16 +02:00
parent 3b96b2e3ea
commit 1c6730a1d2
95 changed files with 14504 additions and 463 deletions

View File

@@ -7,6 +7,7 @@ namespace StellaOps.Policy.Engine.Services;
/// <summary>
/// Compiles policy DSL to canonical representation, signs it deterministically, and stores per revision.
/// Captures AOC (Attestation of Compliance) metadata for policy revisions.
/// </summary>
internal sealed class PolicyBundleService
{
@@ -40,7 +41,9 @@ internal sealed class PolicyBundleService
throw new ArgumentNullException(nameof(request));
}
var compiledAt = _timeProvider.GetUtcNow();
var compileResult = _compilationService.Compile(new PolicyCompileRequest(request.Dsl));
if (!compileResult.Success || compileResult.CanonicalRepresentation.IsDefaultOrEmpty)
{
return new PolicyBundleResponse(
@@ -49,30 +52,55 @@ internal sealed class PolicyBundleService
Signature: null,
SizeBytes: 0,
CreatedAt: null,
Diagnostics: compileResult.Diagnostics);
Diagnostics: compileResult.Diagnostics,
AocMetadata: null);
}
var payload = compileResult.CanonicalRepresentation.ToArray();
var digest = compileResult.Digest ?? $"sha256:{ComputeSha256Hex(payload)}";
var signature = Sign(digest, request.SigningKeyId);
var artifactDigest = compileResult.Digest ?? $"sha256:{ComputeSha256Hex(payload)}";
var sourceDigest = ComputeSourceDigest(request.Dsl.Source);
var signature = Sign(artifactDigest, request.SigningKeyId);
var createdAt = _timeProvider.GetUtcNow();
// Generate AOC metadata
var compilationId = GenerateCompilationId(packId, version, compiledAt);
var aocMetadata = CreateAocMetadata(
compilationId,
request.Dsl.Syntax,
compiledAt,
sourceDigest,
artifactDigest,
compileResult,
request.Provenance);
var record = new PolicyBundleRecord(
Digest: digest,
Digest: artifactDigest,
Signature: signature,
Size: payload.Length,
CreatedAt: createdAt,
Payload: payload.ToImmutableArray());
Payload: payload.ToImmutableArray(),
AocMetadata: aocMetadata);
await _repository.StoreBundleAsync(packId, version, record, cancellationToken).ConfigureAwait(false);
var aocResponse = new PolicyAocMetadataResponse(
CompilationId: aocMetadata.CompilationId,
CompilerVersion: aocMetadata.CompilerVersion,
CompiledAt: aocMetadata.CompiledAt,
SourceDigest: aocMetadata.SourceDigest,
ArtifactDigest: aocMetadata.ArtifactDigest,
ComplexityScore: aocMetadata.ComplexityScore,
RuleCount: aocMetadata.RuleCount,
DurationMilliseconds: aocMetadata.DurationMilliseconds);
return new PolicyBundleResponse(
Success: true,
Digest: digest,
Digest: artifactDigest,
Signature: signature,
SizeBytes: payload.Length,
CreatedAt: createdAt,
Diagnostics: compileResult.Diagnostics);
Diagnostics: compileResult.Diagnostics,
AocMetadata: aocResponse);
}
private static string ComputeSha256Hex(byte[] payload)
@@ -82,6 +110,14 @@ internal sealed class PolicyBundleService
return Convert.ToHexString(hash).ToLowerInvariant();
}
private static string ComputeSourceDigest(string source)
{
var bytes = Encoding.UTF8.GetBytes(source);
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(bytes, hash);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
private static string Sign(string digest, string? signingKeyId)
{
// Deterministic signature stub suitable for offline testing.
@@ -89,4 +125,51 @@ internal sealed class PolicyBundleService
var mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(digest));
return $"sig:sha256:{Convert.ToHexString(mac).ToLowerInvariant()}";
}
private static string GenerateCompilationId(string packId, int version, DateTimeOffset timestamp)
{
// Deterministic compilation ID based on pack, version, and timestamp
var input = $"{packId}:{version}:{timestamp:O}";
var bytes = Encoding.UTF8.GetBytes(input);
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(bytes, hash);
return $"comp-{Convert.ToHexString(hash).ToLowerInvariant()[..16]}";
}
private static PolicyAocMetadata CreateAocMetadata(
string compilationId,
string compilerVersion,
DateTimeOffset compiledAt,
string sourceDigest,
string artifactDigest,
PolicyCompilationResultDto compileResult,
PolicyProvenanceInput? provenanceInput)
{
var complexity = compileResult.Complexity;
var statistics = compileResult.Statistics;
PolicyProvenance? provenance = null;
if (provenanceInput is not null)
{
provenance = new PolicyProvenance(
SourceType: provenanceInput.SourceType,
SourceUrl: provenanceInput.SourceUrl,
Submitter: provenanceInput.Submitter,
CommitSha: provenanceInput.CommitSha,
Branch: provenanceInput.Branch,
IngestedAt: compiledAt);
}
return new PolicyAocMetadata(
CompilationId: compilationId,
CompilerVersion: compilerVersion,
CompiledAt: compiledAt,
SourceDigest: sourceDigest,
ArtifactDigest: artifactDigest,
ComplexityScore: complexity?.Score ?? 0,
RuleCount: statistics?.RuleCount ?? complexity?.RuleCount ?? 0,
DurationMilliseconds: compileResult.DurationMilliseconds,
Provenance: provenance,
AttestationRef: null);
}
}