feat: Add CVSS receipt management endpoints and related functionality
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

- Introduced new API endpoints for creating, retrieving, amending, and listing CVSS receipts.
- Updated IPolicyEngineClient interface to include methods for CVSS receipt operations.
- Implemented PolicyEngineClient to handle CVSS receipt requests.
- Enhanced Program.cs to map new CVSS receipt routes with appropriate authorization.
- Added necessary models and contracts for CVSS receipt requests and responses.
- Integrated Postgres document store for managing CVSS receipts and related data.
- Updated database schema with new migrations for source documents and payload storage.
- Refactored existing components to support new CVSS functionality.
This commit is contained in:
StellaOps Bot
2025-12-07 00:43:14 +02:00
parent 0de92144d2
commit 53889d85e7
67 changed files with 17207 additions and 16293 deletions

View File

@@ -1,15 +1,27 @@
using StellaOps.Policy.Gateway.Contracts;
using StellaOps.Policy.Gateway.Infrastructure;
namespace StellaOps.Policy.Gateway.Clients;
internal interface IPolicyEngineClient
{
using StellaOps.Policy.Gateway.Contracts;
using StellaOps.Policy.Gateway.Infrastructure;
using StellaOps.Policy.Scoring;
using StellaOps.Policy.Scoring.Receipts;
namespace StellaOps.Policy.Gateway.Clients;
internal interface IPolicyEngineClient
{
Task<PolicyEngineResponse<IReadOnlyList<PolicyPackSummaryDto>>> ListPolicyPacksAsync(GatewayForwardingContext? forwardingContext, CancellationToken cancellationToken);
Task<PolicyEngineResponse<PolicyPackDto>> CreatePolicyPackAsync(GatewayForwardingContext? forwardingContext, CreatePolicyPackRequest request, CancellationToken cancellationToken);
Task<PolicyEngineResponse<PolicyRevisionDto>> CreatePolicyRevisionAsync(GatewayForwardingContext? forwardingContext, string packId, CreatePolicyRevisionRequest request, CancellationToken cancellationToken);
Task<PolicyEngineResponse<PolicyRevisionActivationDto>> ActivatePolicyRevisionAsync(GatewayForwardingContext? forwardingContext, string packId, int version, ActivatePolicyRevisionRequest request, CancellationToken cancellationToken);
}
Task<PolicyEngineResponse<PolicyPackDto>> CreatePolicyPackAsync(GatewayForwardingContext? forwardingContext, CreatePolicyPackRequest request, CancellationToken cancellationToken);
Task<PolicyEngineResponse<PolicyRevisionDto>> CreatePolicyRevisionAsync(GatewayForwardingContext? forwardingContext, string packId, CreatePolicyRevisionRequest request, CancellationToken cancellationToken);
Task<PolicyEngineResponse<PolicyRevisionActivationDto>> ActivatePolicyRevisionAsync(GatewayForwardingContext? forwardingContext, string packId, int version, ActivatePolicyRevisionRequest request, CancellationToken cancellationToken);
Task<PolicyEngineResponse<CvssScoreReceipt>> CreateCvssReceiptAsync(GatewayForwardingContext? forwardingContext, CreateCvssReceiptRequest request, CancellationToken cancellationToken);
Task<PolicyEngineResponse<CvssScoreReceipt>> GetCvssReceiptAsync(GatewayForwardingContext? forwardingContext, string receiptId, CancellationToken cancellationToken);
Task<PolicyEngineResponse<CvssScoreReceipt>> AmendCvssReceiptAsync(GatewayForwardingContext? forwardingContext, string receiptId, AmendCvssReceiptRequest request, CancellationToken cancellationToken);
Task<PolicyEngineResponse<IReadOnlyList<ReceiptHistoryEntry>>> GetCvssReceiptHistoryAsync(GatewayForwardingContext? forwardingContext, string receiptId, CancellationToken cancellationToken);
Task<PolicyEngineResponse<IReadOnlyList<CvssPolicy>>> ListCvssPoliciesAsync(GatewayForwardingContext? forwardingContext, CancellationToken cancellationToken);
}

View File

