It's probably easiest to just show by example where I'm hitting issues, so I'll jump right in...
This snippet will work as-is for Nginx with rewrite/auth modules enabled. So hopefully this issue is quick & easy reproduce on pretty much any Nginx install...
server {
listen 8081;
add_header x-user bar;
return 200;
}
server {
listen 8080;
location = /auth {
internal;
proxy_pass http://localhost:8081;
}
location / {
auth_request /auth;
auth_request_set $foo $upstream_http_x_user;
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
}
The above example Nginx site config does the following:
- Sends any request to
/auth
via anauth_request
call - The /auth location sends the request to another server which adds a header
x-user bar
auth_request_set
sets a new var$foo
based on the upstream header value ofx-user
set in step 2 above.- A new header is set
x-test
which is set to the value of$foo
- The request proceededs to an external destination.
Response is exactly how I would expect, and confirmed the $foo
var was set correctly:
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test'
HTTP/1.1 200 OK
x-test: bar
So, here comes the problem...
I need to adjust this config so that it'll return 403 if the value of the upstream header is incorrect.
Seemed like a simple task. So I added an if{}
conditional to check the header:
location / {
auth_request /auth;
auth_request_set $foo $upstream_http_x_user;
# this 'if' is the only part added to the original config
if ($foo != bar) {
return 403;
}
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
The if
conditional evaluated true so I got a 403, which is not what I was expecting. So, this does not work:
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test'
HTTP/1.1 403 Forbidden
I realize that if is evil however I seem to be using it just to return which should be ok. I'm open to using any method to accomplish the same goal - with or without if, so I'm open to ideas!!
I have tried doing things like moving the auth_request
and/or the if
statements into the server{}
block but nothing seems to make this evaluate the way I was expecting.
Further Troubleshooting / Details:
I have verified the problem is that the if
is evaluated BEFORE the auth_request_set
is
location / {
auth_request /auth;
auth_request_set $foo $upstream_http_x_user;
if ($foo != bar) {
# x-test never gets set because $foo is null when if evaluates
add_header x-test $foo always;
add_header success false always;
return 403;
}
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test|success'
HTTP/1.1 403 Forbidden
success: false
I have verified this is not an issue if using set
instead of auth_request_set
. This works (but doesn't accomplish the goal):
# set works, but not auth_request_set
location / {
set $foo bar;
if ($foo != bar) {
return 403;
}
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
This config works. set
is evaluated before if
:
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test'
HTTP/1.1 200 OK
x-test: bar
The behavior persists even if the auth_request
is in the server{}
context:
server {
listen 8081;
add_header x-user bar;
return 200;
}
server {
listen 8080;
auth_request /auth;
auth_request_set $foo $upstream_http_x_user;
location = /auth {
internal;
proxy_pass http://localhost:8081;
}
location / {
if ($foo != bar) {
return 403;
}
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
}
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test|success'
HTTP/1.1 403 Forbidden
success: false
I've reviewed the following docs and questions:
- Can I compare a variable set by auth_request_set after the auth_request has returned in nginx?
- https://stackoverflow.com/questions/73431103/cant-access-added-headers-using-nginx-auth-request-set
- Nginx $upsteam_cache_status custom header will not appear
- auth_request doesn't block return directive, can't return status?
- https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil
- http://nginx.org/en/docs/http/ngx_http_auth_request_module.html
- http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if
- https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
The
if
is a problematic, because it is an imperative construct in otherwise declarative configuration.Therefore it doesn't always work as expected. More information can be found in IfIsEvil article.
In this case, the
set
andauth_request_set
happen in different stages of nginx request processing, and theif
processing happens between those two stages.Unfortunately I don't know how to actually do what you want in nginx. Maybe that needs to be done in the upstream server where you proxy the request to.
This doesn't answer the question, see Tero's answer here for the actual answer (spoiler:
if
is truly evil, even if using it withreturn
).However, as a functional answer providing a workaround: Here is at least a way to make invalidated requests get sent a 403. The proxy_pass endpoint is dynamic depending on the tested header. This seems to solve the use-case...