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 /// accepted. The connect-time half of the defence is /// : egress clients re-resolve and re-validate the /// address in their ConnectCallback, so a name that answers differently on /// the second lookup (DNS rebinding) cannot reach a blocked address. /// 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 PolicyDecision.Deny("dns_failure", $"could not resolve host '{host}': {ex.Message}"); } catch (ArgumentException ex) { return PolicyDecision.Deny("dns_failure", $"could not resolve host '{host}': {ex.Message}"); } return _policy.CheckAddresses(host, addresses); } }