Abort on untrusted proxy being used

1. The problem I’m having:

Hi! I would like to ensure that requests indicating to be proxied / forwarded which do not match the trusted_proxies + trusted_proxies_strict configuration to not be served at all.
I am currently using the following snippet to abort requests that have a X-Forwarded-IP header but do not yield a real client_ip different from the remote host address.

@is_untrusted_proxy {
    header X-Forwarded-For *
    expression {client_ip} == {remote_host}
  }
abort @is_untrusted_proxy

While this somewhat does what I want it to do it is a rather fragile solution. It would be better if we could receive the result of the trusted_proxies evaluation directly somehow. Is there a way this can be achieved?

2. Error messages and/or full log output:

3. Caddy version:

v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=

4. How I installed and ran Caddy:

Docker

a. System environment:

Debian 12
Docker version 27.2.0

b. Command:

c. Service/unit/compose file:

d. My complete Caddy config:

{
  servers {
  #  trusted_proxies static private_ranges
    trusted_proxies_strict
  }
}

(abort_untrusted_proxies) {
  @is_untrusted_proxy {
    header X-Forwarded-For *
    expression {client_ip} == {remote_host}
  }
  abort @is_untrusted_proxy
}

:80 {
  handle {
    import abort_untrusted_proxies
    respond "request works"
  }
}

5. Links to relevant resources:

Howdy @Horstexplorer, welcome to the Caddy community.

Rather than trying to see if the client IP and remote IP are the same, you can simply check if the immediate peer is one of the trusted proxies with remote_ip private_ranges, e.g.

@is_untrusted_proxy {
  header X-Forwarded-For
  not remote_ip private_ranges
}

This isn’t “receiving the result of the trusted_proxies evaluation” per se, but it’s running an identical evaluation all the same, because your trusted_proxies are private_ranges anyway.

P.S.:

Is this meant to be {remote_ip} instead of {remote_host}?

1 Like

Thank you for your response!

Using the remote_ip matcher sounds like a decent idea for this example. I am not too sure if this would scale to using non static trusted_proxy providers like this cloudflare one, unless there is some way to retrieve its utilized ips.

{remote_host} describes the shorthand for the placeholder {http.request.remote.host}.

Whoops, you’re absolutely right, and I should not be conflating placeholders and matchers.

You’re right about this too, unfortunately - my answer only really works for the specific case you asked about in your original post.

Maybe if the matcher could be configured to read from the http.ip_sources modules? But not doable right now, as far as I know.

1 Like

There’s this matcher plugin GitHub - tuzzmaniandevil/caddy-dynamic-clientip: http.matchers.dynamic_client_ip matches requests by the client IP address, the ip addresses to match against is provided my a module that implements IPRangeSource which uses IP source plugins to compare against the known client IP. There’s also GitHub - fvbommel/caddy-combine-ip-ranges: IP prefix module for Caddy that combines the output of other IP prefix modules. which lets you combine IP sources together.

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.