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

@@ -0,0 +1,225 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using MongoDB.Driver;
using StellaOps.Excititor.Core;
using StellaOps.Excititor.Connectors.Abstractions;
using StellaOps.Excititor.Export;
using StellaOps.Excititor.Policy;
using StellaOps.Excititor.Storage.Mongo;
using StellaOps.Excititor.WebService.Options;
namespace StellaOps.Excititor.WebService.Tests;
public sealed class MirrorEndpointsTests : IClassFixture<WebApplicationFactory<Program>>, IDisposable
{
private readonly WebApplicationFactory<Program> _factory;
private readonly Mongo2Go.MongoDbRunner _runner;
public MirrorEndpointsTests(WebApplicationFactory<Program> factory)
{
_runner = Mongo2Go.MongoDbRunner.Start();
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((_, configuration) =>
{
var data = new Dictionary<string, string?>
{
[$"{MirrorDistributionOptions.SectionName}:Domains:0:Id"] = "primary",
[$"{MirrorDistributionOptions.SectionName}:Domains:0:DisplayName"] = "Primary Mirror",
[$"{MirrorDistributionOptions.SectionName}:Domains:0:MaxIndexRequestsPerHour"] = "1000",
[$"{MirrorDistributionOptions.SectionName}:Domains:0:MaxDownloadRequestsPerHour"] = "1000",
[$"{MirrorDistributionOptions.SectionName}:Domains:0:Exports:0:Key"] = "consensus",
[$"{MirrorDistributionOptions.SectionName}:Domains:0:Exports:0:Format"] = "json",
[$"{MirrorDistributionOptions.SectionName}:Domains:0:Exports:0:Filters:vulnId"] = "CVE-2025-0001",
[$"{MirrorDistributionOptions.SectionName}:Domains:0:Exports:0:Filters:productKey"] = "pkg:test/demo",
};
configuration.AddInMemoryCollection(data!);
});
builder.ConfigureServices(services =>
{
services.RemoveAll<IMongoClient>();
services.AddSingleton<IMongoClient>(_ => new MongoClient(_runner.ConnectionString));
services.RemoveAll<IMongoDatabase>();
services.AddSingleton(provider => provider.GetRequiredService<IMongoClient>().GetDatabase("mirror-tests"));
services.RemoveAll<IVexExportStore>();
services.AddSingleton<IVexExportStore>(provider =>
{
var timeProvider = provider.GetRequiredService<TimeProvider>();
return new FakeExportStore(timeProvider);
});
services.RemoveAll<IVexArtifactStore>();
services.AddSingleton<IVexArtifactStore>(_ => new FakeArtifactStore());
services.AddSingleton(new VexConnectorDescriptor("excititor:redhat", VexProviderKind.Distro, "Red Hat CSAF"));
services.AddSingleton<StellaOps.Excititor.Attestation.Signing.IVexSigner, FakeSigner>();
services.AddSingleton<StellaOps.Excititor.Policy.IVexPolicyEvaluator, FakePolicyEvaluator>();
services.AddSingleton<IVexExportDataSource, FakeExportDataSource>();
});
});
}
[Fact]
public async Task ListDomains_ReturnsConfiguredDomain()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/excititor/mirror/domains");
response.EnsureSuccessStatusCode();
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var domains = document.RootElement.GetProperty("domains");
Assert.Equal(1, domains.GetArrayLength());
Assert.Equal("primary", domains[0].GetProperty("id").GetString());
}
[Fact]
public async Task DomainIndex_ReturnsManifestMetadata()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/excititor/mirror/domains/primary/index");
response.EnsureSuccessStatusCode();
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var exports = document.RootElement.GetProperty("exports");
Assert.Equal(1, exports.GetArrayLength());
var entry = exports[0];
Assert.Equal("consensus", entry.GetProperty("exportKey").GetString());
Assert.Equal("exports/20251019T000000000Z/abcdef", entry.GetProperty("exportId").GetString());
var artifact = entry.GetProperty("artifact");
Assert.Equal("sha256", artifact.GetProperty("algorithm").GetString());
Assert.Equal("deadbeef", artifact.GetProperty("digest").GetString());
}
[Fact]
public async Task Download_ReturnsArtifactContent()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/excititor/mirror/domains/primary/exports/consensus/download");
response.EnsureSuccessStatusCode();
Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType);
var payload = await response.Content.ReadAsStringAsync();
Assert.Equal("{\"status\":\"ok\"}", payload);
}
public void Dispose()
{
_runner.Dispose();
}
private sealed class FakeExportStore : IVexExportStore
{
private readonly ConcurrentDictionary<(string Signature, VexExportFormat Format), VexExportManifest> _manifests = new();
public FakeExportStore(TimeProvider timeProvider)
{
var filters = new[]
{
new VexQueryFilter("vulnId", "CVE-2025-0001"),
new VexQueryFilter("productKey", "pkg:test/demo"),
};
var query = VexQuery.Create(filters, Enumerable.Empty<VexQuerySort>());
var signature = VexQuerySignature.FromQuery(query);
var createdAt = new DateTimeOffset(2025, 10, 19, 0, 0, 0, TimeSpan.Zero);
var manifest = new VexExportManifest(
"exports/20251019T000000000Z/abcdef",
signature,
VexExportFormat.Json,
createdAt,
new VexContentAddress("sha256", "deadbeef"),
1,
new[] { "primary" },
fromCache: false,
consensusRevision: "rev-1",
attestation: new VexAttestationMetadata("https://stella-ops.org/attestations/vex-export"),
sizeBytes: 16);
_manifests.TryAdd((signature.Value, VexExportFormat.Json), manifest);
// Seed artifact content for download test.
FakeArtifactStore.Seed(manifest.Artifact, "{\"status\":\"ok\"}");
}
public ValueTask<VexExportManifest?> FindAsync(VexQuerySignature signature, VexExportFormat format, CancellationToken cancellationToken)
{
_manifests.TryGetValue((signature.Value, format), out var manifest);
return ValueTask.FromResult(manifest);
}
public ValueTask SaveAsync(VexExportManifest manifest, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
}
private sealed class FakeArtifactStore : IVexArtifactStore
{
private static readonly ConcurrentDictionary<VexContentAddress, byte[]> Content = new();
public static void Seed(VexContentAddress contentAddress, string payload)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(payload);
Content[contentAddress] = bytes;
}
public ValueTask<VexStoredArtifact> SaveAsync(VexExportArtifact artifact, CancellationToken cancellationToken)
{
Content[artifact.ContentAddress] = artifact.Content.ToArray();
return ValueTask.FromResult(new VexStoredArtifact(artifact.ContentAddress, "memory://artifact", artifact.Content.Length, artifact.Metadata));
}
public ValueTask DeleteAsync(VexContentAddress contentAddress, CancellationToken cancellationToken)
{
Content.TryRemove(contentAddress, out _);
return ValueTask.CompletedTask;
}
public ValueTask<Stream?> OpenReadAsync(VexContentAddress contentAddress, CancellationToken cancellationToken)
{
if (!Content.TryGetValue(contentAddress, out var bytes))
{
return ValueTask.FromResult<Stream?>(null);
}
return ValueTask.FromResult<Stream?>(new MemoryStream(bytes, writable: false));
}
}
private sealed class FakeSigner : StellaOps.Excititor.Attestation.Signing.IVexSigner
{
public ValueTask<StellaOps.Excititor.Attestation.Signing.VexSignedPayload> SignAsync(ReadOnlyMemory<byte> payload, CancellationToken cancellationToken)
=> ValueTask.FromResult(new StellaOps.Excititor.Attestation.Signing.VexSignedPayload("signature", "key"));
}
private sealed class FakePolicyEvaluator : StellaOps.Excititor.Policy.IVexPolicyEvaluator
{
public string Version => "test";
public VexPolicySnapshot Snapshot => VexPolicySnapshot.Default;
public double GetProviderWeight(VexProvider provider) => 1.0;
public bool IsClaimEligible(VexClaim claim, VexProvider provider, out string? rejectionReason)
{
rejectionReason = null;
return true;
}
}
private sealed class FakeExportDataSource : IVexExportDataSource
{
public ValueTask<VexExportDataSet> FetchAsync(VexQuery query, CancellationToken cancellationToken)
{
var dataset = new VexExportDataSet(ImmutableArray<VexConsensus>.Empty, ImmutableArray<VexClaim>.Empty, ImmutableArray<string>.Empty);
return ValueTask.FromResult(dataset);
}
}
}