-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Forwarded header (RFC 7239) #3262
Comments
Thanks for the request. What is your use case for this? The Go team tried to drill down on this a few years ago but the answers were either missing or unsatisfactory: golang/go#20526 |
Thanks for the quick response. Honestly, it's more of a "nice to have", with a use case of replacing the quasi-standardized Some research on the topic: I found @c960657's more comprehensive https://c960657.github.io/forwarded.html immediately after I finished making the list below. 😅 Relevant issues/tickets:
|
There's a couple go libs that do the parsing: They're both over 4 years old though. |
Now this is an excellent feature request. 👍 Not because it's a high priority, but because it's well-researched! |
I don't think there's any plans to implement this at the moment, so I'll mark this "deferred" and close it for now. If there becomes a compelling reason to implement it later (like more of the server applications ecosystem start using it) then we can reopen it. Thanks @0az for the research on the topic! |
It works with Nginx but while looking to switch to Caddy I found no way to implement it. My Sanic services make use of it. There are clear benefits but "no one uses it" remains a chicken-egg problem that can only be fixed by more server implementing it as an option. The de-facto standards are messy in what gets included, whether the original or local "host" header is sent as is, and with some using x-real-ip while others use x-forwarded-for and some append to x-forwarded-for and set x-real-ip with the most recent client IP. I couldn't even find a custom header definition to do this in Caddy. Is that possible somehow? Ideally such that existing proxy headers are kept but don't need to be converted. I always use the "secret" parameter suggested in Nginx docs, which prevents spoofing of proxy headers, even when the full chain of proxies are kept. https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/ Parsing the header is best done last-to-first header (if there is more than one), right-to-left, such that any injections attempted by client cannot mess up the later records added by trusted proxies. The only difficulty being that values may be quoted, and within quotes |
Now that we're about 3 years into Caddy v2, and things are settling down a little, I'd be open to reviewing a PR to implement this. (Just don't have high hopes of it being a one-and-done dealio) |
Almost correct with this config. Apparently IPv6 remote_host would need to be bracketed but I don't know how to do that with Caddy yet. Also, would prefer a shorthand config to remove any x-forwarded headers and add this one.
EDIT: Don't use this code! Fixed version in #3262 (comment) |
Those aren't the Nginx docs, it's a community-maintained wiki article. An Nginx maintainer called the wiki article "questionable" in https://trac.nginx.org/nginx/ticket/1316. So I'm not inclined to use that as a valid source of information about implementing this.
I'm not interested in implementing a parser for these header values, it's obviously non-trivial to get it correct, especially because it's a security sensitive header. There's also the concern that we'd need to configure a toggle/mode to choose whether And it would also mean changes at the HTTP-server level now that we have There's just too many unknowns here, and the design of the RFC is way too complex for little benefit since the amount of users who might use it is very small compared to
I'm aware and I agree it's not ideal, but it's a balance on the amount of complexity we're willing to take on as maintainers. This RFC is not simple. It was already a lot of effort just to get our support of
We could probably add a placeholder which does that. PRs welcome. |
Thanks Francis. Those are good points too. Hm... |
Thanks for the quick and thoughtful replies. Caddy is indeed in a deep mess in trying to parse downstream headers. There would be no ambiguity though, because each type of header should be disabled by default and only manually enabled by config settings (real-ip header name, N for the number of trusted proxies in chain in a private network, or setting the Node package forwarded-http attempts autodetection, and is easily spoofed. E.g. a service that runs on Rackspace and expects Caddy might have a particular issue when running as a middle proxy with non-TLS downstream connections, setting The benefit of Forwarded is that each forwarded-element (of key-value pairs) is from a single proxy, and should form a consistent and more easily secured record. Proxies can also easily wipe any previous One could identify the record of interest by its
This is from the above snippet and by little modification I could instead of |
Indeed, no downstream uses |
Reopening for discussion; I don't have time to get in on this right now but anyone else is welcome to continue the discussion :) |
Solved by config hackery. (forwarded) {
map {remote_host} {for} {
~^[0-9.]+$ for=${0} # IPv4 client address
~^[0-9A-Fa-f:.]+$ for="[${0}]" # IPv6 bracketed and quoted
default for=unknown # Unix socket
}
vars forwarded by="_{system.hostname}";{for};host="{hostport}";proto={scheme}
}
example.com {
import forwarded
reverse_proxy :3000 {
header_up +Forwarded {vars.forwarded}
header_up -X-Forwarded-*
}
} The code is ugly but it works and is standards compliant (assuming no double quotes or other weird characters in system hostname or host header). I am now matching the correct forwarded-element simply by proxy hostname in |
That's great, thanks for sharing. (When this issue was opened, that solution wasn't possible yet.) I imagine that has some limitations compared to the total complexity of how Forwarded works, but probably works for quite a few use cases 😃 |
That's pretty clever! I think you could probably augment it by using Also I think you might want to remove the |
I believe this is complete for sending them upstream as per option 1 below.
A good idea for option 2... Actually I thought
The choice to append is intentional; you are supposed to have a chain of forwarded-elements from each proxy (separated by commas or in multiple headers). Given how buggy HTTP servers are at handling duplicate request headers, it might however be safer to concatenate them all on a single header. Say you have Client -> CDN -> Caddy -> Server. The server receives: Forwarded: by=_cdn; for=<Client IP>; proto=https; host=<Host header from Client>
Forwarded: by=_caddy; for=<CDN IP>; proto=<CDN to Caddy>; host=<Host from CDN> Proxies can simply append their own local information to the end. In practice I can very well see an alternative where proxies strip off any earlier headers and try to forward information from downstream that they trust, i.e. instead the server only gets: Forwarded: by=_caddy; for=<Client IP>; proto=https; host=<Host header from Client> Whether that information comes from XFF, Forwarded or some other means is then up to Caddy (needs manual configuration for trust). But the specification seems to intend for the first option, and that is clearly easier for proxies.
A server that knows its proxies and is not buggy cannot be spoofed. It can either count from the end the Nth element (configured proxies count - works great on XFF), or preferably seek for Buggy servers remain a large practical risk though. A server that only sees the first header appears to work in normal use but can be spoofed. The same goes for those who don't implement the parsing and searching backwards as they should. And the worst still just read the first value from XFF by default (trivial to exploit when they don't actually have any proxies). |
RFC 7239 specifies a
Forwarded
header, which is intended to replaceX-Forwarded-*
.It would be great if Caddy supported this.
Ref:
The text was updated successfully, but these errors were encountered: