[Bug] IPs without /subnet and domain names in TRUSTED_PROXY are ignored for REMOTE_USER purposes
Describe the bug
With env TRUSTED_PROXY=192.168.1.1, or TRUSTED_PROXY=gateway.localdomain, where gateway.localdomain resolves via DNS to 192.168.1.1, FreshRSS does not trust headers like Remote-User from 192.168.1.1 (the proxy). This behavior is inconsistent with Apache. FreshRSS only works with 192.168.1.1/32 with an explicit /32 suffix.
To Reproduce
- A DNS entry is needed to repro the second part. Without it, the best way is to edit
/etc/hoststo add192.168.1.1 gateway.localdomain(or the Docker equivalent) - Run FreshRSS with env var
TRUSTED_PROXY=192.168.1.1orTRUSTED_PROXY=gateway.localdomain. This can be done with Docker env or Kubernetes - Set up a reverse proxy on 192.168.1.1, or whatever internal IP, in front of FreshRSS, with authentication that sends
Remote-User: footo FreshRSS. Authelia can do this. - In FreshRSS admin settings, change authentication method to HTTP headers (external authentication)
- After authenticating with the proxy, FreshRSS shows:
Error 403 - Forbidden
You don’t have permission to access this page [HTTP Remote-User=foo ; Remote IP address=192.168.1.1] - In Apache logs (stderr), observe that the real IP address is logged for each request, instead of 192.168.1.1.
Honestly, way too many steps to repro. I think it's probably possible to take some shortcuts by carefully constructing a curl command that sends a request with Remote-User header set. With enough fiddling with internal / public IPs, it's probably possible to make the request appear to come from an IP address other than 127.0.0.1, thus reproducing the issue without the trouble of setting up Authelia at all. I've also pinpointed the root cause below if you want to skip the trouble.
Expected behavior
I expect the request to succeed and Remote-User to be accepted. In fact, setting TRUSTED_PROXY=192.168.1.1/32 does exactly that but requires hard-coding the IP address, making it inconvenient if the proxy IP is not fixed. Setting TRUSTED_PROXY=192.168.1.1 looks like it should work, but it doesn't.
My expectation came from this sentence from documentation ./docs/en/admins/09_AccessControl.md:
You may alternatively pass a
TRUSTED_PROXYenvironment variable in a format compatible with Apache’smod_remoteipRemoteIPInternalProxy.
Apache's linked documentation gave this example, among others: RemoteIPInternalProxy gateway.localdomain. And Apache itself works just fine with that -- see repro step 6 above. Apache documentation also define the syntax as RemoteIPInternalProxy proxy-ip|proxy-ip/subnet|hostname ... -- see how proxy-ip is called out as an alternative to proxy-ip/subnet. But FreshRSS code only parses the variant with an explicit /subnet part.
FreshRSS version
Tested on both 1.29.1 and 1.30.0 but there may be other affected versions
System information
- Database version: PostgreSQL 17
- PHP version: PHP 8.4.21
- Installation type: Docker
- Web server type: Apache
- Device: All client devices
- OS: Any
- Browser: All browsers
Additional context
Internally, Apache runs a DNS lookup to convert domains to IP addresses at config load time.
Such logic isn't present in FreshRSS. Instead, FreshRSS assumes each entry is a CIDR and attempts to parse them that way. The parsing fails silently, ignoring domain names and IPs without slashes.
It took me quite a while to figure out that FreshRSS has its own parsing logic despite linking to Apache docs and writing Apache config files for other reasons. I didn't realize FreshRSS didn't simply trust the remote IP from Apache mod_remoteip and spent a lot of time staring at Apache logs and thinking "but this is the right remote IP and Apache mod_remoteip did not log the proxy as untrusted".
If this is worth noting, I can draft a PR to add one sentence about this caveat to said docs page. Most use cases can be worked around with hard-coded CIDRs. The hard part is realizing that this limitation exists.
Also, adding a warning when FreshRSS fails to parse TRUSTED_PROXY seems in general a good idea.
I'm personally rusty in PHP and don't know how to handle the domain name properly even if we decide we want to match Apache2 behavior. Running DNS lookup per request sounds like a bad idea so the fix won't be as simple as modifying the linked loop. Caching the resulting IPs require a much bigger change. I bet I'm also missing a lot of context as to why FreshRSS doesn't simply trust Apache's own handling of RemoteIPInternalProxy. I did search the issue tracker and found nothing beyond the introduction of the TRUSTED_PROXY env and the fact that parsing it seems hard.
Source: FreshRSS/FreshRSS