Avoid redirect cycle when combining access_by_lua with ngx.exec to a (named) location - nginx

I want to change an existing nginx configuration in a way where I can completely "mask" the configuration and proxy everything to the upstream when a certain cookie is available (to hide a certain server).
This includes not just some location directives but basically every location directive (as opposed to set or map a variable and update n-location's try_files and more).
My basic idea was to use lua and jump into the Rewrite/Access phase like this:
access_by_lua_block {
# proceed as usual if our cookie is not detected
if ngx.var.cookie_demo ~= nil and string.len(ngx.var.cookie_demo) ~= 32 then
return
end
# proxy and return w/out further processing
ngx.exec("#ngxbackend")
return ngx.exit(ngx.HTTP_OK)
}
# proxy upstream
location #ngxbackend {
include /etc/nginx/proxy_params_demo;
proxy_pass https://demo-upstreams;
}
But this leads to an ERR with rewrite or internal redirection cycle while redirect to named location "#ngxbackend" as the named location is probably never reached because of the access_by_lua_block after it's internal redirect.
Can I solve this by use of variables and further condition checking?

Related

NGINX - different backend proxy based on query parameter

I've got a particular scenario where I'm needing to route to a different backend based on query parameter:
https://edge1.cdn.com/file.zip?{secure_link}&{tokens}&route=aws1
Where aws1 would be say http://edge1.amazonwebservices.com
and if its aws2 then proxy backend would be http://edge2.amazonwebservices.com
and so on... but I still have not figured out how to do this.
You can use map directive to get a proxy hostname from the $arg_route variable (which contains a value of the route query argument):
map $arg_route $aws {
aws1 edge1.amazonwebservices.com;
aws2 edge2.amazonwebservices.com;
...
default <default_hostname>;
}
server {
...
# if you want to proxy the request, you'd need a 'resolver' directive
resolver <some_working_DNS_server_address>;
location / {
# if you want to proxy the request
proxy_pass http://$aws;
# or if you want to redirect the request
rewrite ^ http://$aws$uri permanent;
}
}
If you don't want to serve the request without route query argument, you can omit the last default line at the map block and add the following if block to your server configuration:
if ($aws = '') {
return 403; # HTTP 403 denied
}
If you need to proxy the request you'd additionally need a resolver directive (you can read some technical details about it in this article).

nginx location rules for vue3 ssr

ISSUE
I have a Vue 3 SSR application running in a docker container. I have a second docker container containing NGINX. I need to have correct location rules in my NGINX container to pass on the correct values to my Vue3 app container.
My application URLs coming into NGINX are
/my/prefix/
/my/prefix/page1/123?name=one
/my/prefix/page2/789?id=123&name=one
/my/prefix/css/styles.css and /my/prefix/js/app.js
I need those URLs to pass onto my Vue app container as the following
/
/page1/123?name=one
/page2/789?id=123&name=one
/my/prefix/css/styles.css and /my/prefix/js/app.js
I can not figure out the correct combination to get this to work.
What I have tried
The following resolves (1): /my/prefix/ becomes /. This on its own however does not resolve (2) (3) and (4), as a result the 2nd and 3rd pages in my app show a 404 and I have no styling and js loaded.
location = /my/prefix/ {
set $upstream http://newserver:8080/;
proxy_pass $upstream;
}
I tried adding the following, with this i have (4) working, styles/js are present, because the the URLs passed on are /my/prefix/css/styles.css and /my/prefix/js/app.js. However (2/3) fail as for the routes in my app for the exact reason the styles/js is working, it passes on the URLS /my/prefix/page1/123?name=one and /my/prefix/page2/789?id=123&name=one I need it to be /page1/123?name=one and /page2/789?id=123&name=one
location /my/prefix/ {
set $upstream http://newserver:8080;
proxy_pass $upstream;
}
I tried the following to resolve (2/3). This does match the (2/3) paths only, but what is passed on to the Vue container has the path AFTER page1|page2 removed, i.e. /page1/?name=one and /page2/?id=123&name=one which is wrong.
location ~ ^/my/prefix/(page1|page2) {
set $upstream http://newserver:8080;
proxy_pass $upstream/$1$is_args$args;
}
I have spent a couple of days trying to get the right combination and I am at a complete loss of what to try next.
Does anyone have any suggestions.
Finally after 2 days of trying everything, I have a working solution
# URL (1)
# match the root page URL exactly (the = does an exact match the stops searching for rules)
# - MUST have a trailing / on the location to match our app which appears to always have it
# - MUST have a trailing / on the upstream, so it removes the matched path before passing on
# the path requested and then stops searching for any more matches.
location = /my/prefix/ {
set $upstream http://newserver:8080/;
proxy_pass $upstream;
}
# URL (2) (3)
# match the page1|page2 pages
# remove the path and pass on the paths and parameter values
# $1 references what is in the first ()
# $2 references what is in the second ()
location ~ ^/my/prefix/(page1|page2)(.*) {
set $upstream http://newserver:8080;
proxy_pass $upstream/$1$2$is_args$args;
}
# URL (4)
# match any path starting with /my/prefix/,
# pass on the url exactly as is (/my/prefix/path/path etc)
# this should match the css/js folders
location /my/prefix/ {
set $upstream http://newserver:8080;
proxy_pass $upstream;
}

How to get lua variables back into nginx variables

