Some checks failed
Build Test Deploy / build-test (push) Has been cancelled
Build Test Deploy / authority-container (push) Has been cancelled
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
154 lines
5.4 KiB
C#
154 lines
5.4 KiB
C#
using System.Net;
|
|
using System.Net.Http;
|
|
|
|
namespace StellaOps.Concelier.Connector.Acsc.Configuration;
|
|
|
|
/// <summary>
|
|
/// Connector options governing ACSC feed access and retry behaviour.
|
|
/// </summary>
|
|
public sealed class AcscOptions
|
|
{
|
|
public const string HttpClientName = "acsc";
|
|
|
|
private static readonly TimeSpan DefaultRequestTimeout = TimeSpan.FromSeconds(45);
|
|
private static readonly TimeSpan DefaultFailureBackoff = TimeSpan.FromMinutes(5);
|
|
private static readonly TimeSpan DefaultInitialBackfill = TimeSpan.FromDays(120);
|
|
|
|
public AcscOptions()
|
|
{
|
|
Feeds = new List<AcscFeedOptions>
|
|
{
|
|
new() { Slug = "alerts", RelativePath = "/acsc/view-all-content/alerts/rss" },
|
|
new() { Slug = "advisories", RelativePath = "/acsc/view-all-content/advisories/rss" },
|
|
new() { Slug = "news", RelativePath = "/acsc/view-all-content/news/rss", Enabled = false },
|
|
new() { Slug = "publications", RelativePath = "/acsc/view-all-content/publications/rss", Enabled = false },
|
|
new() { Slug = "threats", RelativePath = "/acsc/view-all-content/threats/rss", Enabled = false },
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base endpoint for direct ACSC fetches.
|
|
/// </summary>
|
|
public Uri BaseEndpoint { get; set; } = new("https://www.cyber.gov.au/", UriKind.Absolute);
|
|
|
|
/// <summary>
|
|
/// Optional relay endpoint used when Akamai terminates direct HTTP/2 connections.
|
|
/// </summary>
|
|
public Uri? RelayEndpoint { get; set; }
|
|
|
|
/// <summary>
|
|
/// Default mode when no preference has been captured in connector state. When <c>true</c>, the relay will be preferred for initial fetches.
|
|
/// </summary>
|
|
public bool PreferRelayByDefault { get; set; }
|
|
|
|
/// <summary>
|
|
/// If enabled, the connector may switch to the relay endpoint when direct fetches fail.
|
|
/// </summary>
|
|
public bool EnableRelayFallback { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// If set, the connector will always use the relay endpoint and skip direct attempts.
|
|
/// </summary>
|
|
public bool ForceRelay { get; set; }
|
|
|
|
/// <summary>
|
|
/// Timeout applied to fetch requests (overrides HttpClient default).
|
|
/// </summary>
|
|
public TimeSpan RequestTimeout { get; set; } = DefaultRequestTimeout;
|
|
|
|
/// <summary>
|
|
/// Backoff applied when marking fetch failures.
|
|
/// </summary>
|
|
public TimeSpan FailureBackoff { get; set; } = DefaultFailureBackoff;
|
|
|
|
/// <summary>
|
|
/// Look-back period used when deriving initial published cursors.
|
|
/// </summary>
|
|
public TimeSpan InitialBackfill { get; set; } = DefaultInitialBackfill;
|
|
|
|
/// <summary>
|
|
/// User-agent header sent with outbound requests.
|
|
/// </summary>
|
|
public string UserAgent { get; set; } = "StellaOps/Concelier (+https://stella-ops.org)";
|
|
|
|
/// <summary>
|
|
/// RSS feeds requested during fetch.
|
|
/// </summary>
|
|
public IList<AcscFeedOptions> Feeds { get; }
|
|
|
|
/// <summary>
|
|
/// HTTP version policy requested for outbound requests.
|
|
/// </summary>
|
|
public HttpVersionPolicy VersionPolicy { get; set; } = HttpVersionPolicy.RequestVersionOrLower;
|
|
|
|
/// <summary>
|
|
/// Default HTTP version requested when connecting to ACSC (defaults to HTTP/2 but allows downgrade).
|
|
/// </summary>
|
|
public Version RequestVersion { get; set; } = HttpVersion.Version20;
|
|
|
|
public void Validate()
|
|
{
|
|
if (BaseEndpoint is null || !BaseEndpoint.IsAbsoluteUri)
|
|
{
|
|
throw new InvalidOperationException("ACSC BaseEndpoint must be an absolute URI.");
|
|
}
|
|
|
|
if (!BaseEndpoint.AbsoluteUri.EndsWith("/", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("ACSC BaseEndpoint must include a trailing slash.");
|
|
}
|
|
|
|
if (RelayEndpoint is not null && !RelayEndpoint.IsAbsoluteUri)
|
|
{
|
|
throw new InvalidOperationException("ACSC RelayEndpoint must be an absolute URI when specified.");
|
|
}
|
|
|
|
if (RelayEndpoint is not null && !RelayEndpoint.AbsoluteUri.EndsWith("/", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("ACSC RelayEndpoint must include a trailing slash when specified.");
|
|
}
|
|
|
|
if (RequestTimeout <= TimeSpan.Zero)
|
|
{
|
|
throw new InvalidOperationException("ACSC RequestTimeout must be positive.");
|
|
}
|
|
|
|
if (FailureBackoff < TimeSpan.Zero)
|
|
{
|
|
throw new InvalidOperationException("ACSC FailureBackoff cannot be negative.");
|
|
}
|
|
|
|
if (InitialBackfill <= TimeSpan.Zero)
|
|
{
|
|
throw new InvalidOperationException("ACSC InitialBackfill must be positive.");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(UserAgent))
|
|
{
|
|
throw new InvalidOperationException("ACSC UserAgent cannot be empty.");
|
|
}
|
|
|
|
if (Feeds.Count == 0)
|
|
{
|
|
throw new InvalidOperationException("At least one ACSC feed must be configured.");
|
|
}
|
|
|
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
for (var i = 0; i < Feeds.Count; i++)
|
|
{
|
|
var feed = Feeds[i];
|
|
feed.Validate(i);
|
|
|
|
if (!feed.Enabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!seen.Add(feed.Slug))
|
|
{
|
|
throw new InvalidOperationException($"Duplicate ACSC feed slug '{feed.Slug}' detected. Slugs must be unique (case-insensitive).");
|
|
}
|
|
}
|
|
}
|
|
}
|