using System.Net; using System.Net.Sockets; namespace w4c_workflows.Services.Security; /// /// Authorises an outbound before a workflow node sends it. /// Names are resolved up front and every returned address is checked, so a host /// that maps to both a public and a private address is rejected rather than /// raced at connect time. Literal IPs skip DNS entirely. /// public sealed class EgressGuard { private readonly EgressPolicy _policy; private readonly IHostAddressResolver _resolver; public EgressGuard(EgressPolicy policy, IHostAddressResolver resolver) { _policy = policy; _resolver = resolver; } /// Ceiling the HTTP node applies to its own redirect setting. public int MaxRedirects => _policy.MaxRedirects; public async Task AuthorizeAsync(Uri uri, CancellationToken ct) { var hostDecision = _policy.CheckHost(uri); if (hostDecision != null) return hostDecision; var host = uri.DnsSafeHost; if (IPAddress.TryParse(host, out var literal)) return _policy.CheckAddresses(host, new[] { literal }); IReadOnlyList addresses; try { addresses = await _resolver.ResolveAsync(host, ct); } catch (SocketException ex) { return EgressDecision.Block("dns_failure", $"could not resolve host '{host}': {ex.Message}"); } catch (ArgumentException ex) { return EgressDecision.Block("dns_failure", $"could not resolve host '{host}': {ex.Message}"); } return _policy.CheckAddresses(host, addresses); } }