feat: Implement approvals workflow and notifications integration
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added approvals orchestration with persistence and workflow scaffolding. - Integrated notifications insights and staged resume hooks. - Introduced approval coordinator and policy notification bridge with unit tests. - Added approval decision API with resume requeue and persisted plan snapshots. - Documented the Excitor consensus API beta and provided JSON sample payload. - Created analyzers to flag usage of deprecated merge service APIs. - Implemented logging for artifact uploads and approval decision service. - Added tests for PackRunApprovalDecisionService and related components.
This commit is contained in:
@@ -84,10 +84,13 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
||||
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
||||
}
|
||||
|
||||
VexSignatureMetadata? signatureMetadata = null;
|
||||
if (document.Format == VexDocumentFormat.OciAttestation && _attestationVerifier is not null)
|
||||
VexSignatureMetadata? signatureMetadata = null;
|
||||
VexAttestationDiagnostics? attestationDiagnostics = null;
|
||||
if (document.Format == VexDocumentFormat.OciAttestation && _attestationVerifier is not null)
|
||||
{
|
||||
signatureMetadata = await VerifyAttestationAsync(document, metadata, cancellationToken).ConfigureAwait(false);
|
||||
var attestationResult = await VerifyAttestationAsync(document, metadata, cancellationToken).ConfigureAwait(false);
|
||||
signatureMetadata = attestationResult.Metadata;
|
||||
attestationDiagnostics = attestationResult.Diagnostics;
|
||||
}
|
||||
|
||||
signatureMetadata ??= ExtractSignatureMetadata(metadata);
|
||||
@@ -96,31 +99,40 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
||||
signatureMetadata = await AttachIssuerTrustAsync(signatureMetadata, metadata, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
var resultLabel = signatureMetadata is null ? "skipped" : "ok";
|
||||
RecordVerification(document.ProviderId, metadata, resultLabel);
|
||||
|
||||
if (resultLabel == "skipped")
|
||||
{
|
||||
if (attestationDiagnostics is not null)
|
||||
{
|
||||
resultLabel = attestationDiagnostics.Result ?? resultLabel;
|
||||
}
|
||||
|
||||
if (attestationDiagnostics is null)
|
||||
{
|
||||
RecordVerification(document.ProviderId, metadata, resultLabel);
|
||||
}
|
||||
|
||||
if (resultLabel == "skipped")
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Signature verification skipped for provider {ProviderId} (no signature metadata).",
|
||||
document.ProviderId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Signature metadata recorded for provider {ProviderId} (type={SignatureType}, subject={Subject}, issuer={Issuer}).",
|
||||
document.ProviderId,
|
||||
signatureMetadata!.Type,
|
||||
signatureMetadata.Subject ?? "<unknown>",
|
||||
signatureMetadata.Issuer ?? "<unknown>");
|
||||
}
|
||||
|
||||
return signatureMetadata;
|
||||
}
|
||||
|
||||
private async ValueTask<VexSignatureMetadata?> VerifyAttestationAsync(
|
||||
VexRawDocument document,
|
||||
ImmutableDictionary<string, string> metadata,
|
||||
CancellationToken cancellationToken)
|
||||
_logger.LogInformation(
|
||||
"Signature metadata recorded for provider {ProviderId} (type={SignatureType}, subject={Subject}, issuer={Issuer}, result={Result}).",
|
||||
document.ProviderId,
|
||||
signatureMetadata!.Type,
|
||||
signatureMetadata.Subject ?? "<unknown>",
|
||||
signatureMetadata.Issuer ?? "<unknown>",
|
||||
resultLabel);
|
||||
}
|
||||
|
||||
return signatureMetadata;
|
||||
}
|
||||
|
||||
private async ValueTask<(VexSignatureMetadata Metadata, VexAttestationDiagnostics Diagnostics)> VerifyAttestationAsync(
|
||||
VexRawDocument document,
|
||||
ImmutableDictionary<string, string> metadata,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -146,37 +158,48 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
||||
attestationMetadata,
|
||||
envelopeJson);
|
||||
|
||||
var verification = await _attestationVerifier!
|
||||
.VerifyAsync(verificationRequest, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (!verification.IsValid)
|
||||
{
|
||||
var diagnostics = string.Join(", ", verification.Diagnostics.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
_logger.LogError(
|
||||
"Attestation verification failed for provider {ProviderId} (uri={SourceUri}) diagnostics={Diagnostics}",
|
||||
document.ProviderId,
|
||||
document.SourceUri,
|
||||
diagnostics);
|
||||
|
||||
var violation = AocViolation.Create(
|
||||
AocViolationCode.SignatureInvalid,
|
||||
"/upstream/signature",
|
||||
"Attestation verification failed.");
|
||||
|
||||
RecordVerification(document.ProviderId, metadata, "fail");
|
||||
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Attestation verification succeeded for provider {ProviderId} (predicate={PredicateType}, subject={Subject}).",
|
||||
document.ProviderId,
|
||||
attestationMetadata.PredicateType,
|
||||
statement.Subject[0].Name ?? "<unknown>");
|
||||
|
||||
return BuildSignatureMetadata(statement, metadata, attestationMetadata, verification.Diagnostics);
|
||||
}
|
||||
catch (ExcititorAocGuardException)
|
||||
{
|
||||
var verification = await _attestationVerifier!
|
||||
.VerifyAsync(verificationRequest, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var diagnosticsSnapshot = verification.Diagnostics;
|
||||
|
||||
if (!verification.IsValid)
|
||||
{
|
||||
var failureReason = diagnosticsSnapshot.FailureReason ?? "verification_failed";
|
||||
var resultTag = diagnosticsSnapshot.Result ?? "invalid";
|
||||
|
||||
RecordVerification(document.ProviderId, metadata, resultTag);
|
||||
_logger.LogError(
|
||||
"Attestation verification failed for provider {ProviderId} (uri={SourceUri}) result={Result} failure={FailureReason} diagnostics={@Diagnostics}",
|
||||
document.ProviderId,
|
||||
document.SourceUri,
|
||||
resultTag,
|
||||
failureReason,
|
||||
diagnosticsSnapshot);
|
||||
|
||||
var violation = AocViolation.Create(
|
||||
AocViolationCode.SignatureInvalid,
|
||||
"/upstream/signature",
|
||||
"Attestation verification failed.");
|
||||
|
||||
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
||||
}
|
||||
|
||||
var successResult = diagnosticsSnapshot.Result ?? "valid";
|
||||
RecordVerification(document.ProviderId, metadata, successResult);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Attestation verification succeeded for provider {ProviderId} (predicate={PredicateType}, subject={Subject}, result={Result}).",
|
||||
document.ProviderId,
|
||||
attestationMetadata.PredicateType,
|
||||
statement.Subject[0].Name ?? "<unknown>",
|
||||
successResult);
|
||||
|
||||
var signatureMetadata = BuildSignatureMetadata(statement, metadata, attestationMetadata, diagnosticsSnapshot);
|
||||
return (signatureMetadata, diagnosticsSnapshot);
|
||||
}
|
||||
catch (ExcititorAocGuardException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -192,10 +215,10 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
||||
"/upstream/signature",
|
||||
$"Attestation verification encountered an error: {ex.Message}");
|
||||
|
||||
RecordVerification(document.ProviderId, metadata, "fail");
|
||||
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
||||
}
|
||||
}
|
||||
RecordVerification(document.ProviderId, metadata, "error");
|
||||
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
||||
}
|
||||
}
|
||||
|
||||
private VexAttestationRequest BuildAttestationRequest(VexInTotoStatement statement, VexAttestationPredicate predicate)
|
||||
{
|
||||
@@ -252,11 +275,11 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
||||
signedAt);
|
||||
}
|
||||
|
||||
private VexSignatureMetadata BuildSignatureMetadata(
|
||||
VexInTotoStatement statement,
|
||||
ImmutableDictionary<string, string> metadata,
|
||||
VexAttestationMetadata attestationMetadata,
|
||||
ImmutableDictionary<string, string> diagnostics)
|
||||
private VexSignatureMetadata BuildSignatureMetadata(
|
||||
VexInTotoStatement statement,
|
||||
ImmutableDictionary<string, string> metadata,
|
||||
VexAttestationMetadata attestationMetadata,
|
||||
VexAttestationDiagnostics diagnostics)
|
||||
{
|
||||
metadata.TryGetValue("vex.signature.type", out var type);
|
||||
metadata.TryGetValue("vex.provenance.cosign.subject", out var subject);
|
||||
|
||||
Reference in New Issue
Block a user