using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; using System.Net; namespace ZB.MOM.WW.ScadaBridge.Security; /// /// Config shape for trusted-proxy forwarded-header handling /// (ScadaBridge:Security:ForwardedHeaders). Disabled by default: a deployment /// with no reverse proxy must NOT honor X-Forwarded-For (a client could spoof its /// throttle key). The documented Traefik topologies enable it with the docker /// network range so LoginThrottle keys on the REAL client IP (arch-review R2 N2). /// public sealed class ForwardedHeadersSecurityOptions { /// Configuration section binding this options class. public const string SectionName = "ScadaBridge:Security:ForwardedHeaders"; /// Whether X-Forwarded-For from trusted proxies is honored. public bool Enabled { get; set; } /// Exact proxy IPs allowed to supply X-Forwarded-For. public string[] KnownProxies { get; set; } = []; /// CIDR networks allowed to supply X-Forwarded-For (e.g. "172.16.0.0/12"). public string[] KnownNetworks { get; set; } = []; } /// /// Builds the for app.UseForwardedHeaders. /// Pure and fail-fast: malformed entries throw at startup rather than silently /// trusting nothing/everything. Defaults (loopback) are CLEARED — only the /// explicitly configured proxies/networks are trusted, and ForwardLimit=1 means /// only the entry the trusted proxy itself appended is honored. /// public static class ForwardedHeadersSetup { /// /// Builds the forwarded-headers options, or when disabled. /// /// The bound . /// The configured , or when disabled. public static ForwardedHeadersOptions? Build(ForwardedHeadersSecurityOptions options) { if (!options.Enabled) return null; var built = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, ForwardLimit = 1, }; built.KnownProxies.Clear(); // KnownIPNetworks (System.Net.IPNetwork) is the net10 successor to the now-deprecated // KnownNetworks; UseForwardedHeaders reads it identically. built.KnownIPNetworks.Clear(); foreach (var proxy in options.KnownProxies) built.KnownProxies.Add(IPAddress.Parse(proxy)); foreach (var network in options.KnownNetworks) { var parts = network.Split('/'); built.KnownIPNetworks.Add(new(IPAddress.Parse(parts[0]), int.Parse(parts[1]))); } return built; } }