Add unit tests for SBOM ingestion and transformation
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implement `SbomIngestServiceCollectionExtensionsTests` to verify the SBOM ingestion pipeline exports snapshots correctly. - Create `SbomIngestTransformerTests` to ensure the transformation produces expected nodes and edges, including deduplication of license nodes and normalization of timestamps. - Add `SbomSnapshotExporterTests` to test the export functionality for manifest, adjacency, nodes, and edges. - Introduce `VexOverlayTransformerTests` to validate the transformation of VEX nodes and edges. - Set up project file for the test project with necessary dependencies and configurations. - Include JSON fixture files for testing purposes.
This commit is contained in:
206
src/Findings/StellaOps.Findings.Ledger.WebService/Program.cs
Normal file
206
src/Findings/StellaOps.Findings.Ledger.WebService/Program.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.DependencyInjection;
|
||||
using StellaOps.Findings.Ledger.Domain;
|
||||
using StellaOps.Findings.Ledger.Infrastructure;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.Merkle;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.Postgres;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.Projection;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.Policy;
|
||||
using StellaOps.Findings.Ledger.Options;
|
||||
using StellaOps.Findings.Ledger.Services;
|
||||
using StellaOps.Findings.Ledger.WebService.Contracts;
|
||||
using StellaOps.Findings.Ledger.WebService.Mappings;
|
||||
using StellaOps.Telemetry.Core;
|
||||
|
||||
const string LedgerWritePolicy = "ledger.events.write";
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration.AddStellaOpsDefaults(options =>
|
||||
{
|
||||
options.BasePath = builder.Environment.ContentRootPath;
|
||||
options.EnvironmentPrefix = "FINDINGS_LEDGER_";
|
||||
options.ConfigureBuilder = configurationBuilder =>
|
||||
{
|
||||
configurationBuilder.AddYamlFile("../etc/findings-ledger.yaml", optional: true, reloadOnChange: true);
|
||||
};
|
||||
});
|
||||
|
||||
var bootstrapOptions = builder.Configuration.BindOptions<LedgerServiceOptions>(
|
||||
LedgerServiceOptions.SectionName,
|
||||
(opts, _) => opts.Validate());
|
||||
|
||||
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
|
||||
{
|
||||
loggerConfiguration
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console();
|
||||
});
|
||||
|
||||
builder.Services.AddOptions<LedgerServiceOptions>()
|
||||
.Bind(builder.Configuration.GetSection(LedgerServiceOptions.SectionName))
|
||||
.PostConfigure(options => options.Validate())
|
||||
.ValidateOnStart();
|
||||
|
||||
builder.Services.AddSingleton(TimeProvider.System);
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
builder.Services.AddStellaOpsTelemetry(
|
||||
builder.Configuration,
|
||||
configureMetering: meterBuilder =>
|
||||
{
|
||||
meterBuilder.AddAspNetCoreInstrumentation();
|
||||
meterBuilder.AddHttpClientInstrumentation();
|
||||
},
|
||||
configureTracing: tracerBuilder =>
|
||||
{
|
||||
tracerBuilder.AddAspNetCoreInstrumentation();
|
||||
tracerBuilder.AddHttpClientInstrumentation();
|
||||
});
|
||||
|
||||
builder.Services.AddStellaOpsResourceServerAuthentication(
|
||||
builder.Configuration,
|
||||
configurationSection: null,
|
||||
configure: resourceOptions =>
|
||||
{
|
||||
resourceOptions.Authority = bootstrapOptions.Authority.Issuer;
|
||||
resourceOptions.RequireHttpsMetadata = bootstrapOptions.Authority.RequireHttpsMetadata;
|
||||
resourceOptions.MetadataAddress = bootstrapOptions.Authority.MetadataAddress;
|
||||
resourceOptions.BackchannelTimeout = bootstrapOptions.Authority.BackchannelTimeout;
|
||||
resourceOptions.TokenClockSkew = bootstrapOptions.Authority.TokenClockSkew;
|
||||
|
||||
resourceOptions.Audiences.Clear();
|
||||
foreach (var audience in bootstrapOptions.Authority.Audiences)
|
||||
{
|
||||
resourceOptions.Audiences.Add(audience);
|
||||
}
|
||||
|
||||
resourceOptions.RequiredScopes.Clear();
|
||||
foreach (var scope in bootstrapOptions.Authority.RequiredScopes)
|
||||
{
|
||||
resourceOptions.RequiredScopes.Add(scope);
|
||||
}
|
||||
|
||||
foreach (var network in bootstrapOptions.Authority.BypassNetworks)
|
||||
{
|
||||
resourceOptions.BypassNetworks.Add(network);
|
||||
}
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
var scopes = bootstrapOptions.Authority.RequiredScopes.Count > 0
|
||||
? bootstrapOptions.Authority.RequiredScopes.ToArray()
|
||||
: new[] { StellaOpsScopes.VulnOperate };
|
||||
|
||||
options.AddPolicy(LedgerWritePolicy, policy =>
|
||||
{
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.Requirements.Add(new StellaOpsScopeRequirement(scopes));
|
||||
policy.AddAuthenticationSchemes(StellaOpsAuthenticationDefaults.AuthenticationScheme);
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<LedgerAnchorQueue>();
|
||||
builder.Services.AddSingleton<LedgerDataSource>();
|
||||
builder.Services.AddSingleton<IMerkleAnchorRepository, PostgresMerkleAnchorRepository>();
|
||||
builder.Services.AddSingleton<ILedgerEventRepository, PostgresLedgerEventRepository>();
|
||||
builder.Services.AddSingleton<IMerkleAnchorScheduler, PostgresMerkleAnchorScheduler>();
|
||||
builder.Services.AddSingleton<ILedgerEventStream, PostgresLedgerEventStream>();
|
||||
builder.Services.AddSingleton<IFindingProjectionRepository, PostgresFindingProjectionRepository>();
|
||||
builder.Services.AddSingleton<IPolicyEvaluationService, InlinePolicyEvaluationService>();
|
||||
builder.Services.AddSingleton<ILedgerEventWriteService, LedgerEventWriteService>();
|
||||
builder.Services.AddHostedService<LedgerMerkleAnchorWorker>();
|
||||
builder.Services.AddHostedService<LedgerProjectionWorker>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseSerilogRequestLogging();
|
||||
app.UseExceptionHandler(exceptionApp =>
|
||||
{
|
||||
exceptionApp.Run(async context =>
|
||||
{
|
||||
var feature = context.Features.Get<IExceptionHandlerFeature>();
|
||||
if (feature?.Error is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var problem = Results.Problem(
|
||||
statusCode: StatusCodes.Status500InternalServerError,
|
||||
title: "ledger_internal_error",
|
||||
detail: feature.Error.Message);
|
||||
await problem.ExecuteAsync(context);
|
||||
});
|
||||
});
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapHealthChecks("/healthz");
|
||||
|
||||
app.MapPost("/vuln/ledger/events", async Task<Results<Created<LedgerEventResponse>, Ok<LedgerEventResponse>, ProblemHttpResult>> (
|
||||
LedgerEventRequest request,
|
||||
ILedgerEventWriteService writeService,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var draft = request.ToDraft();
|
||||
var result = await writeService.AppendAsync(draft, cancellationToken).ConfigureAwait(false);
|
||||
return result.Status switch
|
||||
{
|
||||
LedgerWriteStatus.Success => CreateCreatedResponse(result.Record!),
|
||||
LedgerWriteStatus.Idempotent => TypedResults.Ok(CreateResponse(result.Record!, "idempotent")),
|
||||
LedgerWriteStatus.ValidationFailed => TypedResults.Problem(
|
||||
statusCode: StatusCodes.Status400BadRequest,
|
||||
title: "validation_failed",
|
||||
detail: string.Join(";", result.Errors)),
|
||||
LedgerWriteStatus.Conflict => TypedResults.Problem(
|
||||
statusCode: StatusCodes.Status409Conflict,
|
||||
title: result.ConflictCode ?? "conflict",
|
||||
detail: string.Join(";", result.Errors)),
|
||||
_ => TypedResults.Problem(
|
||||
statusCode: StatusCodes.Status500InternalServerError,
|
||||
title: "ledger_internal_error",
|
||||
detail: "Unexpected ledger status.")
|
||||
};
|
||||
})
|
||||
.WithName("LedgerEventAppend")
|
||||
.RequireAuthorization(LedgerWritePolicy)
|
||||
.Produces(StatusCodes.Status201Created)
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.ProducesProblem(StatusCodes.Status409Conflict)
|
||||
.ProducesProblem(StatusCodes.Status500InternalServerError);
|
||||
|
||||
app.Run();
|
||||
|
||||
static Created<LedgerEventResponse> CreateCreatedResponse(LedgerEventRecord record)
|
||||
{
|
||||
var response = CreateResponse(record, "created");
|
||||
return TypedResults.Created($"/vuln/ledger/events/{record.EventId}", response);
|
||||
}
|
||||
|
||||
static LedgerEventResponse CreateResponse(LedgerEventRecord record, string status)
|
||||
=> new()
|
||||
{
|
||||
EventId = record.EventId,
|
||||
ChainId = record.ChainId,
|
||||
Sequence = record.SequenceNumber,
|
||||
Status = status,
|
||||
EventHash = record.EventHash,
|
||||
PreviousHash = record.PreviousHash,
|
||||
MerkleLeafHash = record.MerkleLeafHash,
|
||||
RecordedAt = record.RecordedAt
|
||||
};
|
||||
Reference in New Issue
Block a user