feat: Initialize Zastava Webhook service with TLS and Authority authentication

- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint.
- Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately.
- Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly.
- Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
This commit is contained in:
2025-10-19 18:36:22 +03:00
parent 7e2fa0a42a
commit 5ce40d2eeb
966 changed files with 91038 additions and 1850 deletions

View File

@@ -1,13 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using System.Collections.Immutable;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using StellaOps.Excititor.Attestation.Extensions;
using StellaOps.Excititor.Attestation;
using StellaOps.Excititor.Attestation.Transparency;
using StellaOps.Excititor.ArtifactStores.S3.Extensions;
using StellaOps.Excititor.Export;
using StellaOps.Excititor.Storage.Mongo;
using StellaOps.Excititor.Connectors.RedHat.CSAF.DependencyInjection;
using StellaOps.Excititor.Core;
using StellaOps.Excititor.Export;
using StellaOps.Excititor.Formats.CSAF;
using StellaOps.Excititor.Formats.CycloneDX;
using StellaOps.Excititor.Formats.OpenVEX;
using StellaOps.Excititor.Policy;
using StellaOps.Excititor.Storage.Mongo;
using StellaOps.Excititor.WebService.Endpoints;
using StellaOps.Excititor.WebService.Options;
using StellaOps.Excititor.WebService.Services;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
@@ -18,11 +28,19 @@ services.AddOptions<VexMongoStorageOptions>()
.ValidateOnStart();
services.AddExcititorMongoStorage();
services.AddCsafNormalizer();
services.AddCycloneDxNormalizer();
services.AddOpenVexNormalizer();
services.AddSingleton<IVexSignatureVerifier, NoopVexSignatureVerifier>();
services.AddSingleton<IVexIngestOrchestrator, VexIngestOrchestrator>();
services.AddVexExportEngine();
services.AddVexExportCacheServices();
services.AddVexAttestation();
services.Configure<VexAttestationClientOptions>(configuration.GetSection("Excititor:Attestation:Client"));
services.AddVexPolicy();
services.AddRedHatCsafConnector();
services.Configure<MirrorDistributionOptions>(configuration.GetSection(MirrorDistributionOptions.SectionName));
services.AddSingleton<MirrorRateLimiter>();
var rekorSection = configuration.GetSection("Excititor:Attestation:Rekor");
if (rekorSection.Exists())
@@ -64,9 +82,15 @@ if (offlineSection.Exists())
services.AddEndpointsApiExplorer();
services.AddHealthChecks();
services.AddSingleton(TimeProvider.System);
services.AddMemoryCache();
services.AddAuthentication();
services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/excititor/status", async (HttpContext context,
IEnumerable<IVexArtifactStore> artifactStores,
IOptions<VexMongoStorageOptions> mongoOptions,
@@ -84,8 +108,139 @@ app.MapGet("/excititor/status", async (HttpContext context,
app.MapHealthChecks("/excititor/health");
app.MapPost("/excititor/statements", async (
VexStatementIngestRequest request,
IVexClaimStore claimStore,
TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
if (request?.Statements is null || request.Statements.Count == 0)
{
return Results.BadRequest("At least one statement must be provided.");
}
var claims = request.Statements.Select(statement => statement.ToDomainClaim());
await claimStore.AppendAsync(claims, timeProvider.GetUtcNow(), cancellationToken).ConfigureAwait(false);
return Results.Accepted();
});
app.MapGet("/excititor/statements/{vulnerabilityId}/{productKey}", async (
string vulnerabilityId,
string productKey,
DateTimeOffset? since,
IVexClaimStore claimStore,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(vulnerabilityId) || string.IsNullOrWhiteSpace(productKey))
{
return Results.BadRequest("vulnerabilityId and productKey are required.");
}
var claims = await claimStore.FindAsync(vulnerabilityId.Trim(), productKey.Trim(), since, cancellationToken).ConfigureAwait(false);
return Results.Ok(claims);
});
IngestEndpoints.MapIngestEndpoints(app);
ResolveEndpoint.MapResolveEndpoint(app);
MirrorEndpoints.MapMirrorEndpoints(app);
app.Run();
public partial class Program;
internal sealed record StatusResponse(DateTimeOffset UtcNow, string MongoBucket, int InlineThreshold, string[] ArtifactStores);
internal sealed record VexStatementIngestRequest(IReadOnlyList<VexStatementEntry> Statements);
internal sealed record VexStatementEntry(
string VulnerabilityId,
string ProviderId,
string ProductKey,
string? ProductName,
string? ProductVersion,
string? ProductPurl,
string? ProductCpe,
IReadOnlyList<string>? ComponentIdentifiers,
VexClaimStatus Status,
VexJustification? Justification,
string? Detail,
DateTimeOffset FirstSeen,
DateTimeOffset LastSeen,
VexDocumentFormat DocumentFormat,
string DocumentDigest,
string DocumentUri,
string? DocumentRevision,
VexSignatureMetadataRequest? Signature,
VexConfidenceRequest? Confidence,
VexSignalRequest? Signals,
IReadOnlyDictionary<string, string>? Metadata)
{
public VexClaim ToDomainClaim()
{
var product = new VexProduct(
ProductKey,
ProductName,
ProductVersion,
ProductPurl,
ProductCpe,
ComponentIdentifiers ?? Array.Empty<string>());
if (!Uri.TryCreate(DocumentUri, UriKind.Absolute, out var uri))
{
throw new InvalidOperationException($"DocumentUri '{DocumentUri}' is not a valid absolute URI.");
}
var document = new VexClaimDocument(
DocumentFormat,
DocumentDigest,
uri,
DocumentRevision,
Signature?.ToDomain());
var additionalMetadata = Metadata is null
? ImmutableDictionary<string, string>.Empty
: Metadata.ToImmutableDictionary(StringComparer.Ordinal);
return new VexClaim(
VulnerabilityId,
ProviderId,
product,
Status,
document,
FirstSeen,
LastSeen,
Justification,
Detail,
Confidence?.ToDomain(),
Signals?.ToDomain(),
additionalMetadata);
}
}
internal sealed record VexSignatureMetadataRequest(
string Type,
string? Subject,
string? Issuer,
string? KeyId,
DateTimeOffset? VerifiedAt,
string? TransparencyLogReference)
{
public VexSignatureMetadata ToDomain()
=> new(Type, Subject, Issuer, KeyId, VerifiedAt, TransparencyLogReference);
}
internal sealed record VexConfidenceRequest(string Level, double? Score, string? Method)
{
public VexConfidence ToDomain() => new(Level, Score, Method);
}
internal sealed record VexSignalRequest(VexSeveritySignalRequest? Severity, bool? Kev, double? Epss)
{
public VexSignalSnapshot ToDomain()
=> new(Severity?.ToDomain(), Kev, Epss);
}
internal sealed record VexSeveritySignalRequest(string Scheme, double? Score, string? Label, string? Vector)
{
public VexSeveritySignal ToDomain() => new(Scheme, Score, Label, Vector);
}