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