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:
master
2025-10-19 18:36:22 +03:00
parent 2062da7a8b
commit d099a90f9b
966 changed files with 91038 additions and 1850 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
@@ -319,6 +320,61 @@ public sealed class CommandHandlersTests
}
}
[Fact]
public async Task HandleExcititorExportAsync_DownloadsWhenOutputProvided()
{
var original = Environment.ExitCode;
using var tempDir = new TempDirectory();
try
{
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
const string manifestJson = """
{
"exportId": "exports/20251019T101530Z/abcdef1234567890",
"format": "openvex",
"createdAt": "2025-10-19T10:15:30Z",
"artifact": { "algorithm": "sha256", "digest": "abcdef1234567890" },
"fromCache": false,
"sizeBytes": 2048,
"attestation": {
"rekor": {
"location": "https://rekor.example/api/v1/log/entries/123",
"logIndex": "123"
}
}
}
""";
backend.ExcititorResult = new ExcititorOperationResult(true, "ok", null, JsonDocument.Parse(manifestJson).RootElement.Clone());
var provider = BuildServiceProvider(backend);
var outputPath = Path.Combine(tempDir.Path, "export.json");
await CommandHandlers.HandleExcititorExportAsync(
provider,
format: "openvex",
delta: false,
scope: null,
since: null,
provider: null,
outputPath: outputPath,
verbose: false,
cancellationToken: CancellationToken.None);
Assert.Equal(0, Environment.ExitCode);
Assert.Single(backend.ExportDownloads);
var request = backend.ExportDownloads[0];
Assert.Equal("exports/20251019T101530Z/abcdef1234567890", request.ExportId);
Assert.Equal(Path.GetFullPath(outputPath), request.DestinationPath);
Assert.Equal("sha256", request.Algorithm);
Assert.Equal("abcdef1234567890", request.Digest);
}
finally
{
Environment.ExitCode = original;
}
}
[Theory]
[InlineData(null)]
[InlineData("default")]
@@ -624,6 +680,7 @@ public sealed class CommandHandlersTests
public string? LastExcititorRoute { get; private set; }
public HttpMethod? LastExcititorMethod { get; private set; }
public object? LastExcititorPayload { get; private set; }
public List<(string ExportId, string DestinationPath, string? Algorithm, string? Digest)> ExportDownloads { get; } = new();
public ExcititorOperationResult? ExcititorResult { get; set; } = new ExcititorOperationResult(true, "ok", null, null);
public IReadOnlyList<ExcititorProviderSummary> ProviderSummaries { get; set; } = Array.Empty<ExcititorProviderSummary>();
@@ -650,8 +707,29 @@ public sealed class CommandHandlersTests
return Task.FromResult(ExcititorResult ?? new ExcititorOperationResult(true, "ok", null, null));
}
public Task<ExcititorExportDownloadResult> DownloadExcititorExportAsync(string exportId, string destinationPath, string? expectedDigestAlgorithm, string? expectedDigest, CancellationToken cancellationToken)
{
var fullPath = Path.GetFullPath(destinationPath);
var directory = Path.GetDirectoryName(fullPath);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
File.WriteAllText(fullPath, "{}");
var info = new FileInfo(fullPath);
ExportDownloads.Add((exportId, fullPath, expectedDigestAlgorithm, expectedDigest));
return Task.FromResult(new ExcititorExportDownloadResult(fullPath, info.Length, false));
}
public Task<IReadOnlyList<ExcititorProviderSummary>> GetExcititorProvidersAsync(bool includeDisabled, CancellationToken cancellationToken)
=> Task.FromResult(ProviderSummaries);
public Task<RuntimePolicyEvaluationResult> EvaluateRuntimePolicyAsync(RuntimePolicyEvaluationRequest request, CancellationToken cancellationToken)
{
var empty = new ReadOnlyDictionary<string, RuntimePolicyImageDecision>(new Dictionary<string, RuntimePolicyImageDecision>());
return Task.FromResult(new RuntimePolicyEvaluationResult(0, null, null, empty));
}
}
private sealed class StubExecutor : IScannerExecutor