Nginx - encoding (normalizing) part of URI - nginx

I have nginx location directive which purpose is to "remove" localization prefix from the URI for the proxy_pass directive.
For example, to make URI http://example.com/en/lalala use proxy_pass http://example.com/lalala
location ~ '^/(?<locale>[\w]{2})(/(?<rest>.*))?$' {
...
proxy_pass http://example/$rest;
...
}
This way the rest variable will be decoded when passed to proxy_pass directeve. It seems to be an expected behavior.
The problem is when my URI contains encoded space %20 passed from client
http://example.com/lala%20lala
nginx decodes URI to
http://example.com/lala lala
I can see it in my error.log.
The question is - is it possible do use encoded rest variable somehow as it is passed from client?
If I am doing something completely wrong, please, suggest the right way.
Thank you.

Yes, this behaviour is expected although docs also say:
If proxy_pass is specified without a URI, the request URI is passed to the server in the same form as sent by a client when the original request is processed, or the full normalized request URI is passed when processing the changed URI:
location /some/path/ {
proxy_pass http://127.0.0.1;
}
Nginx engineers say the same: https://serverfault.com/questions/459369/disabling-url-decoding-in-nginx-proxy
However if you append $request_uri to proxy_pass (and strip locale beforehand it may work as said by Nginx engineer):
set $modified_uri $request_uri;
if ($modified_uri ~ "^/([\w]{2})(/.*)") {
set $modified_uri $1;
}
proxy_pass http://example$modified_uri;

I have had some success using the following with Confluence and other Atlassian applications behind nginx where special characters such as ( ) < > [ ] were causing issues.
location /path {
# [... other proxy options ...]
# set proxy path with regex
if ($request_uri ~* "/path(/.*)") {
proxy_pass http://server:port/path$1;
break;
}
# fallback (probably not needed)
proxy_pass http://server:port/path;
}

The set directive can do the trick. It keeps the encoding intact or rather encodes decoded string.
location ~ '^/(?<locale>[\w]{2})(/(?<rest>.*))?$' {
...
set $encoded_rest $rest
proxy_pass http://example/$encoded_rest;
...
}

Related

Replicate proxy_pass location behavior with variables

So usually when creating a nginx location it would look something like this:
location /foo/ {
proxy_pass http://example.com/;
}
With this setup, requests to /foo/bar are forwarded to http://example.com/bar which is the intended behavior.
However when trying to prevent caching of the domain name example.com or when trying to prevent nginx from crashing if the upstream host is unavailable at startup the only solution seems to be to not use the target directly in the proxy_pass directive, but to instead create a variable containing the target like this:
location /foo/ {
set $targetUri http://example.com/;
proxy_pass $targetUri;
}
But this totally changes the setup. As soon as proxy_pass contains a variable, it no longer appends anything to the target uri, as the nginx docs describe:
When variables are used in proxy_pass [...]. In this case, if URI is specified in the directive, it is passed to the server as is, replacing the original request URI.
So requests to /foo/bar are simply forwarded to http://example.com/.
When bringing $request_uri into the mix, more than what we want is appended:
location /foo/ {
set $targetUri http://example.com$request_uri;
proxy_pass $targetUri;
}
Requests to /foo/bar are now forwarded to http://example.com/foo/bar.
The only workaround I have found is to resort to regex patterns for the location:
location ~ ^/foo/(.*)$ {
set $targetUri http://example.com/$1$is_args$args;
proxy_pass $targetUri;
}
Is there any way to replicate the behavior of proxy_pass when using variables without having to regex-match the location? The reason I want to avoid regex is because the location path is based on a user input from which the location block is generated.
Remove the trailing / from your $targetUri variable so that proxy_pass does not have the "optional URI" part in its value. Then use rewrite...break to duplicate the original behaviour.
For example:
location /foo/ {
set $targetUri http://example.com;
rewrite ^/foo(.*)$ $1 break;
proxy_pass $targetUri;
}

Add a custom header while redirecting in NGINX

