feat: Implement CVSS receipt management client and models
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
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-07 01:14:28 +02:00
parent 53889d85e7
commit 69651212ec
30 changed files with 815 additions and 109 deletions

View File

@@ -64,17 +64,18 @@ internal static class CommandFactory
root.Add(BuildPromotionCommand(services, verboseOption, cancellationToken));
root.Add(BuildDetscoreCommand(services, verboseOption, cancellationToken));
root.Add(BuildObsCommand(services, verboseOption, cancellationToken));
root.Add(BuildPackCommand(services, verboseOption, cancellationToken));
root.Add(BuildExceptionsCommand(services, verboseOption, cancellationToken));
root.Add(BuildOrchCommand(services, verboseOption, cancellationToken));
root.Add(BuildSbomCommand(services, verboseOption, cancellationToken));
root.Add(BuildNotifyCommand(services, verboseOption, cancellationToken));
root.Add(BuildSbomerCommand(services, verboseOption, cancellationToken));
root.Add(BuildRiskCommand(services, verboseOption, cancellationToken));
root.Add(BuildReachabilityCommand(services, verboseOption, cancellationToken));
root.Add(BuildApiCommand(services, verboseOption, cancellationToken));
root.Add(BuildSdkCommand(services, verboseOption, cancellationToken));
root.Add(BuildMirrorCommand(services, verboseOption, cancellationToken));
root.Add(BuildPackCommand(services, verboseOption, cancellationToken));
root.Add(BuildExceptionsCommand(services, verboseOption, cancellationToken));
root.Add(BuildOrchCommand(services, verboseOption, cancellationToken));
root.Add(BuildSbomCommand(services, verboseOption, cancellationToken));
root.Add(BuildNotifyCommand(services, verboseOption, cancellationToken));
root.Add(BuildSbomerCommand(services, verboseOption, cancellationToken));
root.Add(BuildCvssCommand(services, verboseOption, cancellationToken));
root.Add(BuildRiskCommand(services, verboseOption, cancellationToken));
root.Add(BuildReachabilityCommand(services, verboseOption, cancellationToken));
root.Add(BuildApiCommand(services, verboseOption, cancellationToken));
root.Add(BuildSdkCommand(services, verboseOption, cancellationToken));
root.Add(BuildMirrorCommand(services, verboseOption, cancellationToken));
root.Add(BuildAirgapCommand(services, verboseOption, cancellationToken));
root.Add(SystemCommandBuilder.BuildSystemCommand(services, verboseOption, cancellationToken));
@@ -126,9 +127,79 @@ internal static class CommandFactory
return CommandHandlers.HandleScannerDownloadAsync(services, channel, output, overwrite, install, verbose, cancellationToken);
});
scanner.Add(download);
return scanner;
}
scanner.Add(download);
return scanner;
}
private static Command BuildCvssCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken)
{
var cvss = new Command("cvss", "CVSS v4.0 receipt operations (score, show, history, export)." );
var score = new Command("score", "Create a CVSS v4 receipt for a vulnerability.");
var vulnOption = new Option<string>("--vuln") { Description = "Vulnerability identifier (e.g., CVE).", IsRequired = true };
var policyFileOption = new Option<string>("--policy-file") { Description = "Path to CvssPolicy JSON file.", IsRequired = true };
var vectorOption = new Option<string>("--vector") { Description = "CVSS:4.0 vector string.", IsRequired = true };
var jsonOption = new Option<bool>("--json") { Description = "Emit JSON output." };
score.Add(vulnOption);
score.Add(policyFileOption);
score.Add(vectorOption);
score.Add(jsonOption);
score.SetAction((parseResult, _) =>
{
var vuln = parseResult.GetValue(vulnOption) ?? string.Empty;
var policyPath = parseResult.GetValue(policyFileOption) ?? string.Empty;
var vector = parseResult.GetValue(vectorOption) ?? string.Empty;
var json = parseResult.GetValue(jsonOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleCvssScoreAsync(services, vuln, policyPath, vector, json, verbose, cancellationToken);
});
var show = new Command("show", "Fetch a CVSS receipt by ID.");
var receiptArg = new Argument<string>("receipt-id") { Description = "Receipt identifier." };
show.Add(receiptArg);
var showJsonOption = new Option<bool>("--json") { Description = "Emit JSON output." };
show.Add(showJsonOption);
show.SetAction((parseResult, _) =>
{
var receiptId = parseResult.GetValue(receiptArg) ?? string.Empty;
var json = parseResult.GetValue(showJsonOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleCvssShowAsync(services, receiptId, json, verbose, cancellationToken);
});
var history = new Command("history", "Show receipt amendment history.");
history.Add(receiptArg);
var historyJsonOption = new Option<bool>("--json") { Description = "Emit JSON output." };
history.Add(historyJsonOption);
history.SetAction((parseResult, _) =>
{
var receiptId = parseResult.GetValue(receiptArg) ?? string.Empty;
var json = parseResult.GetValue(historyJsonOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleCvssHistoryAsync(services, receiptId, json, verbose, cancellationToken);
});
var export = new Command("export", "Export a CVSS receipt to JSON (pdf not yet supported).");
export.Add(receiptArg);
var formatOption = new Option<string>("--format") { Description = "json|pdf (json default)." };
var outOption = new Option<string>("--out") { Description = "Output file path." };
export.Add(formatOption);
export.Add(outOption);
export.SetAction((parseResult, _) =>
{
var receiptId = parseResult.GetValue(receiptArg) ?? string.Empty;
var format = parseResult.GetValue(formatOption) ?? "json";
var output = parseResult.GetValue(outOption);
var verbose = parseResult.GetValue(verboseOption);
return CommandHandlers.HandleCvssExportAsync(services, receiptId, format, output, verbose, cancellationToken);
});
cvss.Add(score);
cvss.Add(show);
cvss.Add(history);
cvss.Add(export);
return cvss;
}
private static Command BuildScanCommand(IServiceProvider services, StellaOpsCliOptions options, Option<bool> verboseOption, CancellationToken cancellationToken)
{

View File

@@ -27,14 +27,17 @@ using StellaOps.Cli.Configuration;
using StellaOps.Cli.Output;
using StellaOps.Cli.Prompts;
using StellaOps.Cli.Services;
using StellaOps.Cli.Services.Models;
using StellaOps.Cli.Services.Models.AdvisoryAi;
using StellaOps.Cli.Services.Models.Bun;
using StellaOps.Cli.Services.Models.Ruby;
using StellaOps.Cli.Telemetry;
using StellaOps.Cryptography;
using StellaOps.Cryptography.DependencyInjection;
using StellaOps.Cryptography.Kms;
using StellaOps.Cli.Services.Models;
using StellaOps.Cli.Services.Models.AdvisoryAi;
using StellaOps.Cli.Services.Models.Bun;
using StellaOps.Cli.Services.Models.Ruby;
using StellaOps.Cli.Telemetry;
using StellaOps.Cryptography;
using StellaOps.Cryptography.DependencyInjection;
using StellaOps.Cryptography.Kms;
using StellaOps.Policy.Scoring;
using StellaOps.Policy.Scoring.Engine;
using StellaOps.Policy.Scoring.Policies;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Java;
using StellaOps.Scanner.Analyzers.Lang.Node;
@@ -67,12 +70,17 @@ internal static class CommandHandlers
/// <summary>
/// JSON serializer options for output (alias for JsonOptions).
/// </summary>
private static readonly JsonSerializerOptions JsonOutputOptions = JsonOptions;
private static readonly JsonSerializerOptions JsonOutputOptions = JsonOptions;
private static readonly JsonSerializerOptions CompactJson = new(JsonSerializerDefaults.Web)
{
WriteIndented = true
};
/// <summary>
/// Sets the verbosity level for logging.
/// </summary>
private static void SetVerbosity(IServiceProvider services, bool verbose)
private static void SetVerbosity(IServiceProvider services, bool verbose)
{
// Configure logging level based on verbose flag
var loggerFactory = services.GetService<ILoggerFactory>();
@@ -82,7 +90,215 @@ internal static class CommandHandlers
var logger = loggerFactory.CreateLogger("StellaOps.Cli.Commands.CommandHandlers");
logger.LogDebug("Verbose logging enabled");
}
}
}
public static async Task HandleCvssScoreAsync(
IServiceProvider services,
string vulnerabilityId,
string policyPath,
string vector,
bool json,
bool verbose,
CancellationToken cancellationToken)
{
await using var scope = services.CreateAsyncScope();
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("cvss-score");
var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
try
{
var policyJson = await File.ReadAllTextAsync(policyPath, cancellationToken).ConfigureAwait(false);
var loader = new CvssPolicyLoader();
var policyResult = loader.Load(policyJson, cancellationToken);
if (!policyResult.IsValid || policyResult.Policy is null || string.IsNullOrWhiteSpace(policyResult.Hash))
{
var errors = string.Join("; ", policyResult.Errors.Select(e => $"{e.Path}: {e.Message}"));
throw new InvalidOperationException($"Policy invalid: {errors}");
}
var policy = policyResult.Policy with { Hash = policyResult.Hash };
var engine = scope.ServiceProvider.GetRequiredService<ICvssV4Engine>();
var parsed = engine.ParseVector(vector);
var client = scope.ServiceProvider.GetRequiredService<ICvssClient>();
var request = new CreateCvssReceipt(
vulnerabilityId,
policy,
parsed.BaseMetrics,
parsed.ThreatMetrics,
parsed.EnvironmentalMetrics,
parsed.SupplementalMetrics,
Array.Empty<CvssEvidenceItem>(),
SigningKey: null,
CreatedBy: "cli",
CreatedAt: DateTimeOffset.UtcNow);
var receipt = await client.CreateReceiptAsync(request, cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("CVSS receipt creation failed.");
if (json)
{
Console.WriteLine(JsonSerializer.Serialize(receipt, CompactJson));
}
else
{
Console.WriteLine($"✔ CVSS receipt {receipt.ReceiptId} created | Severity {receipt.Severity} | Effective {receipt.Scores.EffectiveScore:0.0}");
Console.WriteLine($"Vector: {receipt.VectorString}");
Console.WriteLine($"Policy: {receipt.PolicyRef.PolicyId} v{receipt.PolicyRef.Version} ({receipt.PolicyRef.Hash})");
}
Environment.ExitCode = 0;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to create CVSS receipt");
Environment.ExitCode = 1;
if (json)
{
var problem = new { error = "cvss_score_failed", message = ex.Message };
Console.WriteLine(JsonSerializer.Serialize(problem, CompactJson));
}
}
}
public static async Task HandleCvssShowAsync(
IServiceProvider services,
string receiptId,
bool json,
bool verbose,
CancellationToken cancellationToken)
{
await using var scope = services.CreateAsyncScope();
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("cvss-show");
var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
try
{
var client = scope.ServiceProvider.GetRequiredService<ICvssClient>();
var receipt = await client.GetReceiptAsync(receiptId, cancellationToken).ConfigureAwait(false);
if (receipt is null)
{
Environment.ExitCode = 5;
Console.WriteLine(json
? JsonSerializer.Serialize(new { error = "not_found", receiptId }, CompactJson)
: $"✖ Receipt {receiptId} not found");
return;
}
if (json)
{
Console.WriteLine(JsonSerializer.Serialize(receipt, CompactJson));
}
else
{
Console.WriteLine($"Receipt {receipt.ReceiptId} | Severity {receipt.Severity} | Effective {receipt.Scores.EffectiveScore:0.0}");
Console.WriteLine($"Created {receipt.CreatedAt:u} by {receipt.CreatedBy}");
Console.WriteLine($"Vector: {receipt.VectorString}");
}
Environment.ExitCode = 0;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to fetch CVSS receipt {ReceiptId}", receiptId);
Environment.ExitCode = 1;
}
}
public static async Task HandleCvssHistoryAsync(
IServiceProvider services,
string receiptId,
bool json,
bool verbose,
CancellationToken cancellationToken)
{
await using var scope = services.CreateAsyncScope();
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("cvss-history");
var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
try
{
var client = scope.ServiceProvider.GetRequiredService<ICvssClient>();
var history = await client.GetHistoryAsync(receiptId, cancellationToken).ConfigureAwait(false);
if (json)
{
Console.WriteLine(JsonSerializer.Serialize(history, CompactJson));
}
else
{
if (history.Count == 0)
{
Console.WriteLine("(no history)");
}
else
{
foreach (var entry in history.OrderBy(h => h.Timestamp))
{
Console.WriteLine($"{entry.Timestamp:u} | {entry.Actor} | {entry.ChangeType} {entry.Field} => {entry.NewValue ?? ""} ({entry.Reason})");
}
}
}
Environment.ExitCode = 0;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to fetch CVSS receipt history {ReceiptId}", receiptId);
Environment.ExitCode = 1;
}
}
public static async Task HandleCvssExportAsync(
IServiceProvider services,
string receiptId,
string format,
string? output,
bool verbose,
CancellationToken cancellationToken)
{
await using var scope = services.CreateAsyncScope();
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("cvss-export");
var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
try
{
var client = scope.ServiceProvider.GetRequiredService<ICvssClient>();
var receipt = await client.GetReceiptAsync(receiptId, cancellationToken).ConfigureAwait(false);
if (receipt is null)
{
Environment.ExitCode = 5;
Console.WriteLine($"✖ Receipt {receiptId} not found");
return;
}
if (!string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
{
Environment.ExitCode = 9;
Console.WriteLine("Only json export is supported at this time.");
return;
}
var targetPath = string.IsNullOrWhiteSpace(output)
? $"cvss-receipt-{receipt.ReceiptId}.json"
: output!;
var jsonPayload = JsonSerializer.Serialize(receipt, CompactJson);
await File.WriteAllTextAsync(targetPath, jsonPayload, cancellationToken).ConfigureAwait(false);
Console.WriteLine($"✔ Exported receipt to {targetPath}");
Environment.ExitCode = 0;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to export CVSS receipt {ReceiptId}", receiptId);
Environment.ExitCode = 1;
}
}
private static async Task VerifyBundleAsync(string path, ILogger logger, CancellationToken cancellationToken)
{

View File

@@ -13,6 +13,7 @@ using StellaOps.Cli.Services;
using StellaOps.Cli.Telemetry;
using StellaOps.AirGap.Policy;
using StellaOps.Configuration;
using StellaOps.Policy.Scoring.Engine;
namespace StellaOps.Cli;
@@ -213,8 +214,8 @@ internal static class Program
// CLI-PARITY-41-002: Notify client for notification management
services.AddHttpClient<INotifyClient, NotifyClient>(client =>
{
client.Timeout = TimeSpan.FromSeconds(60);
}).AddEgressPolicyGuard("stellaops-cli", "notify-api");
client.Timeout = TimeSpan.FromSeconds(60);
}).AddEgressPolicyGuard("stellaops-cli", "notify-api");
// CLI-SBOM-60-001: Sbomer client for layer/compose operations
services.AddHttpClient<ISbomerClient, SbomerClient>(client =>
@@ -222,6 +223,14 @@ internal static class Program
client.Timeout = TimeSpan.FromMinutes(5); // Composition may take longer
}).AddEgressPolicyGuard("stellaops-cli", "sbomer-api");
// CLI-CVSS-190-010: CVSS receipt client (talks to Policy Gateway /api/cvss)
services.AddHttpClient<ICvssClient, CvssClient>(client =>
{
client.Timeout = TimeSpan.FromSeconds(60);
}).AddEgressPolicyGuard("stellaops-cli", "cvss-api");
services.AddSingleton<ICvssV4Engine, CvssV4Engine>();
// CLI-AIRGAP-56-001: Mirror bundle import service for air-gap operations
services.AddSingleton<StellaOps.AirGap.Importer.Repositories.IBundleCatalogRepository,
StellaOps.AirGap.Importer.Repositories.InMemoryBundleCatalogRepository>();

View File

@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Auth.Client;
using StellaOps.Cli.Configuration;
using StellaOps.Policy.Scoring;
namespace StellaOps.Cli.Services;
internal sealed class CvssClient : ICvssClient
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
private static readonly TimeSpan TokenRefreshSkew = TimeSpan.FromSeconds(30);
private readonly HttpClient httpClient;
private readonly StellaOpsCliOptions options;
private readonly ILogger<CvssClient> logger;
private readonly IStellaOpsTokenClient? tokenClient;
private readonly object tokenSync = new();
private string? cachedAccessToken;
private DateTimeOffset cachedAccessTokenExpiresAt = DateTimeOffset.MinValue;
public CvssClient(HttpClient httpClient, StellaOpsCliOptions options, ILogger<CvssClient> logger, IStellaOpsTokenClient? tokenClient = null)
{
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.tokenClient = tokenClient;
if (!string.IsNullOrWhiteSpace(options.BackendUrl) && httpClient.BaseAddress is null)
{
if (Uri.TryCreate(options.BackendUrl, UriKind.Absolute, out var baseUri))
{
httpClient.BaseAddress = baseUri;
}
}
}
public async Task<CvssScoreReceipt?> CreateReceiptAsync(CreateCvssReceipt request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
EnsureConfigured();
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/cvss/receipts")
{
Content = JsonContent.Create(request, options: SerializerOptions)
};
await AuthorizeRequestAsync(httpRequest, StellaOps.Auth.Abstractions.StellaOpsScopes.PolicyRun, cancellationToken).ConfigureAwait(false);
using var response = await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
return await ReadResponseAsync<CvssScoreReceipt>(response, cancellationToken).ConfigureAwait(false);
}
public async Task<CvssScoreReceipt?> GetReceiptAsync(string receiptId, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(receiptId);
EnsureConfigured();
using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"/api/cvss/receipts/{Uri.EscapeDataString(receiptId)}");
await AuthorizeRequestAsync(httpRequest, StellaOps.Auth.Abstractions.StellaOpsScopes.FindingsRead, cancellationToken).ConfigureAwait(false);
using var response = await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
return await ReadResponseAsync<CvssScoreReceipt>(response, cancellationToken).ConfigureAwait(false);
}
public async Task<IReadOnlyList<ReceiptHistoryEntry>> GetHistoryAsync(string receiptId, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(receiptId);
EnsureConfigured();
using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"/api/cvss/receipts/{Uri.EscapeDataString(receiptId)}/history");
await AuthorizeRequestAsync(httpRequest, StellaOps.Auth.Abstractions.StellaOpsScopes.FindingsRead, cancellationToken).ConfigureAwait(false);
using var response = await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
return await ReadResponseAsync<IReadOnlyList<ReceiptHistoryEntry>>(response, cancellationToken).ConfigureAwait(false)
?? Array.Empty<ReceiptHistoryEntry>();
}
public async Task<IReadOnlyList<CvssPolicy>> ListPoliciesAsync(CancellationToken cancellationToken)
{
EnsureConfigured();
using var httpRequest = new HttpRequestMessage(HttpMethod.Get, "/api/cvss/policies");
await AuthorizeRequestAsync(httpRequest, StellaOps.Auth.Abstractions.StellaOpsScopes.PolicyRun, cancellationToken).ConfigureAwait(false);
using var response = await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
return await ReadResponseAsync<IReadOnlyList<CvssPolicy>>(response, cancellationToken).ConfigureAwait(false)
?? Array.Empty<CvssPolicy>();
}
private async Task<T?> ReadResponseAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken)
{
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return default;
}
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
logger.LogWarning("CVSS request failed with {Status}: {Body}", (int)response.StatusCode, string.IsNullOrWhiteSpace(body) ? "<empty>" : body);
return default;
}
return await response.Content.ReadFromJsonAsync<T>(SerializerOptions, cancellationToken).ConfigureAwait(false);
}
private void EnsureConfigured()
{
if (string.IsNullOrWhiteSpace(options.BackendUrl) && httpClient.BaseAddress is null)
{
throw new InvalidOperationException("Backend URL not configured. Set STELLAOPS_BACKEND_URL or --backend-url.");
}
}
private async Task AuthorizeRequestAsync(HttpRequestMessage request, string scope, CancellationToken cancellationToken)
{
var token = await GetAccessTokenAsync(scope, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(token))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
}
private async Task<string?> GetAccessTokenAsync(string scope, CancellationToken cancellationToken)
{
if (tokenClient is null)
{
return null;
}
lock (tokenSync)
{
if (cachedAccessToken is not null && DateTimeOffset.UtcNow < cachedAccessTokenExpiresAt - TokenRefreshSkew)
{
return cachedAccessToken;
}
}
try
{
var result = await tokenClient.GetAccessTokenAsync(scope, cancellationToken).ConfigureAwait(false);
lock (tokenSync)
{
cachedAccessToken = result.AccessToken;
cachedAccessTokenExpiresAt = result.ExpiresAt;
}
return result.AccessToken;
}
catch (Exception ex)
{
logger.LogWarning(ex, "Token acquisition failed for scope {Scope}", scope);
return null;
}
}
}

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Policy.Scoring;
namespace StellaOps.Cli.Services;
internal interface ICvssClient
{
Task<CvssScoreReceipt?> CreateReceiptAsync(CreateCvssReceipt request, CancellationToken cancellationToken);
Task<CvssScoreReceipt?> GetReceiptAsync(string receiptId, CancellationToken cancellationToken);
Task<IReadOnlyList<ReceiptHistoryEntry>> GetHistoryAsync(string receiptId, CancellationToken cancellationToken);
Task<IReadOnlyList<CvssPolicy>> ListPoliciesAsync(CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using StellaOps.Attestor.Envelope;
using StellaOps.Policy.Scoring;
namespace StellaOps.Cli.Services.Models;
internal sealed record CreateCvssReceipt(
[property: JsonPropertyName("vulnerabilityId")] string VulnerabilityId,
[property: JsonPropertyName("policy")] CvssPolicy Policy,
[property: JsonPropertyName("baseMetrics")] CvssBaseMetrics BaseMetrics,
[property: JsonPropertyName("threatMetrics")] CvssThreatMetrics? ThreatMetrics,
[property: JsonPropertyName("environmentalMetrics")] CvssEnvironmentalMetrics? EnvironmentalMetrics,
[property: JsonPropertyName("supplementalMetrics")] CvssSupplementalMetrics? SupplementalMetrics,
[property: JsonPropertyName("evidence")] IReadOnlyList<CvssEvidenceItem> Evidence,
[property: JsonPropertyName("signingKey")] EnvelopeKey? SigningKey,
[property: JsonPropertyName("createdBy")] string? CreatedBy,
[property: JsonPropertyName("createdAt")] DateTimeOffset? CreatedAt);

View File

@@ -69,6 +69,7 @@
<ProjectReference Include="../../Policy/__Libraries/StellaOps.Policy.Storage.Postgres/StellaOps.Policy.Storage.Postgres.csproj" />
<ProjectReference Include="../../Notify/__Libraries/StellaOps.Notify.Storage.Postgres/StellaOps.Notify.Storage.Postgres.csproj" />
<ProjectReference Include="../../Excititor/__Libraries/StellaOps.Excititor.Storage.Postgres/StellaOps.Excititor.Storage.Postgres.csproj" />
<ProjectReference Include="../../Policy/StellaOps.Policy.Scoring/StellaOps.Policy.Scoring.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">