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,6 +1,8 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Excititor.Connectors.MSRC.CSAF.Configuration;
|
||||
|
||||
@@ -8,6 +10,10 @@ public sealed class MsrcConnectorOptions
|
||||
{
|
||||
public const string TokenClientName = "excititor.connector.msrc.token";
|
||||
public const string DefaultScope = "https://api.msrc.microsoft.com/.default";
|
||||
public const string ApiClientName = "excititor.connector.msrc.api";
|
||||
public const string DefaultBaseUri = "https://api.msrc.microsoft.com/sug/v2.0/";
|
||||
public const string DefaultLocale = "en-US";
|
||||
public const string DefaultApiVersion = "2024-08-01";
|
||||
|
||||
/// <summary>
|
||||
/// Azure AD tenant identifier (GUID or domain).
|
||||
@@ -45,6 +51,61 @@ public sealed class MsrcConnectorOptions
|
||||
/// </summary>
|
||||
public int ExpiryLeewaySeconds { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Base URI for MSRC Security Update Guide API.
|
||||
/// </summary>
|
||||
public Uri BaseUri { get; set; } = new(DefaultBaseUri, UriKind.Absolute);
|
||||
|
||||
/// <summary>
|
||||
/// Locale requested when fetching summaries.
|
||||
/// </summary>
|
||||
public string Locale { get; set; } = DefaultLocale;
|
||||
|
||||
/// <summary>
|
||||
/// API version appended to MSRC requests.
|
||||
/// </summary>
|
||||
public string ApiVersion { get; set; } = DefaultApiVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Page size used while enumerating summaries.
|
||||
/// </summary>
|
||||
public int PageSize { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum CSAF advisories fetched per connector run.
|
||||
/// </summary>
|
||||
public int MaxAdvisoriesPerFetch { get; set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Overlap window applied when resuming from the last modified cursor.
|
||||
/// </summary>
|
||||
public TimeSpan CursorOverlap { get; set; } = TimeSpan.FromMinutes(10);
|
||||
|
||||
/// <summary>
|
||||
/// Delay between CSAF downloads to respect rate limits.
|
||||
/// </summary>
|
||||
public TimeSpan RequestDelay { get; set; } = TimeSpan.FromMilliseconds(250);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum retry attempts for summary/detail fetch operations.
|
||||
/// </summary>
|
||||
public int MaxRetryAttempts { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Base delay applied between retries (jitter handled by connector).
|
||||
/// </summary>
|
||||
public TimeSpan RetryBaseDelay { get; set; } = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// Optional lower bound for initial synchronisation when no cursor is stored.
|
||||
/// </summary>
|
||||
public DateTimeOffset? InitialLastModified { get; set; } = DateTimeOffset.UtcNow.AddDays(-30);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of document digests persisted for deduplication.
|
||||
/// </summary>
|
||||
public int MaxTrackedDigests { get; set; } = 2048;
|
||||
|
||||
public void Validate(IFileSystem? fileSystem = null)
|
||||
{
|
||||
if (PreferOfflineToken)
|
||||
@@ -82,6 +143,61 @@ public sealed class MsrcConnectorOptions
|
||||
ExpiryLeewaySeconds = 10;
|
||||
}
|
||||
|
||||
if (BaseUri is null || !BaseUri.IsAbsoluteUri)
|
||||
{
|
||||
throw new InvalidOperationException("BaseUri must be an absolute URI.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Locale))
|
||||
{
|
||||
throw new InvalidOperationException("Locale must be provided.");
|
||||
}
|
||||
|
||||
if (!CultureInfo.GetCultures(CultureTypes.AllCultures).Any(c => string.Equals(c.Name, Locale, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
throw new InvalidOperationException($"Locale '{Locale}' is not recognised.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ApiVersion))
|
||||
{
|
||||
throw new InvalidOperationException("ApiVersion must be provided.");
|
||||
}
|
||||
|
||||
if (PageSize <= 0 || PageSize > 500)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(PageSize)} must be between 1 and 500.");
|
||||
}
|
||||
|
||||
if (MaxAdvisoriesPerFetch <= 0)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(MaxAdvisoriesPerFetch)} must be greater than zero.");
|
||||
}
|
||||
|
||||
if (CursorOverlap < TimeSpan.Zero || CursorOverlap > TimeSpan.FromHours(6))
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(CursorOverlap)} must be within 0-6 hours.");
|
||||
}
|
||||
|
||||
if (RequestDelay < TimeSpan.Zero || RequestDelay > TimeSpan.FromSeconds(10))
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(RequestDelay)} must be between 0 and 10 seconds.");
|
||||
}
|
||||
|
||||
if (MaxRetryAttempts <= 0 || MaxRetryAttempts > 10)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(MaxRetryAttempts)} must be between 1 and 10.");
|
||||
}
|
||||
|
||||
if (RetryBaseDelay < TimeSpan.Zero || RetryBaseDelay > TimeSpan.FromMinutes(5))
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(RetryBaseDelay)} must be between 0 and 5 minutes.");
|
||||
}
|
||||
|
||||
if (MaxTrackedDigests <= 0 || MaxTrackedDigests > 10000)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(MaxTrackedDigests)} must be between 1 and 10000.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(OfflineTokenPath))
|
||||
{
|
||||
var fs = fileSystem ?? new FileSystem();
|
||||
|
||||
Reference in New Issue
Block a user