save work
This commit is contained in:
@@ -85,8 +85,21 @@ public sealed class PolicyDecisionAttestationService : IPolicyDecisionAttestatio
|
||||
var payloadBase64 = Convert.ToBase64String(statementJson);
|
||||
|
||||
// Sign the payload
|
||||
string? tenantId = request.TenantId;
|
||||
if (string.IsNullOrWhiteSpace(tenantId) && _signerClient is not null && options.UseSignerService)
|
||||
{
|
||||
return new PolicyDecisionAttestationResult
|
||||
{
|
||||
Success = false,
|
||||
Error = "TenantId is required when using the signer service"
|
||||
};
|
||||
}
|
||||
|
||||
tenantId ??= "unknown";
|
||||
|
||||
string? attestationDigest;
|
||||
string? keyId;
|
||||
VexDsseSignature signature;
|
||||
|
||||
if (_signerClient is not null && options.UseSignerService)
|
||||
{
|
||||
@@ -96,7 +109,7 @@ public sealed class PolicyDecisionAttestationService : IPolicyDecisionAttestatio
|
||||
PayloadType = PredicateTypes.StellaOpsPolicyDecision,
|
||||
PayloadBase64 = payloadBase64,
|
||||
KeyId = request.KeyId ?? options.DefaultKeyId,
|
||||
TenantId = request.TenantId
|
||||
TenantId = tenantId
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -110,25 +123,39 @@ public sealed class PolicyDecisionAttestationService : IPolicyDecisionAttestatio
|
||||
};
|
||||
}
|
||||
|
||||
// Compute attestation digest from signed payload
|
||||
attestationDigest = ComputeDigest(statementJson);
|
||||
keyId = signResult.KeyId;
|
||||
signature = new VexDsseSignature
|
||||
{
|
||||
KeyId = signResult.KeyId,
|
||||
Sig = signResult.Signature!
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create unsigned attestation (dev/test mode)
|
||||
attestationDigest = ComputeDigest(statementJson);
|
||||
// Create locally-signed envelope (dev/test mode; placeholder signature).
|
||||
keyId = null;
|
||||
_logger.LogDebug("Created unsigned attestation (signer service not available)");
|
||||
signature = SignLocally(PredicateTypes.StellaOpsPolicyDecision, statementJson);
|
||||
_logger.LogDebug("Created locally-signed attestation (signer service not available)");
|
||||
}
|
||||
|
||||
var envelope = new VexDsseEnvelope
|
||||
{
|
||||
PayloadType = PredicateTypes.StellaOpsPolicyDecision,
|
||||
Payload = payloadBase64,
|
||||
Signatures = [signature]
|
||||
};
|
||||
|
||||
var envelopeJson = SerializeCanonical(envelope);
|
||||
var envelopeDigestHex = Convert.ToHexString(SHA256.HashData(envelopeJson)).ToLowerInvariant();
|
||||
attestationDigest = $"sha256:{envelopeDigestHex}";
|
||||
|
||||
// Submit to Rekor if requested
|
||||
RekorSubmissionResult? rekorResult = null;
|
||||
var shouldSubmitToRekor = request.SubmitToRekor || options.SubmitToRekorByDefault;
|
||||
|
||||
if (shouldSubmitToRekor && attestationDigest is not null)
|
||||
{
|
||||
rekorResult = await SubmitToRekorAsync(attestationDigest, cancellationToken)
|
||||
rekorResult = await SubmitEnvelopeToRekorAsync(envelope, envelopeDigestHex, request, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!rekorResult.Success)
|
||||
@@ -266,6 +293,99 @@ public sealed class PolicyDecisionAttestationService : IPolicyDecisionAttestatio
|
||||
return JsonSerializer.SerializeToUtf8Bytes(value, CanonicalJsonOptions);
|
||||
}
|
||||
|
||||
private static VexDsseSignature SignLocally(string payloadType, byte[] payload)
|
||||
{
|
||||
// DSSE PAE: "DSSEv1" + len(payloadType) + payloadType + len(payload) + payload
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
|
||||
var prefix = "DSSEv1 "u8;
|
||||
writer.Write(prefix);
|
||||
|
||||
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
|
||||
writer.Write(typeBytes.Length.ToString());
|
||||
writer.Write(' ');
|
||||
writer.Write(typeBytes);
|
||||
writer.Write(' ');
|
||||
|
||||
writer.Write(payload.Length.ToString());
|
||||
writer.Write(' ');
|
||||
writer.Write(payload);
|
||||
|
||||
var pae = ms.ToArray();
|
||||
var signatureBytes = SHA256.HashData(pae);
|
||||
|
||||
return new VexDsseSignature
|
||||
{
|
||||
Sig = Convert.ToBase64String(signatureBytes)
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<RekorSubmissionResult> SubmitEnvelopeToRekorAsync(
|
||||
VexDsseEnvelope envelope,
|
||||
string envelopeDigestHex,
|
||||
PolicyDecisionAttestationRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (_rekorClient is null)
|
||||
{
|
||||
return new RekorSubmissionResult
|
||||
{
|
||||
Success = false,
|
||||
Error = "Rekor client not available"
|
||||
};
|
||||
}
|
||||
|
||||
var subjectUris = request.Subjects
|
||||
.OrderBy(static x => x.Name, StringComparer.Ordinal)
|
||||
.Select(static subject =>
|
||||
{
|
||||
var digest = subject.Digest
|
||||
.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal)
|
||||
.Select(static kvp => $"{kvp.Key}:{kvp.Value}")
|
||||
.FirstOrDefault();
|
||||
|
||||
return digest is null ? subject.Name : $"{subject.Name}@{digest}";
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var submitResult = await _rekorClient.SubmitAsync(
|
||||
new VexRekorSubmitRequest
|
||||
{
|
||||
Envelope = envelope,
|
||||
EnvelopeDigest = envelopeDigestHex,
|
||||
ArtifactKind = "policy-decision",
|
||||
SubjectUris = subjectUris
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!submitResult.Success)
|
||||
{
|
||||
return new RekorSubmissionResult
|
||||
{
|
||||
Success = false,
|
||||
Error = submitResult.Error ?? "Rekor submission failed"
|
||||
};
|
||||
}
|
||||
|
||||
if (submitResult.Metadata is null)
|
||||
{
|
||||
return new RekorSubmissionResult
|
||||
{
|
||||
Success = false,
|
||||
Error = "Rekor submission succeeded but no metadata was returned"
|
||||
};
|
||||
}
|
||||
|
||||
return new RekorSubmissionResult
|
||||
{
|
||||
Success = true,
|
||||
LogIndex = submitResult.Metadata.Index,
|
||||
Uuid = submitResult.Metadata.Uuid,
|
||||
IntegratedTime = submitResult.Metadata.IntegratedAt
|
||||
};
|
||||
}
|
||||
|
||||
private static string ComputeDigest(byte[] data)
|
||||
{
|
||||
var hash = SHA256.HashData(data);
|
||||
|
||||
@@ -184,7 +184,8 @@ public sealed class ProofAwareScoringEngine : IScoringEngine
|
||||
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
||||
|
||||
var inputString = $"{input.FindingId}:{input.TenantId}:{input.ProfileId}:{input.AsOf:O}";
|
||||
foreach (var kvp in input.InputDigests?.OrderBy(x => x.Key) ?? [])
|
||||
foreach (var kvp in input.InputDigests?.OrderBy(x => x.Key)
|
||||
?? Enumerable.Empty<System.Collections.Generic.KeyValuePair<string, string>>())
|
||||
{
|
||||
inputString += $":{kvp.Key}={kvp.Value}";
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class ScoringEngineFactory : IScoringEngineFactory
|
||||
/// </summary>
|
||||
public IScoringEngine GetEngine(ScoringProfile profile)
|
||||
{
|
||||
var engine = profile switch
|
||||
IScoringEngine engine = profile switch
|
||||
{
|
||||
ScoringProfile.Simple => _services.GetRequiredService<SimpleScoringEngine>(),
|
||||
ScoringProfile.Advanced => _services.GetRequiredService<AdvancedScoringEngine>(),
|
||||
|
||||
@@ -6,3 +6,4 @@ This file mirrors sprint work for the Policy Engine module.
|
||||
| --- | --- | --- | --- |
|
||||
| `POLICY-GATE-401-033` | `docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md` | DONE (2025-12-13) | Implemented PolicyGateEvaluator (lattice/uncertainty/evidence completeness) and aligned tests/docs; see `src/Policy/StellaOps.Policy.Engine/Gates/PolicyGateEvaluator.cs` and `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Gates/PolicyGateEvaluatorTests.cs`. |
|
||||
| `DET-3401-011` | `docs/implplan/SPRINT_3401_0001_0001_determinism_scoring_foundations.md` | DONE (2025-12-14) | Added `Explain` to `RiskScoringResult` and covered JSON serialization + null-coercion in `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Scoring/RiskScoringResultTests.cs`. |
|
||||
| `PDA-3801-0001` | `docs/implplan/SPRINT_3801_0001_0001_policy_decision_attestation.md` | DONE (2025-12-19) | Implemented `PolicyDecisionAttestationService` + predicate model + DI wiring; covered signer/Rekor flows in `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Attestation/PolicyDecisionAttestationServiceTests.cs`. |
|
||||
|
||||
Reference in New Issue
Block a user