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:
		| @@ -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 | ||||
|   | ||||
| @@ -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] | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user