Implement VEX document verification system with issuer management and signature verification

- Added IIssuerDirectory interface for managing VEX document issuers, including methods for registration, revocation, and trust validation.
- Created InMemoryIssuerDirectory class as an in-memory implementation of IIssuerDirectory for testing and single-instance deployments.
- Introduced ISignatureVerifier interface for verifying signatures on VEX documents, with support for multiple signature formats.
- Developed SignatureVerifier class as the default implementation of ISignatureVerifier, allowing extensibility for different signature formats.
- Implemented handlers for DSSE and JWS signature formats, including methods for verification and signature extraction.
- Defined various records and enums for issuer and signature metadata, enhancing the structure and clarity of the verification process.
This commit is contained in:
StellaOps Bot
2025-12-06 13:41:22 +02:00
parent 2141196496
commit 5e514532df
112 changed files with 24861 additions and 211 deletions

View File

@@ -0,0 +1,228 @@
using System.Collections.Immutable;
using System.Text.Json.Serialization;
using StellaOps.Policy.Engine.Attestation;
namespace StellaOps.Policy.Engine.ConsoleSurface;
/// <summary>
/// Console request for attestation report query per CONTRACT-VERIFICATION-POLICY-006.
/// </summary>
internal sealed record ConsoleAttestationReportRequest(
[property: JsonPropertyName("artifact_digests")] IReadOnlyList<string>? ArtifactDigests,
[property: JsonPropertyName("artifact_uri_pattern")] string? ArtifactUriPattern,
[property: JsonPropertyName("policy_ids")] IReadOnlyList<string>? PolicyIds,
[property: JsonPropertyName("predicate_types")] IReadOnlyList<string>? PredicateTypes,
[property: JsonPropertyName("status_filter")] IReadOnlyList<string>? StatusFilter,
[property: JsonPropertyName("from_time")] DateTimeOffset? FromTime,
[property: JsonPropertyName("to_time")] DateTimeOffset? ToTime,
[property: JsonPropertyName("group_by")] ConsoleReportGroupBy? GroupBy,
[property: JsonPropertyName("sort_by")] ConsoleReportSortBy? SortBy,
[property: JsonPropertyName("page")] int Page = 1,
[property: JsonPropertyName("page_size")] int PageSize = 25);
/// <summary>
/// Grouping options for Console attestation reports.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<ConsoleReportGroupBy>))]
internal enum ConsoleReportGroupBy
{
None,
Policy,
PredicateType,
Status,
ArtifactUri
}
/// <summary>
/// Sorting options for Console attestation reports.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<ConsoleReportSortBy>))]
internal enum ConsoleReportSortBy
{
EvaluatedAtDesc,
EvaluatedAtAsc,
StatusAsc,
StatusDesc,
CoverageDesc,
CoverageAsc
}
/// <summary>
/// Console response for attestation reports.
/// </summary>
internal sealed record ConsoleAttestationReportResponse(
[property: JsonPropertyName("schema_version")] string SchemaVersion,
[property: JsonPropertyName("summary")] ConsoleReportSummary Summary,
[property: JsonPropertyName("reports")] IReadOnlyList<ConsoleArtifactReport> Reports,
[property: JsonPropertyName("groups")] IReadOnlyList<ConsoleReportGroup>? Groups,
[property: JsonPropertyName("pagination")] ConsolePagination Pagination,
[property: JsonPropertyName("filters_applied")] ConsoleFiltersApplied FiltersApplied);
/// <summary>
/// Summary of attestation reports for Console.
/// </summary>
internal sealed record ConsoleReportSummary(
[property: JsonPropertyName("total_artifacts")] int TotalArtifacts,
[property: JsonPropertyName("total_attestations")] int TotalAttestations,
[property: JsonPropertyName("status_breakdown")] ImmutableDictionary<string, int> StatusBreakdown,
[property: JsonPropertyName("coverage_rate")] double CoverageRate,
[property: JsonPropertyName("compliance_rate")] double ComplianceRate,
[property: JsonPropertyName("average_age_hours")] double AverageAgeHours);
/// <summary>
/// Console-friendly artifact attestation report.
/// </summary>
internal sealed record ConsoleArtifactReport(
[property: JsonPropertyName("artifact_digest")] string ArtifactDigest,
[property: JsonPropertyName("artifact_uri")] string? ArtifactUri,
[property: JsonPropertyName("artifact_short_digest")] string ArtifactShortDigest,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("status_label")] string StatusLabel,
[property: JsonPropertyName("status_icon")] string StatusIcon,
[property: JsonPropertyName("attestation_count")] int AttestationCount,
[property: JsonPropertyName("coverage_percentage")] double CoveragePercentage,
[property: JsonPropertyName("policies_passed")] int PoliciesPassed,
[property: JsonPropertyName("policies_failed")] int PoliciesFailed,
[property: JsonPropertyName("evaluated_at")] DateTimeOffset EvaluatedAt,
[property: JsonPropertyName("evaluated_at_relative")] string EvaluatedAtRelative,
[property: JsonPropertyName("details")] ConsoleReportDetails? Details);
/// <summary>
/// Detailed report information for Console.
/// </summary>
internal sealed record ConsoleReportDetails(
[property: JsonPropertyName("predicate_types")] IReadOnlyList<ConsolePredicateTypeStatus> PredicateTypes,
[property: JsonPropertyName("policies")] IReadOnlyList<ConsolePolicyStatus> Policies,
[property: JsonPropertyName("signers")] IReadOnlyList<ConsoleSignerInfo> Signers,
[property: JsonPropertyName("issues")] IReadOnlyList<ConsoleIssue> Issues);
/// <summary>
/// Predicate type status for Console.
/// </summary>
internal sealed record ConsolePredicateTypeStatus(
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("type_label")] string TypeLabel,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("status_label")] string StatusLabel,
[property: JsonPropertyName("freshness")] string Freshness);
/// <summary>
/// Policy status for Console.
/// </summary>
internal sealed record ConsolePolicyStatus(
[property: JsonPropertyName("policy_id")] string PolicyId,
[property: JsonPropertyName("policy_version")] string PolicyVersion,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("status_label")] string StatusLabel,
[property: JsonPropertyName("verdict")] string Verdict);
/// <summary>
/// Signer information for Console.
/// </summary>
internal sealed record ConsoleSignerInfo(
[property: JsonPropertyName("key_fingerprint_short")] string KeyFingerprintShort,
[property: JsonPropertyName("issuer")] string? Issuer,
[property: JsonPropertyName("subject")] string? Subject,
[property: JsonPropertyName("algorithm")] string Algorithm,
[property: JsonPropertyName("verified")] bool Verified,
[property: JsonPropertyName("trusted")] bool Trusted);
/// <summary>
/// Issue for Console display.
/// </summary>
internal sealed record ConsoleIssue(
[property: JsonPropertyName("severity")] string Severity,
[property: JsonPropertyName("message")] string Message,
[property: JsonPropertyName("field")] string? Field);
/// <summary>
/// Report group for Console.
/// </summary>
internal sealed record ConsoleReportGroup(
[property: JsonPropertyName("key")] string Key,
[property: JsonPropertyName("label")] string Label,
[property: JsonPropertyName("count")] int Count,
[property: JsonPropertyName("status_breakdown")] ImmutableDictionary<string, int> StatusBreakdown);
/// <summary>
/// Pagination information for Console.
/// </summary>
internal sealed record ConsolePagination(
[property: JsonPropertyName("page")] int Page,
[property: JsonPropertyName("page_size")] int PageSize,
[property: JsonPropertyName("total_pages")] int TotalPages,
[property: JsonPropertyName("total_items")] int TotalItems,
[property: JsonPropertyName("has_next")] bool HasNext,
[property: JsonPropertyName("has_previous")] bool HasPrevious);
/// <summary>
/// Applied filters information for Console.
/// </summary>
internal sealed record ConsoleFiltersApplied(
[property: JsonPropertyName("artifact_count")] int ArtifactCount,
[property: JsonPropertyName("policy_ids")] IReadOnlyList<string>? PolicyIds,
[property: JsonPropertyName("predicate_types")] IReadOnlyList<string>? PredicateTypes,
[property: JsonPropertyName("status_filter")] IReadOnlyList<string>? StatusFilter,
[property: JsonPropertyName("time_range")] ConsoleTimeRange? TimeRange);
/// <summary>
/// Time range for Console filters.
/// </summary>
internal sealed record ConsoleTimeRange(
[property: JsonPropertyName("from")] DateTimeOffset? From,
[property: JsonPropertyName("to")] DateTimeOffset? To);
/// <summary>
/// Console request for attestation statistics dashboard.
/// </summary>
internal sealed record ConsoleAttestationDashboardRequest(
[property: JsonPropertyName("time_range")] string? TimeRange,
[property: JsonPropertyName("policy_ids")] IReadOnlyList<string>? PolicyIds,
[property: JsonPropertyName("artifact_uri_pattern")] string? ArtifactUriPattern);
/// <summary>
/// Console response for attestation statistics dashboard.
/// </summary>
internal sealed record ConsoleAttestationDashboardResponse(
[property: JsonPropertyName("schema_version")] string SchemaVersion,
[property: JsonPropertyName("overview")] ConsoleDashboardOverview Overview,
[property: JsonPropertyName("trends")] ConsoleDashboardTrends Trends,
[property: JsonPropertyName("top_issues")] IReadOnlyList<ConsoleDashboardIssue> TopIssues,
[property: JsonPropertyName("policy_compliance")] IReadOnlyList<ConsoleDashboardPolicyCompliance> PolicyCompliance,
[property: JsonPropertyName("evaluated_at")] DateTimeOffset EvaluatedAt);
/// <summary>
/// Dashboard overview for Console.
/// </summary>
internal sealed record ConsoleDashboardOverview(
[property: JsonPropertyName("total_artifacts")] int TotalArtifacts,
[property: JsonPropertyName("total_attestations")] int TotalAttestations,
[property: JsonPropertyName("pass_rate")] double PassRate,
[property: JsonPropertyName("coverage_rate")] double CoverageRate,
[property: JsonPropertyName("average_freshness_hours")] double AverageFreshnessHours);
/// <summary>
/// Dashboard trends for Console.
/// </summary>
internal sealed record ConsoleDashboardTrends(
[property: JsonPropertyName("pass_rate_change")] double PassRateChange,
[property: JsonPropertyName("coverage_rate_change")] double CoverageRateChange,
[property: JsonPropertyName("attestation_count_change")] int AttestationCountChange,
[property: JsonPropertyName("trend_direction")] string TrendDirection);
/// <summary>
/// Dashboard issue for Console.
/// </summary>
internal sealed record ConsoleDashboardIssue(
[property: JsonPropertyName("issue")] string Issue,
[property: JsonPropertyName("count")] int Count,
[property: JsonPropertyName("severity")] string Severity);
/// <summary>
/// Dashboard policy compliance for Console.
/// </summary>
internal sealed record ConsoleDashboardPolicyCompliance(
[property: JsonPropertyName("policy_id")] string PolicyId,
[property: JsonPropertyName("policy_version")] string PolicyVersion,
[property: JsonPropertyName("compliance_rate")] double ComplianceRate,
[property: JsonPropertyName("artifacts_evaluated")] int ArtifactsEvaluated);

