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,5 +1,6 @@
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Options;
using Mongo2Go;
@@ -181,6 +182,78 @@ public sealed class MongoVexRepositoryTests : IAsyncLifetime
Assert.Null(remaining);
}
[Fact]
public async Task ClaimStore_AppendsAndQueriesStatements()
{
var database = _client.GetDatabase($"vex-claims-{Guid.NewGuid():N}");
var store = new MongoVexClaimStore(database);
var product = new VexProduct("pkg:demo/app", "Demo App", version: "1.0.0", purl: "pkg:demo/app@1.0.0");
var document = new VexClaimDocument(
VexDocumentFormat.Csaf,
"sha256:claim-1",
new Uri("https://example.org/vex/claim-1.json"),
revision: "2025-10-19");
var initialClaim = new VexClaim(
vulnerabilityId: "CVE-2025-0101",
providerId: "redhat",
product: product,
status: VexClaimStatus.NotAffected,
document: document,
firstSeen: DateTimeOffset.UtcNow.AddMinutes(-30),
lastSeen: DateTimeOffset.UtcNow.AddMinutes(-10),
justification: VexJustification.ComponentNotPresent,
detail: "Package not shipped in this channel.",
confidence: new VexConfidence("high", 0.9, "policy/default"),
signals: new VexSignalSnapshot(
new VexSeveritySignal("CVSS:3.1", 5.8, "medium", "CVSS:3.1/..."),
kev: false,
epss: 0.21),
additionalMetadata: ImmutableDictionary<string, string>.Empty.Add("source", "csaf"));
await store.AppendAsync(new[] { initialClaim }, DateTimeOffset.UtcNow.AddMinutes(-5), CancellationToken.None);
var secondDocument = new VexClaimDocument(
VexDocumentFormat.Csaf,
"sha256:claim-2",
new Uri("https://example.org/vex/claim-2.json"),
revision: "2025-10-19.1");
var secondClaim = new VexClaim(
vulnerabilityId: initialClaim.VulnerabilityId,
providerId: initialClaim.ProviderId,
product: initialClaim.Product,
status: initialClaim.Status,
document: secondDocument,
firstSeen: initialClaim.FirstSeen,
lastSeen: DateTimeOffset.UtcNow,
justification: initialClaim.Justification,
detail: initialClaim.Detail,
confidence: initialClaim.Confidence,
signals: new VexSignalSnapshot(
new VexSeveritySignal("CVSS:3.1", 7.2, "high"),
kev: true,
epss: 0.43),
additionalMetadata: initialClaim.AdditionalMetadata.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value));
await store.AppendAsync(new[] { secondClaim }, DateTimeOffset.UtcNow, CancellationToken.None);
var all = await store.FindAsync("CVE-2025-0101", product.Key, since: null, CancellationToken.None);
var allList = all.ToList();
Assert.Equal(2, allList.Count);
Assert.Equal("sha256:claim-2", allList[0].Document.Digest);
Assert.True(allList[0].Signals?.Kev);
Assert.Equal(0.43, allList[0].Signals?.Epss);
Assert.Equal("sha256:claim-1", allList[1].Document.Digest);
Assert.Equal("csaf", allList[1].AdditionalMetadata["source"]);
var recentOnly = await store.FindAsync("CVE-2025-0101", product.Key, DateTimeOffset.UtcNow.AddMinutes(-2), CancellationToken.None);
var recentList = recentOnly.ToList();
Assert.Single(recentList);
Assert.Equal("sha256:claim-2", recentList[0].Document.Digest);
}
private MongoVexRawStore CreateRawStore(IMongoDatabase database, int thresholdBytes)
{
var options = Options.Create(new VexMongoStorageOptions

View File

@@ -150,10 +150,29 @@ public sealed class MongoVexStoreMappingTests : IAsyncLifetime
},
}
},
{
"Signals",
new BsonDocument
{
{
"Severity",
new BsonDocument
{
{ "Scheme", "CVSS:3.1" },
{ "Score", 7.5 },
{ "Label", "high" },
{ "Vector", "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" },
}
},
{ "Kev", true },
{ "Epss", 0.42 },
}
},
{ "PolicyVersion", "2025.10" },
{ "PolicyRevisionId", "rev-1" },
{ "PolicyDigest", "sha256:abc" },
{ "Summary", "Vendor confirms not affected." },
{ "GeneratedAt", DateTime.UtcNow },
{ "Unexpected", new BsonDocument { { "foo", "bar" } } },
};
@@ -186,6 +205,12 @@ public sealed class MongoVexStoreMappingTests : IAsyncLifetime
Assert.Equal("policy_override", conflict.Reason);
Assert.Equal("Vendor confirms not affected.", result.Summary);
Assert.Equal("2025.10", result.PolicyVersion);
Assert.NotNull(result.Signals);
Assert.True(result.Signals!.Kev);
Assert.Equal(0.42, result.Signals.Epss);
Assert.NotNull(result.Signals.Severity);
Assert.Equal("CVSS:3.1", result.Signals.Severity!.Scheme);
Assert.Equal(7.5, result.Signals.Severity.Score);
}
[Fact]

View File

@@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Mongo2Go;
using MongoDB.Driver;
using StellaOps.Excititor.Storage.Mongo.Migrations;
using StellaOps.Excititor.Storage.Mongo;
namespace StellaOps.Excititor.Storage.Mongo.Tests;
@@ -23,24 +24,32 @@ public sealed class VexMongoMigrationRunnerTests : IAsyncLifetime
[Fact]
public async Task RunAsync_AppliesInitialIndexesOnce()
{
var migration = new VexInitialIndexMigration();
var runner = new VexMongoMigrationRunner(_database, new[] { migration }, NullLogger<VexMongoMigrationRunner>.Instance);
var migrations = new IVexMongoMigration[]
{
new VexInitialIndexMigration(),
new VexConsensusSignalsMigration(),
};
var runner = new VexMongoMigrationRunner(_database, migrations, NullLogger<VexMongoMigrationRunner>.Instance);
await runner.RunAsync(CancellationToken.None);
await runner.RunAsync(CancellationToken.None);
var appliedCollection = _database.GetCollection<VexMigrationRecord>(VexMongoCollectionNames.Migrations);
var applied = await appliedCollection.Find(FilterDefinition<VexMigrationRecord>.Empty).ToListAsync();
Assert.Single(applied);
Assert.Equal(migration.Id, applied[0].Id);
Assert.Equal(2, applied.Count);
Assert.Equal(migrations.Select(m => m.Id).OrderBy(id => id, StringComparer.Ordinal), applied.Select(record => record.Id).OrderBy(id => id, StringComparer.Ordinal));
Assert.True(HasIndex(_database.GetCollection<VexRawDocumentRecord>(VexMongoCollectionNames.Raw), "ProviderId_1_Format_1_RetrievedAt_1"));
Assert.True(HasIndex(_database.GetCollection<VexProviderRecord>(VexMongoCollectionNames.Providers), "Kind_1"));
Assert.True(HasIndex(_database.GetCollection<VexConsensusRecord>(VexMongoCollectionNames.Consensus), "VulnerabilityId_1_Product.Key_1"));
Assert.True(HasIndex(_database.GetCollection<VexConsensusRecord>(VexMongoCollectionNames.Consensus), "PolicyRevisionId_1_PolicyDigest_1"));
Assert.True(HasIndex(_database.GetCollection<VexConsensusRecord>(VexMongoCollectionNames.Consensus), "PolicyRevisionId_1_CalculatedAt_-1"));
Assert.True(HasIndex(_database.GetCollection<VexExportManifestRecord>(VexMongoCollectionNames.Exports), "QuerySignature_1_Format_1"));
Assert.True(HasIndex(_database.GetCollection<VexCacheEntryRecord>(VexMongoCollectionNames.Cache), "QuerySignature_1_Format_1"));
Assert.True(HasIndex(_database.GetCollection<VexCacheEntryRecord>(VexMongoCollectionNames.Cache), "ExpiresAt_1"));
Assert.True(HasIndex(_database.GetCollection<VexStatementRecord>(VexMongoCollectionNames.Statements), "VulnerabilityId_1_Product.Key_1_InsertedAt_-1"));
Assert.True(HasIndex(_database.GetCollection<VexStatementRecord>(VexMongoCollectionNames.Statements), "ProviderId_1_InsertedAt_-1"));
Assert.True(HasIndex(_database.GetCollection<VexStatementRecord>(VexMongoCollectionNames.Statements), "Document.Digest_1"));
}
private static bool HasIndex<TDocument>(IMongoCollection<TDocument> collection, string name)