I have a lua script that uses lua-resty to call another service via co-sockets.
Now I would like to use the information from this call to route the request in nginx.
nginx includes the lua script in access_by_lua*
which sets the var like this:
ngx.var.blocked = '1'
and routes in the location like this:
if ( $blocked ) {
proxy_pass http://haproxy-9001;
break;
}
the problem is now that nginx does not pick up the variable change (in this phase).
if I include the lua script in set_by_lua* phase then the variable passing works but I dont have access to the co-sockets in this phase.
Any idea how to get the variable out of lua land into the nginx variable in the access_by_lua, rewrite_by_lua or content_by_lua phase so that I can use the co-socket api to make a http call?
if nginx directive is implemented by https://nginx.ru/en/docs/http/ngx_http_rewrite_module.html.
Obviously it works on rewrite phase, so your changes at access phase doesn't work.
Just don't use if. Below is snippet from one of my nginx config:
proxy_pass $scheme://$upstream$uri$is_args$args;
Just set $upstream variable at access phase and it will work at content phase (proxy_pass).
Maybe you could capture location with that proxy instead of variable, it works in access_by_lua scope
https://github.com/openresty/lua-nginx-module#ngxlocationcapture

Proxy a request - get a parameter from URL, add a header and update request URL using Nginx

I am looking for a way to do the following using Nginx:
Intercept a request
Read URL, parse it and read a value from it.
Add that value as a new request header
Update the URL (remove a particular value)
Forward the request to another server
e.g
Request URL - http://<<nginx>>/test/001.xml/25
Final URL - http://<<server>>/test/001.xml with header (x-replica: 25)
I have a nginx server setup with a upstream for the actual server. I was wondering how do I setup Nginx to achieve this ?
Since the data exists within the request URI itself (available by the $uri variable in nginx), you can parse that using the nginx lua module. nginx will need to be compiled with lua for this to work, see: openresty's nginx lua module.
From there you can use the set_by_lua_block or set_by_lua_file directive given $uri as a parameter.
In configuration this would look something like:
location / {
...
set_by_lua_file $var_to_set /path/to/script.lua $uri;
# $var_to_set would contain the result of the script from this point
proxy_set_header X-Replica $var_to_set;
...
}
In script.lua we can access the $uri variable from in the ngx.arg list (see these docs):
function parse_uri( uri )
parsed_uri = uri
-- Parse logic here
return parsed_uri
end
return parse_uri( ngx.arg[1] )
Similarly, you can modify this function or create another to make a variable with the updated $uri.

Using Lua in nginx to pass a request to FastCGI

Using nginx compiled with Lua support, how can we make a sort of sub-request to a FastCGI handler, much like nginx's fastcgi_pass directive?
What I'd like to do is something like this:
location = / {
access_by_lua '
res = ngx_fastcgi.pass("backend")
';
}
(Obviously, this doesn't work.)
I'm pouring over HttpLuaModule where I see mention ngx_fastcgi and ngx.location.capture, which, evidently, makes
non-blocking internal requests to other locations configured with
disk file directory or any other nginx C modules like ... ngx_fastcgi,
...
But then following the link of ngx_fastcgi takes me to HttpFastcgiModule which explains only nginx directives, not Lua-scriptable commands. Is ngx.location.capture the right function to use? (These requests, by the way, will be to localhost, just on a different port, like 9000 or 9001.)
How can I use Lua in nginx to forward a request, or make a sub-request, to a FastCGI endpoint?
Use the ngx.location.capture() method to perform a subrequest to a predefined location block. Then, from within the location block, perform the external, FastCGI request. Because the subrequest itself isn't actually a network operation, but is performed purely within nginx C-based environment, there's very little overhead. Further, because the FastCGI request and other "proxy_pass"-type requests are event-based, nginx can operate as an efficient intermediary.
As an example, you could have the following:
location / {
access_by_lua '
response = ngx.location.capture("/my-subrequest-handler")
if response.status == 404 then
return ngx.exit(401) -- can't find/authenticate user, refuse request
end
ngx.say(response.status)
';
# other nginx config stuff here as necessary--perhaps another fastcgi_pass
# depending upon the status code of the response above...
}
location = /my-subrequest-handler {
internal; # this location block can only be seen by nginx subrequests
fastcgi_pass localhost:9000; # or some named "upstream"
fastcgi_pass_request_body off; # send client request body upstream?
fastcgi_pass_request_headers off; # send client request headers upstream?
fastcgi_connect_timeout 100ms; # optional; control backend timeouts
fastcgi_send_timeout 100ms; # same
fastcgi_read_timeout 100ms; # same
fastcgi_keep_conn on; # keep request alive
include fastcgi_params;
}
In the above example, even though I'm performing a subrequest to "/my-subrequest-handler", the actual URL passed to the FastCGI process is the one requested by the HTTP client calling into nginx in the first place.
Note that that ngx.location.capture is a synchronous, but non-blocking operation which means that your code execution stops until a response is received, but the nginx worker is free to perform other operations in the meantime.
There are some really cool things that you can do with Lua to modify the request and response at any point in the nginx pipeline. For example, you could change the original request by adding headers, removing headers, even transforming the body. Perhaps the caller wants to work with XML, but the upstream application only understands JSON, we can convert to/from JSON when calling the upstream application.
Lua is not built into nginx by default. Instead it's a 3rd party module that must be compiled in. There's a flavor of nginx called OpenResty that builds in Lua+LuaJIT along with a few other modules that you may or may not need.

Resources