View File

@@ -0,0 +1,470 @@
using System.Collections.Immutable;
using StellaOps.Policy.Engine.Attestation;
namespace StellaOps.Policy.Engine.ConsoleSurface;
/// <summary>
/// Service for Console attestation report integration per CONTRACT-VERIFICATION-POLICY-006.
/// </summary>
internal sealed class ConsoleAttestationReportService
{
private const string SchemaVersion = "1.0.0";
private readonly IAttestationReportService _reportService;
private readonly IVerificationPolicyStore _policyStore;
private readonly TimeProvider _timeProvider;
public ConsoleAttestationReportService(
IAttestationReportService reportService,
IVerificationPolicyStore policyStore,
TimeProvider timeProvider)
{
_reportService = reportService ?? throw new ArgumentNullException(nameof(reportService));
_policyStore = policyStore ?? throw new ArgumentNullException(nameof(policyStore));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
public async Task<ConsoleAttestationReportResponse> QueryReportsAsync(
ConsoleAttestationReportRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var now = _timeProvider.GetUtcNow();
// Convert Console request to internal query
var query = new AttestationReportQuery(
ArtifactDigests: request.ArtifactDigests,
ArtifactUriPattern: request.ArtifactUriPattern,
PolicyIds: request.PolicyIds,
PredicateTypes: request.PredicateTypes,
StatusFilter: ParseStatusFilter(request.StatusFilter),
FromTime: request.FromTime,
ToTime: request.ToTime,
IncludeDetails: true,
Limit: request.PageSize,
Offset: (request.Page - 1) * request.PageSize);
// Get reports
var response = await _reportService.ListReportsAsync(query, cancellationToken).ConfigureAwait(false);
// Get statistics for summary
var statistics = await _reportService.GetStatisticsAsync(query, cancellationToken).ConfigureAwait(false);
// Convert to Console format
var consoleReports = response.Reports.Select(r => ToConsoleReport(r, now)).ToList();
// Calculate groups if requested
IReadOnlyList<ConsoleReportGroup>? groups = null;
if (request.GroupBy.HasValue && request.GroupBy.Value != ConsoleReportGroupBy.None)
{
groups = CalculateGroups(response.Reports, request.GroupBy.Value);
}
// Calculate pagination
var totalPages = (int)Math.Ceiling((double)response.Total / request.PageSize);
var pagination = new ConsolePagination(
Page: request.Page,
PageSize: request.PageSize,
TotalPages: totalPages,
TotalItems: response.Total,
HasNext: request.Page < totalPages,
HasPrevious: request.Page > 1);
// Create summary
var summary = new ConsoleReportSummary(
TotalArtifacts: statistics.TotalArtifacts,
TotalAttestations: statistics.TotalAttestations,
StatusBreakdown: statistics.StatusDistribution
.ToImmutableDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value),
CoverageRate: Math.Round(statistics.CoverageRate, 2),
ComplianceRate: CalculateComplianceRate(response.Reports),
AverageAgeHours: Math.Round(statistics.AverageAgeSeconds / 3600, 2));
return new ConsoleAttestationReportResponse(
SchemaVersion: SchemaVersion,
Summary: summary,
Reports: consoleReports,
Groups: groups,
Pagination: pagination,
FiltersApplied: new ConsoleFiltersApplied(
ArtifactCount: request.ArtifactDigests?.Count ?? 0,
PolicyIds: request.PolicyIds,
PredicateTypes: request.PredicateTypes,
StatusFilter: request.StatusFilter,
TimeRange: request.FromTime.HasValue || request.ToTime.HasValue
? new ConsoleTimeRange(request.FromTime, request.ToTime)
: null));
}
public async Task<ConsoleAttestationDashboardResponse> GetDashboardAsync(
ConsoleAttestationDashboardRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var now = _timeProvider.GetUtcNow();
var (fromTime, toTime) = ParseTimeRange(request.TimeRange, now);
var query = new AttestationReportQuery(
ArtifactDigests: null,
ArtifactUriPattern: request.ArtifactUriPattern,
PolicyIds: request.PolicyIds,
PredicateTypes: null,
StatusFilter: null,
FromTime: fromTime,
ToTime: toTime,
IncludeDetails: false,
Limit: int.MaxValue,
Offset: 0);
var statistics = await _reportService.GetStatisticsAsync(query, cancellationToken).ConfigureAwait(false);
var reports = await _reportService.ListReportsAsync(query, cancellationToken).ConfigureAwait(false);
// Calculate pass rate
var passCount = statistics.StatusDistribution.GetValueOrDefault(AttestationReportStatus.Pass, 0);
var failCount = statistics.StatusDistribution.GetValueOrDefault(AttestationReportStatus.Fail, 0);
var warnCount = statistics.StatusDistribution.GetValueOrDefault(AttestationReportStatus.Warn, 0);
var total = passCount + failCount + warnCount;
var passRate = total > 0 ? (double)passCount / total * 100 : 0;
// Calculate overview
var overview = new ConsoleDashboardOverview(
TotalArtifacts: statistics.TotalArtifacts,
TotalAttestations: statistics.TotalAttestations,
PassRate: Math.Round(passRate, 2),
CoverageRate: Math.Round(statistics.CoverageRate, 2),
AverageFreshnessHours: Math.Round(statistics.AverageAgeSeconds / 3600, 2));
// Calculate trends (simplified - would normally compare to previous period)
var trends = new ConsoleDashboardTrends(
PassRateChange: 0,
CoverageRateChange: 0,
AttestationCountChange: 0,
TrendDirection: "stable");
// Get top issues
var topIssues = reports.Reports
.SelectMany(r => r.VerificationResults)
.SelectMany(v => v.Issues)
.GroupBy(i => i)
.OrderByDescending(g => g.Count())
.Take(5)
.Select(g => new ConsoleDashboardIssue(
Issue: g.Key,
Count: g.Count(),
Severity: "error"))
.ToList();
// Get policy compliance
var policyCompliance = await CalculatePolicyComplianceAsync(reports.Reports, cancellationToken).ConfigureAwait(false);
return new ConsoleAttestationDashboardResponse(
SchemaVersion: SchemaVersion,
Overview: overview,
Trends: trends,
TopIssues: topIssues,
PolicyCompliance: policyCompliance,
EvaluatedAt: now);
}
private ConsoleArtifactReport ToConsoleReport(ArtifactAttestationReport report, DateTimeOffset now)
{
var age = now - report.EvaluatedAt;
var ageRelative = FormatRelativeTime(age);
return new ConsoleArtifactReport(
ArtifactDigest: report.ArtifactDigest,
ArtifactUri: report.ArtifactUri,
ArtifactShortDigest: report.ArtifactDigest.Length > 12
? report.ArtifactDigest[..12]
: report.ArtifactDigest,
Status: report.OverallStatus.ToString().ToLowerInvariant(),
StatusLabel: GetStatusLabel(report.OverallStatus),
StatusIcon: GetStatusIcon(report.OverallStatus),
AttestationCount: report.AttestationCount,
CoveragePercentage: report.Coverage.CoveragePercentage,
PoliciesPassed: report.PolicyCompliance.PoliciesPassed,
PoliciesFailed: report.PolicyCompliance.PoliciesFailed,
EvaluatedAt: report.EvaluatedAt,
EvaluatedAtRelative: ageRelative,
Details: ToConsoleDetails(report));
}
private static ConsoleReportDetails ToConsoleDetails(ArtifactAttestationReport report)
{
var predicateTypes = report.VerificationResults
.GroupBy(v => v.PredicateType)
.Select(g => new ConsolePredicateTypeStatus(
Type: g.Key,
TypeLabel: GetPredicateTypeLabel(g.Key),
Status: g.First().Status.ToString().ToLowerInvariant(),
StatusLabel: GetStatusLabel(g.First().Status),
Freshness: FormatFreshness(g.First().FreshnessStatus)))
.ToList();
var policies = report.PolicyCompliance.PolicyResults
.Select(p => new ConsolePolicyStatus(
PolicyId: p.PolicyId,
PolicyVersion: p.PolicyVersion,
Status: p.Status.ToString().ToLowerInvariant(),
StatusLabel: GetStatusLabel(p.Status),
Verdict: p.Verdict))
.ToList();
var signers = report.VerificationResults
.SelectMany(v => v.SignatureStatus.Signers)
.DistinctBy(s => s.KeyFingerprint)
.Select(s => new ConsoleSignerInfo(
KeyFingerprintShort: s.KeyFingerprint.Length > 8
? s.KeyFingerprint[..8]
: s.KeyFingerprint,
Issuer: s.Issuer,
Subject: s.Subject,
Algorithm: s.Algorithm,
Verified: s.Verified,
Trusted: s.Trusted))
.ToList();
var issues = report.VerificationResults
.SelectMany(v => v.Issues)
.Distinct()
.Select(i => new ConsoleIssue(
Severity: "error",
Message: i,
Field: null))
.ToList();
return new ConsoleReportDetails(
PredicateTypes: predicateTypes,
Policies: policies,
Signers: signers,
Issues: issues);
}
private static IReadOnlyList<ConsoleReportGroup> CalculateGroups(
IReadOnlyList<ArtifactAttestationReport> reports,
ConsoleReportGroupBy groupBy)
{
return groupBy switch
{
ConsoleReportGroupBy.Policy => GroupByPolicy(reports),
ConsoleReportGroupBy.PredicateType => GroupByPredicateType(reports),
ConsoleReportGroupBy.Status => GroupByStatus(reports),
ConsoleReportGroupBy.ArtifactUri => GroupByArtifactUri(reports),
_ => []
};
}
private static IReadOnlyList<ConsoleReportGroup> GroupByPolicy(IReadOnlyList<ArtifactAttestationReport> reports)
{
return reports
.SelectMany(r => r.PolicyCompliance.PolicyResults)
.GroupBy(p => p.PolicyId)
.Select(g => new ConsoleReportGroup(
Key: g.Key,
Label: g.Key,
Count: g.Count(),
StatusBreakdown: g.GroupBy(p => p.Status.ToString())
.ToImmutableDictionary(s => s.Key, s => s.Count())))
.ToList();
}
private static IReadOnlyList<ConsoleReportGroup> GroupByPredicateType(IReadOnlyList<ArtifactAttestationReport> reports)
{
return reports
.SelectMany(r => r.VerificationResults)
.GroupBy(v => v.PredicateType)
.Select(g => new ConsoleReportGroup(
Key: g.Key,
Label: GetPredicateTypeLabel(g.Key),
Count: g.Count(),
StatusBreakdown: g.GroupBy(v => v.Status.ToString())
.ToImmutableDictionary(s => s.Key, s => s.Count())))
.ToList();
}
private static IReadOnlyList<ConsoleReportGroup> GroupByStatus(IReadOnlyList<ArtifactAttestationReport> reports)
{
return reports
.GroupBy(r => r.OverallStatus)
.Select(g => new ConsoleReportGroup(
Key: g.Key.ToString(),
Label: GetStatusLabel(g.Key),
Count: g.Count(),
StatusBreakdown: ImmutableDictionary<string, int>.Empty.Add(g.Key.ToString(), g.Count())))
.ToList();
}
private static IReadOnlyList<ConsoleReportGroup> GroupByArtifactUri(IReadOnlyList<ArtifactAttestationReport> reports)
{
return reports
.Where(r => !string.IsNullOrWhiteSpace(r.ArtifactUri))
.GroupBy(r => ExtractRepository(r.ArtifactUri!))
.Select(g => new ConsoleReportGroup(
Key: g.Key,
Label: g.Key,
Count: g.Count(),
StatusBreakdown: g.GroupBy(r => r.OverallStatus.ToString())
.ToImmutableDictionary(s => s.Key, s => s.Count())))
.ToList();
}
private async Task<IReadOnlyList<ConsoleDashboardPolicyCompliance>> CalculatePolicyComplianceAsync(
IReadOnlyList<ArtifactAttestationReport> reports,
CancellationToken cancellationToken)
{
var policyResults = reports
.SelectMany(r => r.PolicyCompliance.PolicyResults)
.GroupBy(p => p.PolicyId)
.Select(g =>
{
var total = g.Count();
var passed = g.Count(p => p.Status == AttestationReportStatus.Pass);
var complianceRate = total > 0 ? (double)passed / total * 100 : 0;
return new ConsoleDashboardPolicyCompliance(
PolicyId: g.Key,
PolicyVersion: g.First().PolicyVersion,
ComplianceRate: Math.Round(complianceRate, 2),
ArtifactsEvaluated: total);
})
.OrderByDescending(p => p.ArtifactsEvaluated)
.Take(10)
.ToList();
return policyResults;
}
private static IReadOnlyList<AttestationReportStatus>? ParseStatusFilter(IReadOnlyList<string>? statusFilter)
{
if (statusFilter == null || statusFilter.Count == 0)
{
return null;
}
return statusFilter
.Select(s => Enum.TryParse<AttestationReportStatus>(s, true, out var status) ? status : (AttestationReportStatus?)null)
.Where(s => s.HasValue)
.Select(s => s!.Value)
.ToList();
}
private static (DateTimeOffset? from, DateTimeOffset? to) ParseTimeRange(string? timeRange, DateTimeOffset now)
{
return timeRange?.ToLowerInvariant() switch
{
"1h" => (now.AddHours(-1), now),
"24h" => (now.AddDays(-1), now),
"7d" => (now.AddDays(-7), now),
"30d" => (now.AddDays(-30), now),
"90d" => (now.AddDays(-90), now),
_ => (null, null)
};
}
private static double CalculateComplianceRate(IReadOnlyList<ArtifactAttestationReport> reports)
{
if (reports.Count == 0)
{
return 0;
}
var compliant = reports.Count(r =>
r.OverallStatus == AttestationReportStatus.Pass ||
r.OverallStatus == AttestationReportStatus.Warn);
return Math.Round((double)compliant / reports.Count * 100, 2);
}
private static string GetStatusLabel(AttestationReportStatus status)
{
return status switch
{
AttestationReportStatus.Pass => "Passed",
AttestationReportStatus.Fail => "Failed",
AttestationReportStatus.Warn => "Warning",
AttestationReportStatus.Skipped => "Skipped",
AttestationReportStatus.Pending => "Pending",
_ => "Unknown"
};
}
private static string GetStatusIcon(AttestationReportStatus status)
{
return status switch
{
AttestationReportStatus.Pass => "check-circle",
AttestationReportStatus.Fail => "x-circle",
AttestationReportStatus.Warn => "alert-triangle",
AttestationReportStatus.Skipped => "minus-circle",
AttestationReportStatus.Pending => "clock",
_ => "help-circle"
};
}
private static string GetPredicateTypeLabel(string predicateType)
{
return predicateType switch
{
PredicateTypes.SbomV1 => "SBOM",
PredicateTypes.VexV1 => "VEX",
PredicateTypes.VexDecisionV1 => "VEX Decision",
PredicateTypes.PolicyV1 => "Policy",
PredicateTypes.PromotionV1 => "Promotion",
PredicateTypes.EvidenceV1 => "Evidence",
PredicateTypes.GraphV1 => "Graph",
PredicateTypes.ReplayV1 => "Replay",
PredicateTypes.SlsaProvenanceV1 => "SLSA v1",
PredicateTypes.SlsaProvenanceV02 => "SLSA v0.2",
PredicateTypes.CycloneDxBom => "CycloneDX",
PredicateTypes.SpdxDocument => "SPDX",
PredicateTypes.OpenVex => "OpenVEX",
_ => predicateType
};
}
private static string FormatFreshness(FreshnessVerificationStatus freshness)
{
return freshness.IsFresh ? "Fresh" : $"{freshness.AgeSeconds / 3600}h old";
}
private static string FormatRelativeTime(TimeSpan age)
{
if (age.TotalMinutes < 1)
{
return "just now";
}
if (age.TotalHours < 1)
{
return $"{(int)age.TotalMinutes}m ago";
}
if (age.TotalDays < 1)
{
return $"{(int)age.TotalHours}h ago";
}
if (age.TotalDays < 7)
{
return $"{(int)age.TotalDays}d ago";
}
return $"{(int)(age.TotalDays / 7)}w ago";
}
private static string ExtractRepository(string artifactUri)
{
try
{
var uri = new Uri(artifactUri);
var path = uri.AbsolutePath.Split('/');
return path.Length >= 2 ? path[1] : uri.Host;
}
catch
{
return artifactUri;
}
}
}