I am trying to redirect all API calls to an authorization service endpoint using nginx. I will need to pass a custom header in which i intend to pass the original uri or $request_uri.
Trying the below:
location /api/other {`
add_header X-Original_URI $request_uri
return 308 https://example.com/myauthservice/api/authorize
}
unfortunately the header is not getting added, need some help to see if this is correct way to do.
I tried auth_request module, proxy_pass. auth_request I cannot use, as it cannot send $request_body. Followed this, but not able store or capture the $request_body.
proxy_pass I am not able to use as it ends up like this:
https://myauthservice/api/authorize/createuser
where createuser is from https://example.com/api/other/createuser
You can prevent appending the /createuser suffix to the proxied request. As the proxy_pass documentation states:
In some cases, the part of a request URI to be replaced cannot be determined:
...
When the URI is changed inside a proxied location using the rewrite directive, and this same configuration will be used to process a request (break):
location /name/ {
rewrite /name/([^/]+) /users?name=$1 break;
proxy_pass http://127.0.0.1;
}
In this case, the URI specified in the directive is ignored and the full changed request URI is passed to the server.
Try the following location block:
location /api/other {
rewrite ^ /myauthservice/api/authorize break;
proxy_set_header X-Original_URI $request_uri;
proxy_pass https://example.com;
}

Nginx remove location path with upstream variable [duplicate]

I am working with Nginx on Docker and I want to assign each user to a different port.
First, without adding anything, my code works fine:
location /viewer/ {
proxy_pass http://xx.xxx.xxx.xxx:18080/Road/;
}
Going to "/viewer/" in URL will proxy to the port 18080, just as expected.
But if I add any variable to the proxy_pass like:
set $test 1;
proxy_pass http://xx.xxx.xxx.xxx:18080/Road/?$test;
then, first of all, the static files do not load anymore and I have to add lines like these:
location ~ \.css {
add_header Content-Type text/css;
}
location ~ \.js {
add_header Content-Type application/x-javascript;
}
After this, the static files work again but the page starts to reload infinitely.
Before I was thinking it was because I replaced the port by a variable in proxy_pass, but as I showed you it happens when I add any variable there.
What do you think I could do wrong?
Thank you for your help!
Adding a variable to proxy_pass changes it's behaviour. You will need to construct the entire URI.
In your original configuration, the URI /viewer/foo is translated to /Road/foo before passing upstream.
In your new configuration, the URI /viewer/foo is translated to /Road/?1 and the tail of the original URI is lost.
You may have more success using rewrite...break to modify the URI.
For example:
location /viewer/ {
rewrite ^/viewer(.*)$ /road$1?something break;
proxy_pass http://xx.xxx.xxx.xxx:18080;
}
See this document for details.
According to your comment, you wish to change the destination port.
For example:
location /viewer/ {
rewrite ^/viewer(.*)$ /road$1 break;
proxy_pass http://xx.xxx.xxx.xxx:$myport;
}
If you specify the upstream server by IP address, a resolver statement will not be required. But if you specify the upstream by name, you will need to define a resolver. See this document for details.

Reverse image proxy without specifying host

I have the following in my config as a reverse proxy for images:
location ~ ^/image/(.+) {
proxy_pass http://example.com/$1;
}
The problem is that not all images will be example.com images and so we need to pass in the full url. If I try:
location ~ ^/image/(.+) {
proxy_pass $1;
}
I get an error:
invalid URL prefix in "https:/somethingelse.com/someimage.png"
The question is quite vague, but, based on the error message, what you're trying to do is perform a proxy_pass entirely based on the user input, by using the complete URL specified after the /image/ prefix of the URI.
Basically, this is a very bad idea, as you're opening yourself to become an open proxy. However, the reason it doesn't work as in the conf you supplied is due to URL normalisation, which, in your case, compacts http://example into http:/example (double slash becomes single), which is different in the context of proxy_pass.
If you don't care about security, you can just change merge_slashes from the default of on to off:
merge_slashes off;
location …
Another possibility is to somewhat related to nginx proxy_pass and URL decoding
location ~ ^/image/.+ {
rewrite ^ $request_uri;
rewrite ^/image/(.*) $1 break;
return 400;
proxy_pass $uri; # will result in an open-proxy, don't try at home
}
The proper solution would be to implement a whitelist, possibly with the help of map or even prefix-based location directives:
location ~ ^/image/(http):/(upload.example.org)/(.*) {
proxy_pass $1://$2/$3;
}
Do note that, as per the explanation in the begginning, the location above is subject to the merge_slash setting, so, it'll never have the double // by default, hence the need to add the double // manually at the proxy_pass stage.
I would use a map in this case
map $request_uri $proxied_url {
# if you don't care about domain and file extension
~*/image/(https?)://?(.*) $1://$2;
# if you want to limit file extension
~*/image/(https?)://?(.*\.(png|jpg|jpeg|ico))$ $1://$2;
# if you want to limit file extension and domain
~*/image/(https?)://?(abc\.xyz\.com/)(.*\.(png|jpg|jpeg|ico))$ $1://$2$3;
default "/404";
}
Then in your proxy pass part you would use something like below
location /image/ {
proxy_pass $proxied_url;
}
I have given three different example depending how you want to handle it

Avoid nginx decoding query parameters on proxy_pass (equivalent to AllowEncodedSlashes NoDecode)

I use nginx as a load balencer in front of several tomcats. In my incoming requests, I have encoded query parameters. But when the request arrives to tomcat, parameters are decoded :
incoming request to nginx:
curl -i "http://server/1.1/json/T;cID=1234;pID=1200;rF=http%3A%2F%2Fwww.google.com%2F"
incoming request to tomcat:
curl -i "http://server/1.1/json/T;cID=1234;pID=1200;rF=http:/www.google.com/"
I don't want my request parameters to be transformed, because in that case my tomcat throws a 405 error.
My nginx configuration is the following :
upstream tracking {
server front-01.server.com:8080;
server front-02.server.com:8080;
server front-03.server.com:8080;
server front-04.server.com:8080;
}
server {
listen 80;
server_name tracking.server.com;
access_log /var/log/nginx/tracking-access.log;
error_log /var/log/nginx/tracking-error.log;
location / {
proxy_pass http://tracking/webapp;
}
}
In my current apache load balancer configuration, I have the AllowEncodedSlashes directive that preserves my encoded parameters:
AllowEncodedSlashes NoDecode
I need to move from apache to nginx.
My question is quite the opposite from this question : Avoid nginx escaping query parameters on proxy_pass
I finally found the solution: I need to pass $request_uri parameter :
location / {
proxy_pass http://tracking/webapp$request_uri;
}
That way, characters that were encoded in the original request will not be decoded, i.e. will be passed as-is to the proxied server.
Jean's answer is good, but it does not work with sublocations. In that case, the more generic answer is:
location /path/ {
if ($request_uri ~* "/path/(.*)") {
proxy_pass http://tracking/webapp/$1;
}
}
Note that URL decoding, commonly known as $uri "normalisation" within the documentation of nginx, happens before the backend IFF:
either any URI is specified within proxy_pass itself, even if just the trailing slash all by itself,
or, URI is changed during the processing, e.g., through rewrite.
Both conditions are explicitly documented at http://nginx.org/r/proxy_pass (emphasis mine):
If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive
If proxy_pass is specified without a URI, the request URI is passed to the server in the same form as sent by a client when the original request is processed, or the full normalized request URI is passed when processing the changed URI
The solution depends on whether or not you need to change the URL between the front-end and the backend.
If no URI change is required:
# map `/foo` to `/foo`:
location /foo {
proxy_pass http://localhost:8080; # no URI -- not even just a slash
}
Otherwise, if you do need to swap or map /api of the front-end with /app on the backend, then you can get the original URI from the $request_uri variable, and the use the rewrite directives over the $uri variable similar to a DFA (BTW, if you want more rewrite DFA action, take a look at mdoc.su). Note that the return 400 part is needed in case someone tries to get around your second rewrite rule, as it wouldn't match something like //api/.
# map `/api` to `/app`:
location /foo {
rewrite ^ $request_uri; # get original URI
rewrite ^/api(/.*) /app$1 break; # drop /api, put /app
return 400; # if the second rewrite won't match
proxy_pass http://localhost:8080$uri;
}
If you simply want to add a prefix for the backend, then you can just use the $request_uri variable right away:
# add `/webapp` to the backend:
location / {
proxy_pass http://localhost:8080/webapp$request_uri;
}
You might also want to take a look at a related answer, which shows some test-runs of the code similar to the above.
There is one documented option for Nginx proxy_pass directive
If it is necessary to transmit URI in the unprocessed form then directive proxy_pass should be used without URI part:
location /some/path/ {
proxy_pass http://127.0.0.1;
}
so in your case it could be like this. Do not worry about request URI it will be passed over to upstream servers
location / {
proxy_pass http://tracking;
}
Hope it helps.
In some cases, the problem is not on the nginx side - you must set the uri encoding on Tomcat connector to UTF-8.

Resources