Class RemoteUrlPolicy

java.lang.Object
org.apache.juneau.http.remote.RemoteUrlPolicy

public final class RemoteUrlPolicy extends Object
Shared SSRF-hardening policy for the @Remote/@RemoteOp proxy engines.

Replaces the earlier scheme-only guard (requireHttpScheme) with a deny-private default policy: an absolute http/https @Remote URL (an explicit @Url value, a baseUrl() override, or an already-absolute default-path fullPath) is additionally checked against a deny-list of loopback, RFC1918/link-local, and cloud-metadata targets — both lexically (the literal host string) and, separately, against the resolved address actually connected to ("pin-on-connect", see selectAllowedAddress(String, boolean, AddressResolver)).

Scheme-less (relative) values are unaffected — they resolve against the operator-configured client root, which is not re-checked by this policy.

Both engines (next-generation RestClient.remote(...) and classic RestClient.getRemote(...)) call into this single shared class so the deny-list and pin-selection logic are defined exactly once.

Since:
10.0.0
  • Field Details

    • MAX_REDIRECT_HOPS

      public static final int MAX_REDIRECT_HOPS
      The maximum number of redirect hops a policy-covered @Remote call will follow before failing.
      See Also:
    • ALLOW_PRIVATE_URLS_PROPERTY

      public static final String ALLOW_PRIVATE_URLS_PROPERTY
      The RestClient builder/settings key for the allowPrivateUrls opt-in (system property fallback).
      See Also:
    • ALLOW_ADDRESS_RANGES_PROPERTY

      The system property holding the narrow lab allow-list: a comma- or whitespace-separated list of CIDR ranges (e.g. 198.18.0.0/15,192.0.0.8) that re-permit addresses denied only because they fall in one of the special-purpose ranges 192.0.0.0/24, 198.18.0.0/15, 224.0.0.0/4, 240.0.0.0/4 or ff00::/8. Unset or blank (the default) allows nothing.

      The allow is narrow by construction: every entry must lie entirely inside one of those five ranges (anything else, including 0.0.0.0/0 and ::/0, is rejected with an IllegalArgumentException), and it never re-permits loopback, link-local/metadata, private, CGNAT, this-network, Teredo, local-use NAT64 or any other denied address, unlike the all-or-nothing ALLOW_PRIVATE_URLS_PROPERTY opt-in. An IPv4 entry matches an IPv4 address or the IPv4 address embedded in an IPv6 form; an IPv6 entry matches only a native ff00::/8 address.

      See Also:
  • Method Details

    • requireAllowedUrl

      public static String requireAllowedUrl(String url, boolean allowPrivateUrls)
      Enforces the SSRF guardrail on an effective @Remote URL: when url carries a URI scheme, it must be a canonical absolute http/https URL with a nonempty host, and — unless allowPrivateUrls is set — the literal host must not match the deny-list (see class javadoc). Scheme-less (relative) values pass through unchanged (they resolve against the client root, which is not re-checked here).

      This is the pre-check stage only. It does not resolve DNS; a hostname that is not itself denied but resolves to a denied address is instead caught by selectAllowedAddress(String, boolean, AddressResolver) ("pin-on-connect") at connect time, including on every followed redirect hop.

      Parameters:
      url - The effective URL/path to validate.
      allowPrivateUrls - When true, the deny-list check is skipped (the http/https scheme requirement and canonical-URI requirement still apply).
      Returns:
      The unchanged url.
      Throws:
      IllegalArgumentException - If url is absolute and violates the policy.
    • requireAllowedUrl

      public static String requireAllowedUrl(String url, boolean allowPrivateUrls, RemoteUrlPolicy.AllowedRanges allowedRanges)
      Same as requireAllowedUrl(String, boolean), additionally re-permitting literal hosts that fall inside the per-client allowedRanges (unioned with the "RestClient.allowAddressRanges" system property).
      Parameters:
      url - The effective URL/path to validate.
      allowPrivateUrls - When true, the deny-list check is skipped.
      allowedRanges - The per-client narrow allow-list. Must not be null.
      Returns:
      The unchanged url.
      Throws:
      IllegalArgumentException - If url is absolute and violates the policy.
    • isDeniedHost

      public static boolean isDeniedHost(String host)
      Returns true if the literal host string is denied by the default policy: a dotted-decimal IPv4/bracketed-IPv6 literal classified as denied by isDeniedAddress(InetAddress) (an IPv6 zone id is ignored, and an unparseable literal containing a : is denied), or a case-insensitive match on localhost/localhost.localdomain/metadata.google.internal (and its subdomains).

      A bare decimal/octal/hex IP literal (e.g. http://2130706433) does not look like a dotted IPv4 literal and is intentionally left to selectAllowedAddress(String, boolean, AddressResolver) (resolved at pin-on-connect time), not this lexical pre-check.

      Parameters:
      host - The literal host string (from URI.getHost()).
      Returns:
      true if the host is denied by the default policy.
    • isDeniedHost

      public static boolean isDeniedHost(String host, RemoteUrlPolicy.AllowedRanges allowedRanges)
      Same as isDeniedHost(String), taking the per-client allowedRanges into account.
      Parameters:
      host - The literal host string (from URI.getHost()).
      allowedRanges - The per-client narrow allow-list. Must not be null.
      Returns:
      true if the host is denied.
    • isDeniedAddress

      public static boolean isDeniedAddress(InetAddress addr)
      Returns true if addr is denied by the default policy -- the deny-list applied both to literal-IP hosts (pre-check) and to resolved addresses (pin-on-connect).

      Denied addresses:

      • Loopback, unspecified, link-local (including the 169.254.169.254 metadata address) and RFC1918 site-local addresses.
      • IPv4 0.0.0.0/8 ("this network") and 100.64.0.0/10 (carrier-grade NAT shared space).
      • IPv4 special-purpose ranges that are never legitimate remote targets: 192.0.0.0/24 (IETF protocol assignments), 198.18.0.0/15 (benchmarking; often lab or appliance networks — see ALLOW_ADDRESS_RANGES_PROPERTY), 224.0.0.0/4 (multicast) and 240.0.0.0/4 (reserved, including the limited broadcast address 255.255.255.255).
      • IPv6 unique-local fc00::/7 and multicast ff00::/8.
      • IPv6 Teredo 2001::/32, denied outright because the embedded client IPv4 address is obfuscated.
      • IPv6 local-use NAT64 64:ff9b:1::/48 (RFC 8215), denied outright because the embedding length (/48, /56, /64 or /96) is operator-defined and cannot be determined from the address.

      IPv6 forms that embed an IPv4 address are unwrapped and classified by the embedded IPv4 address: IPv4-mapped (::ffff:a.b.c.d), IPv4-compatible (::a.b.c.d), IPv4-translated (::ffff:0:a.b.c.d), NAT64 (64:ff9b::/96) and 6to4 (2002::/16, IPv4 in bytes 2-5). A public embedded address (e.g. 64:ff9b::808:808) remains allowed.

      Addresses inside a range listed in the "RestClient.allowAddressRanges" system property (empty by default) are re-permitted if they are denied only as a special-purpose range; see ALLOW_ADDRESS_RANGES_PROPERTY.

      Parameters:
      addr - The address to classify. Must not be null.
      Returns:
      true if the address is denied, taking the "RestClient.allowAddressRanges" allow-list into account.
      Throws:
      IllegalArgumentException - If the "RestClient.allowAddressRanges" property is malformed.
    • isDeniedAddress

      public static boolean isDeniedAddress(InetAddress addr, RemoteUrlPolicy.AllowedRanges allowedRanges)
      Same as isDeniedAddress(InetAddress), additionally re-permitting special-purpose addresses inside the per-client allowedRanges. The effective allow-list is the union of those ranges and the "RestClient.allowAddressRanges" system property; both are bounded to the same five special-purpose ranges, so the union can never re-open any other deny.
      Parameters:
      addr - The address to classify. Must not be null.
      allowedRanges - The per-client narrow allow-list. Must not be null.
      Returns:
      true if the address is denied.
      Throws:
      IllegalArgumentException - If the "RestClient.allowAddressRanges" property is malformed.
    • selectAllowedAddress

      public static InetAddress selectAllowedAddress(String host, boolean allowPrivateUrls, RemoteUrlPolicy.AddressResolver resolver) throws UnknownHostException
      Resolves host and returns the first candidate address that is allowed by the default policy ("pin-on-connect") -- the address the caller should actually connect to. When multiple candidates are returned (e.g. mixed A/AAAA), the first allowed one wins; a candidate that is itself denied is skipped rather than rejecting the whole resolution, so a name with one public and one loopback address may still be used.
      Parameters:
      host - The hostname (or IP literal) to resolve and pin.
      allowPrivateUrls - When true, the deny-list check is skipped and the first resolved candidate is returned unconditionally.
      resolver - The address resolver to use. Pass RemoteUrlPolicy.AddressResolver.DEFAULT for real DNS, or an injectable stub in tests to simulate rebinding/mixed-family resolution without live DNS.
      Returns:
      The selected address to connect to. Never null.
      Throws:
      UnknownHostException - If host cannot be resolved.
      IllegalArgumentException - If every resolved candidate is denied (and allowPrivateUrls is false).
    • selectAllowedAddress

      public static InetAddress selectAllowedAddress(String host, boolean allowPrivateUrls, RemoteUrlPolicy.AddressResolver resolver, RemoteUrlPolicy.AllowedRanges allowedRanges) throws UnknownHostException
      Same as selectAllowedAddress(String, boolean, AddressResolver), additionally re-permitting candidates inside the per-client allowedRanges (unioned with the "RestClient.allowAddressRanges" system property).
      Parameters:
      host - The hostname (or IP literal) to resolve and pin.
      allowPrivateUrls - When true, the deny-list check is skipped.
      resolver - The address resolver to use.
      allowedRanges - The per-client narrow allow-list. Must not be null.
      Returns:
      The selected address to connect to. Never null.
      Throws:
      UnknownHostException - If host cannot be resolved.
      IllegalArgumentException - If every resolved candidate is denied.
    • resolveRedirectLocation

      public static URI resolveRedirectLocation(URI from, String location)
      Resolves a redirect Location header value against the current request URI, per RFC 7231 §7.1.2 (relative locations are resolved against the request URI).
      Parameters:
      from - The current (pre-redirect) request URI. Must be absolute.
      location - The Location header value. Must not be null or blank.
      Returns:
      The resolved absolute redirect-target URI.
      Throws:
      IllegalArgumentException - If location is blank or does not resolve to an absolute URI.
    • schemeOf

      public static String schemeOf(String url)
      Returns the URI scheme of a URL (the token before the first : when it precedes any /, ? or #), or null if the value has no scheme (e.g. a relative path or a value beginning with a {var} token).
      Parameters:
      url - The URL to inspect.
      Returns:
      The scheme, or null if none.