@@ -5,13 +5,15 @@ using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Gateway.Contracts;
using StellaOps.Policy.Gateway.Infrastructure;
using StellaOps.Policy.Gateway.Options;
using StellaOps.Policy.Gateway.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Gateway.Contracts;
using StellaOps.Policy.Gateway.Infrastructure;
using StellaOps.Policy.Gateway.Options;
using StellaOps.Policy.Gateway.Services;
using StellaOps.Policy.Scoring;
using StellaOps.Policy.Scoring.Receipts;
namespace StellaOps.Policy.Gateway.Clients;
@@ -85,18 +87,73 @@ internal sealed class PolicyEngineClient : IPolicyEngineClient
request,
cancellationToken);
public Task<PolicyEngineResponse<PolicyRevisionActivationDto>> ActivatePolicyRevisionAsync(
GatewayForwardingContext? forwardingContext,
string packId,
int version,
ActivatePolicyRevisionRequest request,
CancellationToken cancellationToken)
=> SendAsync<PolicyRevisionActivationDto>(
HttpMethod.Post,
$"api/policy/packs/{Uri.EscapeDataString(packId)}/revisions/{version}:activate",
forwardingContext,
request,
cancellationToken);
public Task<PolicyEngineResponse<PolicyRevisionActivationDto>> ActivatePolicyRevisionAsync(
GatewayForwardingContext? forwardingContext,
string packId,
int version,
ActivatePolicyRevisionRequest request,
CancellationToken cancellationToken)
=> SendAsync<PolicyRevisionActivationDto>(
HttpMethod.Post,
$"api/policy/packs/{Uri.EscapeDataString(packId)}/revisions/{version}:activate",
forwardingContext,
request,
cancellationToken);
public Task<PolicyEngineResponse<CvssScoreReceipt>> CreateCvssReceiptAsync(
GatewayForwardingContext? forwardingContext,
CreateCvssReceiptRequest request,
CancellationToken cancellationToken)
=> SendAsync<CvssScoreReceipt>(
HttpMethod.Post,
"api/cvss/receipts",
forwardingContext,
request,
cancellationToken);
public Task<PolicyEngineResponse<CvssScoreReceipt>> GetCvssReceiptAsync(
GatewayForwardingContext? forwardingContext,
string receiptId,
CancellationToken cancellationToken)
=> SendAsync<CvssScoreReceipt>(
HttpMethod.Get,
$"api/cvss/receipts/{Uri.EscapeDataString(receiptId)}",
forwardingContext,
content: null,
cancellationToken);
public Task<PolicyEngineResponse<CvssScoreReceipt>> AmendCvssReceiptAsync(
GatewayForwardingContext? forwardingContext,
string receiptId,
AmendCvssReceiptRequest request,
CancellationToken cancellationToken)
=> SendAsync<CvssScoreReceipt>(
HttpMethod.Put,
$"api/cvss/receipts/{Uri.EscapeDataString(receiptId)}/amend",
forwardingContext,
request,
cancellationToken);
public Task<PolicyEngineResponse<IReadOnlyList<ReceiptHistoryEntry>>> GetCvssReceiptHistoryAsync(
GatewayForwardingContext? forwardingContext,
string receiptId,
CancellationToken cancellationToken)
=> SendAsync<IReadOnlyList<ReceiptHistoryEntry>>(
HttpMethod.Get,
$"api/cvss/receipts/{Uri.EscapeDataString(receiptId)}/history",
forwardingContext,
content: null,
cancellationToken);
public Task<PolicyEngineResponse<IReadOnlyList<CvssPolicy>>> ListCvssPoliciesAsync(
GatewayForwardingContext? forwardingContext,
CancellationToken cancellationToken)
=> SendAsync<IReadOnlyList<CvssPolicy>>(
HttpMethod.Get,
"api/cvss/policies",
forwardingContext,
content: null,
cancellationToken);
private async Task<PolicyEngineResponse<TSuccess>> SendAsync<TSuccess>(
HttpMethod method,

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using StellaOps.Attestor.Envelope;
using StellaOps.Policy.Scoring;
using StellaOps.Policy.Scoring.Receipts;
namespace StellaOps.Policy.Gateway.Contracts;
public sealed record CreateCvssReceiptRequest(
[Required] string VulnerabilityId,
[Required] CvssPolicy Policy,
[Required] CvssBaseMetrics BaseMetrics,
CvssThreatMetrics? ThreatMetrics,
CvssEnvironmentalMetrics? EnvironmentalMetrics,
CvssSupplementalMetrics? SupplementalMetrics,
IReadOnlyList<CvssEvidenceItem>? Evidence,
EnvelopeKey? SigningKey,
string? CreatedBy,
DateTimeOffset? CreatedAt);
public sealed record AmendCvssReceiptRequest(
[Required] string Field,
string? PreviousValue,
string? NewValue,
[Required] string Reason,
string? ReferenceUri,
EnvelopeKey? SigningKey,
string? Actor);
public sealed record CvssReceiptHistoryResponse(
string ReceiptId,
IReadOnlyList<ReceiptHistoryEntry> History);

View File

@@ -279,11 +279,11 @@ policyPacks.MapPost("/{packId}/revisions", async Task<IResult> (
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyAuthor));
policyPacks.MapPost("/{packId}/revisions/{version:int}:activate", async Task<IResult> (
HttpContext context,
string packId,
int version,
ActivatePolicyRevisionRequest request,
policyPacks.MapPost("/{packId}/revisions/{version:int}:activate", async Task<IResult> (
HttpContext context,
string packId,
int version,
ActivatePolicyRevisionRequest request,
IPolicyEngineClient client,
PolicyEngineTokenProvider tokenProvider,
PolicyGatewayMetrics metrics,
@@ -330,13 +330,144 @@ policyPacks.MapPost("/{packId}/revisions/{version:int}:activate", async Task<IRe
var logger = loggerFactory.CreateLogger("StellaOps.Policy.Gateway.Activation");
LogActivation(logger, packId, version, outcome, source, response.StatusCode);
return response.ToMinimalResult();
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(
StellaOpsScopes.PolicyOperate,
StellaOpsScopes.PolicyActivate));
app.Run();
return response.ToMinimalResult();
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(
StellaOpsScopes.PolicyOperate,
StellaOpsScopes.PolicyActivate));
var cvss = app.MapGroup("/api/cvss")
.WithTags("CVSS Receipts");
cvss.MapPost("/receipts", async Task<IResult>(
HttpContext context,
CreateCvssReceiptRequest request,
IPolicyEngineClient client,
PolicyEngineTokenProvider tokenProvider,
CancellationToken cancellationToken) =>
{
if (request is null)
{
return Results.BadRequest(new ProblemDetails
{
Title = "Request body required.",
Status = StatusCodes.Status400BadRequest
});
}
GatewayForwardingContext? forwardingContext = null;
if (GatewayForwardingContext.TryCreate(context, out var callerContext))
{
forwardingContext = callerContext;
}
else if (!tokenProvider.IsEnabled)
{
return Results.Unauthorized();
}
var response = await client.CreateCvssReceiptAsync(forwardingContext, request, cancellationToken).ConfigureAwait(false);
return response.ToMinimalResult();
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyRun));
cvss.MapGet("/receipts/{receiptId}", async Task<IResult>(
HttpContext context,
string receiptId,
IPolicyEngineClient client,
PolicyEngineTokenProvider tokenProvider,
CancellationToken cancellationToken) =>
{
GatewayForwardingContext? forwardingContext = null;
if (GatewayForwardingContext.TryCreate(context, out var callerContext))
{
forwardingContext = callerContext;
}
else if (!tokenProvider.IsEnabled)
{
return Results.Unauthorized();
}
var response = await client.GetCvssReceiptAsync(forwardingContext, receiptId, cancellationToken).ConfigureAwait(false);
return response.ToMinimalResult();
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.FindingsRead));
cvss.MapPut("/receipts/{receiptId}/amend", async Task<IResult>(
HttpContext context,
string receiptId,
AmendCvssReceiptRequest request,
IPolicyEngineClient client,
PolicyEngineTokenProvider tokenProvider,
CancellationToken cancellationToken) =>
{
if (request is null)
{
return Results.BadRequest(new ProblemDetails
{
Title = "Request body required.",
Status = StatusCodes.Status400BadRequest
});
}
GatewayForwardingContext? forwardingContext = null;
if (GatewayForwardingContext.TryCreate(context, out var callerContext))
{
forwardingContext = callerContext;
}
else if (!tokenProvider.IsEnabled)
{
return Results.Unauthorized();
}
var response = await client.AmendCvssReceiptAsync(forwardingContext, receiptId, request, cancellationToken).ConfigureAwait(false);
return response.ToMinimalResult();
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyRun));
cvss.MapGet("/receipts/{receiptId}/history", async Task<IResult>(
HttpContext context,
string receiptId,
IPolicyEngineClient client,
PolicyEngineTokenProvider tokenProvider,
CancellationToken cancellationToken) =>
{
GatewayForwardingContext? forwardingContext = null;
if (GatewayForwardingContext.TryCreate(context, out var callerContext))
{
forwardingContext = callerContext;
}
else if (!tokenProvider.IsEnabled)
{
return Results.Unauthorized();
}
var response = await client.GetCvssReceiptHistoryAsync(forwardingContext, receiptId, cancellationToken).ConfigureAwait(false);
return response.ToMinimalResult();
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.FindingsRead));
cvss.MapGet("/policies", async Task<IResult>(
HttpContext context,
IPolicyEngineClient client,
PolicyEngineTokenProvider tokenProvider,
CancellationToken cancellationToken) =>
{
GatewayForwardingContext? forwardingContext = null;
if (GatewayForwardingContext.TryCreate(context, out var callerContext))
{
forwardingContext = callerContext;
}
else if (!tokenProvider.IsEnabled)
{
return Results.Unauthorized();
}
var response = await client.ListCvssPoliciesAsync(forwardingContext, cancellationToken).ConfigureAwait(false);
return response.ToMinimalResult();
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.FindingsRead));
app.Run();
static IAsyncPolicy<HttpResponseMessage> CreateAuthorityRetryPolicy(IServiceProvider provider)
{

View File

@@ -16,6 +16,7 @@
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj" />
<ProjectReference Include="../../AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.csproj" />
<ProjectReference Include="../StellaOps.Policy.Scoring/StellaOps.Policy.Scoring.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0" />