How to access a variable value set inside Lua block in proxy pass - nginx

I am new to nginx.
I have a variable set $myuser and updating the value ngx.var.myuser inside rewrite_by_lua. I want to pass this as a header in proxy_pass.
I am using proxy_add_header to add $myuser but I am not getting the updated value.
code block:
location /
{
set $myuser '';
rewrite_by_lua_block
{
local user=//code to get the updated value
//perform some redirection logic on error
ngx.var.myuser= user
}
proxy_pass "https://backend.com"
proxy_set_header myheader $myuser
}
value of $myuser is coming as empty but it's getting retrieved fine inside the lua block.

Below code worked. No need to set variable anduse it in proxy_set_header. We can set the header inside the lua block
rewrite_by_lua_block
{
local user=//code to get the updated value
//perform some redirection logic on error
ngx.req.set_header("myheader", user)
}

Related

nginx upstream $http variable (exists) checking

Want to use proxy cache on selected pages, for these I've added a header variable ngx_cachekey. Other pages that is not for caching are don't have this header variable.
mysite.com/cacheable-page (has the ngx_cachekey in the header)
mysite.com/dynamic-content (has no the ngx_cachekey in the header)
I can debug this with add_header X-NGX-CacheKey $upstream_http_ngx_cachekey; , so setting the variable and giving to the nginx is working.
But i'm unable to check if the $upstream_http_ngx_cachekey exists in IF statement.
location / {
set $cacheByPass 1;
if ($upstream_http_ngx_cachekey) {
set $cacheByPass 0;
}
the $cacheByPass always returns 1 even if the $upstream_http_ngx_cachekey has value.
I'm looking for a solution similar to PHP isset() if there's any.
Thank you in advance!

nginx - Proxy to a different backend based on query param

I've got a case where I want to proxy a particular call down to a different backend based on the existence of a query param. The following is sort of what I start with
location ~ ^/abc/xyz/?$ {
proxy_pass $backend_url;
}
What I'd like to do is check for a query param foo (or even just the existence of that string anywhere). So I thought I could do this
location ~ ^/abc/xyz/?$ {
set $backend_url "somelocation"
if ($request_url ~ .*foo.*) {
set $backend_url "someotherlocation"
proxy_pass $backend_url
}
proxy_pass $backend_url;
}
But this doesn't seem to actually proxy to the new location. Am I doing something wrong with my code, or is the whole approach wrong?
I don't know why are you using two proxy_pass directives, this block should do it in a logic you described:
location ~ ^/abc/xyz/?$ {
set $backend_url "somelocation";
if ($request_url ~ \?(.*&)?foo(=|&|$)) {
set $backend_url "otherlocation";
}
proxy_pass $backend_url;
}
I slightly modified your regex to match only request URLs where foo is a query argument name and not a query argument value (or its part). However I'd rather use map directive for this purpose (map block should be placed outside the server block):
map $arg_foo $backend_url {
"" somelocation; # if the value is empty
default otherlocation; # otherwise
}
server {
...
location ~ ^/abc/xyz/?$ {
proxy_pass $backend_url;
}
}
Pay an attention you may need to define a resolver for this configuration to work (some additional info about this can be found here).

nginx match request body without using lua module

is there a way in nginx to do something based on if the request body has a string or not?
I am sure i can do it with using Lua module..
I am try to find out if there is a way using nginx alone.
I am hoping something like below will work.
location /students-api {
if ($request_body ~* "(.*)specialstudent(.*)" ) {
set $student_status 'special';
}
// and use student_status for some logic
}
I think it should work, however it needs to be tested. In practice I used $request_body only for logging, not sure if it is available at the rewrite stage of request processing. Here is an official description which says:
The variable’s value is made available in locations processed by the proxy_pass, fastcgi_pass, uwsgi_pass, and scgi_pass directives when the request body was read to a memory buffer.
Additionally, you don't need those capture groups to check a variable for substring presence if you don't use them later (in fact you just wasting resources to keep them in memory), just if ($request_body ~* "specialstudent") { ... } should be enough.
Update
Here is another approach that has more chances to work since proxy_add_header directive is definitely executed later than the rewrite stage of request processing:
map $request_body $special {
~*"specialstudent" "special";
# otherwise '$special' variable value will be empty
}
server {
...
location /students-api {
...
proxy_set_header X-Student-Status $special;
...
}
}
Update 2
After testing all of this, I can confirm that the if approach does not work:
server {
...
location /students-api {
if ($request_body ~* "specialstudent") {
set $student_status "special";
}
proxy_set_header X-Student-Status $student_status;
...
}
}
As being expected, the $request_body variable doesn't get initialized at the rewrite stage of request processing. However, the map approach work as expected:
map $request_body $student_status {
~*"specialstudent" "special";
# otherwise '$special' variable value will be empty
}
server {
...
location /students-api {
proxy_set_header X-Student-Status $student_status;
...
}
}
What really surprises me is that the following example doesn't set any of two headers:
map $request_body $student_status {
~*"specialstudent" "special";
# otherwise '$special' variable value will be empty
}
server {
...
location /students-api {
if ($request_body ~* "specialstudent") {
set $student_special "special";
}
proxy_set_header X-Student-Status $student_status;
proxy_set_header X-Student-Special $student_special;
...
}
}
Somehow accessing the $request_body variable at the early rewrite stage of request processing leads the map translation to stop working too. I didn't have an explanation of this behavior for now and would be grateful if someone could explain what happened here.
Update 3
I think I'm finally found an explanation of what happened with the last example in the Nginx Tutorials written by Yichun Zhang, the author of famous lua-nginx-module and the OpenResty bundle:
Some Nginx variables choose to use their value containers as a data cache when the "get handler" is configured. In this setting, the "get handler" is run only once, i.e., at the first time the variable is read, which reduces overhead when the variable is read multiple times during its lifetime.
Looks like the $request_body variable behaves exactly this way, if being accessed at the early NGX_HTTP_REWRITE_PHASE (see the request processing phases description). Its value, if being read during that phase, gets cached as an empty value and became useless during the later request processing phases.

Nginx access_by_lua_block not executed in case of 3XX redirects

I am using the access_by_lua_block in my nginx configuration to add/modify custom request headers (let's say ngx.req.set_header("foo", "bar")). I am accessing these headers within the header_filter_by_lua_block as ngx.var["http_foo"] just before returning any response to the client.
This works fine in case of passing the request to upstream, however it doesn't work in case of redirects.
So basically,
This works (No redirect).
location /abc {
proxy_pass some_upstream;
access_by_lua_block {
ngx.req.set_header("foo", "bar")
}
header_filter_by_lua_block {
ngx.header["foo2"] = ngx.var["http_foo"] # this is correctly getting the value of "foo" header set above
}
}
This doesn't work (With Redirect)
location /abc {
access_by_lua_block {
ngx.req.set_header("foo", "bar")
}
header_filter_by_lua_block {
ngx.header["foo2"] = ngx.var["http_foo"] # this is not getting the value of "foo" header set above
}
return 301 xyz.com;
}
The access_by_lua_block is not getting executed in case of redirects only (return 301 statement). I don't understand why so? Because access_by_lua_block has a execution priority before the content phase (link)
As far as I understand, execution of return directive happens within the rewrite phase, and access phase doesn't executed at all in this case. You can try to change access_by_lua_block to rewrite_by_lua_block and see what happens.
Update
First attempt to solve the problem gives nothing. Indeed, as lua-ngx-module documentation for rewrite_by_lua states:
Note that this handler always runs after the standard ngx_http_rewrite_module.
What else you could try is to do the redirect inside the rewrite_by_lua_block:
location /abc {
rewrite_by_lua_block {
ngx.req.set_header("foo", "bar")
ngx.redirect("xyz.com", 301)
}
header_filter_by_lua_block {
ngx.header["foo2"] = ngx.var["http_foo"] # this is not getting the value of "foo" header set above
}
}

Nginx: Reject request if header is not present or wrong

If I have the headers: X_HEADER1 & X_HEADER2, I want to reject all requests if either of these headers are not set or do not contain the correct values. What is the best way to do this?
Thanks
You can use two IF statements either before or in the location block to inspect the headers and then return a 403 error code if it is present. Alternatively, you can use those IF statements to rewrite to a specific location block and deny all in that location:
if ($http_x_custom_header) {
return 403;
}
Reference:
https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
https://nginx.org/en/docs/http/ngx_http_access_module.html
Adding more detail per comment/request:
if ($http_x_custom_header) {
return 405;
}
this looks to see if header exists
if you want to check to see if the correct values exist, then you first need to map the correct values to a variable.
map $http_x_header $is_ok {
default "0";
Value1 "1";
Value2 "1";
Value3 "1";
}
if ($is_ok) {
return 405;
}
this first maps the header value to whether or not its ok, then checks to see if the variable is ok.
EDIT: Removed semicolon after map block since this causes an error.
I researched a lot to solve a simple problem: Only allow proxy_pass if request have a specific token in the header. I tried all the answers here and nothing worked how I liked. My final solution is:
location /api {
proxy_http_version 1.1;
if ($http_authorization != "Bearer 1234") {
return 401;
}
proxy_pass http://app:3000/;
}
References:
NGINX not equal to
nginx - read custom header from upstream server
https://serverfault.com/questions/490760/nginx-location-exact-match-matches-beyond-arguement
https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
If you want to allow the HTTP requests only based on some valid header values which are placed inside the response header, one possible way is to use OpenResty tool to apply such restrictions.
The following example allow access to only requests having values "name1" or "name2" for header1:
header_filter_by_lua '
local val = ngx.header["header1"]
if val then
if (val ~= "name1") and (val ~= "name2") then
return ngx.exit(400)
end
end
';

Resources