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;
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user