Open tuetenk0pp opened 1 month ago
I just had a quick look at the description of proxyaddr:
The closest untrusted address is returned.
So this means that proxyaddr()
returns the client IP, which in my case is <my_external_ip>
.
I am getting the idea that it would be better to use proxyaddr.all(req, 'uniquelocal')
(docs), lose the last object of the returned list and check the remaining list against the allowed_ips
.
@tuetenk0pp at the moment if your proxy is a private IP it would have been already trusted, and so you don't need to specify any proxy in the args.
Because we are using 'uniquelocal' we only need folks to pass a trusted proxy that matches the closest public IP range proxy before actualbudget server. In this case we only care about a single trusted endpoint, our closest one. We want to make sure the auth header is coming through a proxy we trust. (just as a form of allowing users to turn on header auth for specific routes, rather than the public way to your budget if you have one)
A better experience would likely be to not use uniquelocal
and instead include the private ranges in our allowed_ips. That way even if folks provide a private IP range (even though they don't have to), it would still work as they expected.
@twk3 I understand this is the way it should work. Unfortunately for me, it produces the described behavior. My reverse proxy is in fact in a private range.
The way I understand the validateAuthHeader()
funtion is that if no trusted proxy setting is provided, it returns true
immediately. So this actually does not correspond to the desired behavior that it would check for matches in private ranges by default.
But the main issue I see is something else. From what I understand, the proxyaddr()
function is not used properly. As it says in the docs:
The closest untrusted address is returned.
So this means, if my reverse proxy were at 192.168.1.100
, actual budget were at 192.168.1.101
, my external IP were 100.100.100.100
and I set the trusted proxy to 192.168.1.100
, proxyaddr(req, 'uniquelocal')
would return my external IP: 100.100.100.100
. But then, it uses the external IP to compare it against the trusted proxy, which in this example is 192.168.1.100
using the subnetMatch()
funktion. So of course, the comparison fails and validateAuthHeader()
returns false
.
That's why I created #379.
The way I understand the validateAuthHeader() funtion is that if no trusted proxy setting is provided, it returns true immediately. So this actually does not correspond to the desired behavior that it would check for matches in private ranges by default.
Ahh yes, that is correct. We need a change that fixes that.
What we want to be checking is the closest untrusted proxy though, and not all IPs in the request. The ideal that I would want would be if you could define the proxies in your network between the server and the proxy that's we're trying to ignore. (These maybe load balancers and gateways in your network, today we've hardcoded these to uniquelocal), and the auth proxy. (this is what we've defined as the trusted proxy in our config). Then all other IPs beyond that need to be ignored (cloudflare/cloudfront etc and the like, where you have very little control of the IP even if they are the front of your network, and of course the client ip)
Maybe we named the config poorly, maybe it should have been authProxy instead of trustedProxies. (as trusted proxies would usually be the list that goes into the ipaddr function instead of uniquelocal)
Not related to this bug but I saw your caddy log as X-Forwarded-For
header; did you do anything else with your setup?
With a simple traefik -> actual-server, I got #392
Not related to this bug but I saw your caddy log as
X-Forwarded-For
header; did you do anything else with your setup? With a simple traefik -> actual-server, I got #392
Now I also get this error.
What we want to be checking is the closest untrusted proxy though, and not all IPs in the request. The ideal that I would want would be if you could define the proxies in your network between the server and the proxy that's we're trying to ignore. (These maybe load balancers and gateways in your network, today we've hardcoded these to uniquelocal), and the auth proxy. (this is what we've defined as the trusted proxy in our config). Then all other IPs beyond that need to be ignored (cloudflare/cloudfront etc and the like, where you have very little control of the IP even if they are the front of your network, and of course the client ip)
Maybe we named the config poorly, maybe it should have been authProxy instead of trustedProxies. (as trusted proxies would usually be the list that goes into the ipaddr function instead of uniquelocal)
[!CAUTION] Still, the
ACTUAL_TRUSTED_PROXIES
setting does not work andvalidateAuthHeader()
will always check an untrusted IP against the trusted IPs and therefore always returnFalse
.
I see #399 will hopefully resolve this so there is probably no need for #379. Feel free to close that / I will close it once #399 hits main.
Verified issue does not already exist?
What happened?
I set up actual budget to allow for header authentication via authentik. So I added the group attributes in authentik:
I also added the following environment variables to my docker setup:
This is how I configured Caddy:
What error did you receive?
When I try to login into my actual budget instance, I get this error:
The server log prints:
So the header authentication works in principle, but appearently, actual budget confuses my external IP and the reverse proxy IP. I suspect the issue might be related to the
validateAuthHeader
function.https://github.com/actualbudget/actual-server/blob/1af5ab09e92954235cc3efff74c274c10fb4a2e9/src/util/validate-user.js#L32
I also checked my caddy logs, so here's an example:
As you can see, it correctly passes the
X-Forwarded-For
andX-Forwarded-Host
headers to actual budget.I was able to login after I added
<my_external_ip>
to theACTUAL_TRUSTED_PROXIES
. Obviously, this is not how it should work.Let me know if I can assist in any way.
Where are you hosting Actual?
Docker
What browsers are you seeing the problem on?
No response
Operating System
None