feat: Add CVSS receipt management endpoints and related functionality
- 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:
@@ -0,0 +1,327 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Attestor.Envelope;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.Policy.Scoring;
|
||||
using StellaOps.Policy.Scoring.Engine;
|
||||
using StellaOps.Policy.Scoring.Receipts;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal API surface for CVSS v4.0 score receipts (create, read, amend, history).
|
||||
/// </summary>
|
||||
internal static class CvssReceiptEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapCvssReceipts(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/cvss")
|
||||
.RequireAuthorization()
|
||||
.WithTags("CVSS Receipts");
|
||||
|
||||
group.MapPost("/receipts", CreateReceipt)
|
||||
.WithName("CreateCvssReceipt")
|
||||
.WithSummary("Create a CVSS v4.0 receipt with deterministic hashing and optional DSSE attestation.")
|
||||
.Produces<CvssScoreReceipt>(StatusCodes.Status201Created)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status401Unauthorized);
|
||||
|
||||
group.MapGet("/receipts/{receiptId}", GetReceipt)
|
||||
.WithName("GetCvssReceipt")
|
||||
.WithSummary("Retrieve a CVSS v4.0 receipt by ID.")
|
||||
.Produces<CvssScoreReceipt>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapPut("/receipts/{receiptId}/amend", AmendReceipt)
|
||||
.WithName("AmendCvssReceipt")
|
||||
.WithSummary("Append an amendment entry to a CVSS receipt history and optionally re-sign.")
|
||||
.Produces<CvssScoreReceipt>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/receipts/{receiptId}/history", GetReceiptHistory)
|
||||
.WithName("GetCvssReceiptHistory")
|
||||
.WithSummary("Return the ordered amendment history for a CVSS receipt.")
|
||||
.Produces<IReadOnlyList<ReceiptHistoryEntry>>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/policies", ListPolicies)
|
||||
.WithName("ListCvssPolicies")
|
||||
.WithSummary("List available CVSS policies configured on this host.")
|
||||
.Produces<IReadOnlyList<CvssPolicy>>(StatusCodes.Status200OK);
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private static async Task<IResult> CreateReceipt(
|
||||
HttpContext context,
|
||||
[FromBody] CreateCvssReceiptRequest request,
|
||||
IReceiptBuilder receiptBuilder,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRun);
|
||||
if (scopeResult is not null)
|
||||
{
|
||||
return scopeResult;
|
||||
}
|
||||
|
||||
if (request is null)
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Request body required.",
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
|
||||
if (request.Policy is null || string.IsNullOrWhiteSpace(request.Policy.Hash))
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Policy hash required",
|
||||
Detail = "CvssPolicy with a deterministic hash must be supplied.",
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(context);
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Tenant required",
|
||||
Detail = "Specify tenant via X-Tenant-Id header or tenant_id claim.",
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
|
||||
var actor = ResolveActorId(context) ?? request.CreatedBy ?? "system";
|
||||
var createdAt = request.CreatedAt ?? DateTimeOffset.UtcNow;
|
||||
|
||||
var createRequest = new CreateReceiptRequest
|
||||
{
|
||||
TenantId = tenantId,
|
||||
VulnerabilityId = request.VulnerabilityId,
|
||||
CreatedBy = actor,
|
||||
CreatedAt = createdAt,
|
||||
Policy = request.Policy,
|
||||
BaseMetrics = request.BaseMetrics,
|
||||
ThreatMetrics = request.ThreatMetrics,
|
||||
EnvironmentalMetrics = request.EnvironmentalMetrics ?? request.Policy.DefaultEnvironmentalMetrics,
|
||||
SupplementalMetrics = request.SupplementalMetrics,
|
||||
Evidence = request.Evidence?.ToImmutableList() ?? ImmutableList<CvssEvidenceItem>.Empty,
|
||||
SigningKey = request.SigningKey
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var receipt = await receiptBuilder.CreateAsync(createRequest, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Created($"/api/cvss/receipts/{receipt.ReceiptId}", receipt);
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidOperationException or ArgumentException)
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Failed to create CVSS receipt",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetReceipt(
|
||||
HttpContext context,
|
||||
[FromRoute] string receiptId,
|
||||
IReceiptRepository repository,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.FindingsRead);
|
||||
if (scopeResult is not null)
|
||||
{
|
||||
return scopeResult;
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(context);
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Tenant required",
|
||||
Detail = "Specify tenant via X-Tenant-Id header or tenant_id claim.",
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
|
||||
var receipt = await repository.GetAsync(tenantId, receiptId, cancellationToken).ConfigureAwait(false);
|
||||
if (receipt is null)
|
||||
{
|
||||
return Results.NotFound(new ProblemDetails
|
||||
{
|
||||
Title = "Receipt not found",
|
||||
Detail = $"CVSS receipt '{receiptId}' was not found.",
|
||||
Status = StatusCodes.Status404NotFound
|
||||
});
|
||||
}
|
||||
|
||||
return Results.Ok(receipt);
|
||||
}
|
||||
|
||||
private static async Task<IResult> AmendReceipt(
|
||||
HttpContext context,
|
||||
[FromRoute] string receiptId,
|
||||
[FromBody] AmendCvssReceiptRequest request,
|
||||
IReceiptHistoryService historyService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRun);
|
||||
if (scopeResult is not null)
|
||||
{
|
||||
return scopeResult;
|
||||
}
|
||||
|
||||
if (request is null)
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Request body required.",
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(context);
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Tenant required",
|
||||
Detail = "Specify tenant via X-Tenant-Id header or tenant_id claim.",
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
|
||||
var actor = ResolveActorId(context) ?? request.Actor ?? "system";
|
||||
|
||||
var amend = new AmendReceiptRequest
|
||||
{
|
||||
ReceiptId = receiptId,
|
||||
TenantId = tenantId,
|
||||
Actor = actor,
|
||||
Field = request.Field,
|
||||
PreviousValue = request.PreviousValue,
|
||||
NewValue = request.NewValue,
|
||||
Reason = request.Reason,
|
||||
ReferenceUri = request.ReferenceUri,
|
||||
SigningKey = request.SigningKey
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var amended = await historyService.AmendAsync(amend, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(amended);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.NotFound(new ProblemDetails
|
||||
{
|
||||
Title = "Receipt not found",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status404NotFound
|
||||
});
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException)
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Failed to amend receipt",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetReceiptHistory(
|
||||
HttpContext context,
|
||||
[FromRoute] string receiptId,
|
||||
IReceiptRepository repository,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.FindingsRead);
|
||||
if (scopeResult is not null)
|
||||
{
|
||||
return scopeResult;
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(context);
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return Results.BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "Tenant required",
|
||||
Detail = "Specify tenant via X-Tenant-Id header or tenant_id claim.",
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
}
|
||||
|
||||
var receipt = await repository.GetAsync(tenantId, receiptId, cancellationToken).ConfigureAwait(false);
|
||||
if (receipt is null)
|
||||
{
|
||||
return Results.NotFound(new ProblemDetails
|
||||
{
|
||||
Title = "Receipt not found",
|
||||
Detail = $"CVSS receipt '{receiptId}' was not found.",
|
||||
Status = StatusCodes.Status404NotFound
|
||||
});
|
||||
}
|
||||
|
||||
var orderedHistory = receipt.History
|
||||
.OrderBy(h => h.Timestamp)
|
||||
.ToList();
|
||||
|
||||
return Results.Ok(orderedHistory);
|
||||
}
|
||||
|
||||
private static IResult ListPolicies()
|
||||
=> Results.Ok(Array.Empty<CvssPolicy>());
|
||||
|
||||
private static string? ResolveTenantId(HttpContext context)
|
||||
{
|
||||
if (context.Request.Headers.TryGetValue("X-Tenant-Id", out var tenantHeader) &&
|
||||
!string.IsNullOrWhiteSpace(tenantHeader))
|
||||
{
|
||||
return tenantHeader.ToString();
|
||||
}
|
||||
|
||||
return context.User?.FindFirst("tenant_id")?.Value;
|
||||
}
|
||||
|
||||
private static string? ResolveActorId(HttpContext context)
|
||||
{
|
||||
var user = context.User;
|
||||
return user?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value
|
||||
?? user?.FindFirst("sub")?.Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record CreateCvssReceiptRequest(
|
||||
string VulnerabilityId,
|
||||
CvssPolicy Policy,
|
||||
CvssBaseMetrics BaseMetrics,
|
||||
CvssThreatMetrics? ThreatMetrics,
|
||||
CvssEnvironmentalMetrics? EnvironmentalMetrics,
|
||||
CvssSupplementalMetrics? SupplementalMetrics,
|
||||
IReadOnlyList<CvssEvidenceItem>? Evidence,
|
||||
EnvelopeKey? SigningKey,
|
||||
string? CreatedBy,
|
||||
DateTimeOffset? CreatedAt);
|
||||
|
||||
internal sealed record AmendCvssReceiptRequest(
|
||||
string Field,
|
||||
string? PreviousValue,
|
||||
string? NewValue,
|
||||
string Reason,
|
||||
string? ReferenceUri,
|
||||
EnvelopeKey? SigningKey,
|
||||
string? Actor);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user