Trouble rewriting urls - nginx

I'm using nginx as a reverse proxy for a service running on port 8080 of the local machine. Additionally I need to prefix paths sent to the upstream with /vp. This is simple and I have a working location block for it:
location ~ ^/(.*?)$ {
proxy_pass $scheme://127.0.0.1:8080/vp/$1;
proxy_set_header Host $host;
proxy_redirect $scheme://$host/vp/ $scheme://$host/;
}
So the above would support a url like example.com/resource and work perfectly.
However, I also want to support urls like example.com/vp/resource. For this I have to write another location block or else it would passed to the upstream as /vp/vp/resource which doesn't work.
location ~ ^/vp/(.*?)$ {
rewrite /vp(.*?)$ /$1;
}
The above works and now I have support for urls like example.com/vp/resource.
But I have one last thing I'd like to fix. When a user would access example.com/vp/resource I want the url in the browser to be rewritten to just example.com/resource. My above configuration does not do this and I do not know how to modify it so that it does. I thought the point of rewrite was to rewrite urls seen in the browser but that doesn't seem to be the case.

First I tried change this:
location ~ ^/vp/(.*?)$ {
rewrite /vp(.*?)$ /$1;
}
to this:
location ~ ^/vp/(.*?)$ {
return /$1;
}
And this mostly worked. All URIs starting with /vp would be redirected to ones without which would change the url in the browser. However, this had the side effect of making POST requests not work. This is because the POST request never actually makes it to the upstream server. Instead the POST returns the 301 redirect immediately, without a proxy pass. The browser then GETS the redirect Location and that's the end.
So I needed to selectively return a true 301 only when the request was NOT a POST and when it was I needed to proxy_pass it. After reading the If is Evil I learned I can't use a proxy_pass inside of an if (inside of a location), only return and rewrite. Doing reading on rewrite, I learned that it rewrites the URI of a request but doesn't send it back to the client immediately. Instead it runs the newly rewritten URI against all the location blocks and executes whichever one matches. So in my case I just needed to use a rewrite to execute my original location block.
My final config ended up looking like this:
location ~ ^/vp/(.*?)$ {
if ($request_method = POST ) {
rewrite /vp(.*?)$ /$1;
}
if ($request_method != POST) {
return 301 /$1;
}
}
location ~ ^/(.*?)$ {
proxy_pass http://127.0.0.1:8080/vp/$1;
proxy_set_header Host $host;
proxy_redirect $scheme://$host/vp/ $scheme://$host/;
}

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 pass_proxy with variables

I'm having trouble making nginx proxy an url with variable to a service within kubernetes.
Url looks like this:
http://localhost/user?username=Dave
I expect this url to take me to a subpage /user, which will read ?username=Dave and then fetch data from a database. However this takes me to the home page of the application(/ instead of /user) and does not read the variable even though url includes /user?username=Dave.
My current nginx config file looks like this:
server {
listen 0.0.0.0:80;
server_name localhost;
location / {
proxy_pass http://${FLASK_APP}:8080/;
}
location /user {
proxy_pass http://${GO_APP}:8000/;
}
}
I have read that location /user will match the url I'm passing. What is wrong with it? Or do I need to add something to proxy_pass http://${GO_APP}:8000/; or location /user?
As noted in the comments, the issue arises because you are using a variable in the proxy_pass target. As also noted in the comments, this question is related. As the answer referencing the docs states:
A special case is using variables in the proxy_pass statement: The
requested URL is not used and you are fully responsible to construct
the target URL yourself.
This means that you either need to use a static proxy_pass target, such as
// note that I added the forward slash
location /user/ {
proxy_pass http://destination:8000/;
}
Or as an alternative, I believe you can do it this way also
location /user/ {
proxy_pass http://${GO_APP}:8000/user$is_args$args;
}

Redirect/Rewrite (in nginx) Requests to Subdirectory to S3-Compatible Bucket

I am attempting to get nginx to redirect/rewrite requests to a specific subdirectory so that they are served by an S3-compatible bucket instead of the server. Here is my current server block:
{snip} (See infra.)
Despite fiddling with this for some time now, I've only been able to get it to return 404s.
Additional Information
https://omnifora.com/t/redirect-rewrite-in-nginx-requests-to-subdirectory-to-s3-compatible-bucket/402
Attempts Solutions So Far
rewrite
rewrite ^/security-now/(.*) $scheme://s3.us-west-1.wasabisys.com/bits-podcasts/security-now/$1;
return
return 302 $scheme://s3.us-west-1.wasabisys.com/bits-podcasts/security-now/$1;
proxy_pass
proxy_set_header Host s3.us-west-1.wasabisys.com;
proxy_pass $scheme://s3.us-west-1.wasabisys.com/bits-podcasts/security-now/$1;
You can't use rewrite for cross-domain redirections, for this case you must use proxy_pass, for example:
location ~ ^/directory1/(.*) {
proxy_set_header Host s3.us-west-1.wasabisys.com;
proxy_pass $scheme://s3.us-west-1.wasabisys.com/target-bucket/security-now/$1;
}
Note that if you specify your server with domain name instead or IP address, you'll need to specify additional parameter resolver in your server configuration block, for example:
server {
...
resolver 8.8.8.8;
...
}
Update.
It seems I was wrong stating that you can't use rewrite for cross-domain redirections. You can, but in this case your user got HTTP 301 redirect instead of "transparent" content delivery. Maybe you got 404 error because you missed a $ sign before scheme variable?

Nginx - how to return a proxy generated file?

Description:
I want to implement an http server (using nginx) that serves static files.
If the requested file doesn't exist, nginx shall send a request to a service (REST API) that will create the file and return its path.
After that, I want nginx to return the static file that was created.
Question:
What is the best way to return the file after its creation?
So far I managed to do this by changing the REST API in order to return the created file path with the 302 status code and with a location header as a redirect, but I am not sure if this is a good thing to do. Is it?
Is there any nginx-side solution for this? Do I have to create a custom module?
Conf file:
http {
server {
listen 80;
location /files {
try_files $uri #rest;
}
location #rest {
rewrite ^(.*)$ /api/ break;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://localhost:8080;
}
}
}
Edit: Actually, on balance, this should be even simpler:
location #rest {
...
proxy_intercept_errors on;
error_page 404 = $uri;
}
Configure the named location to intercept an "error" coming back (I chose 404), and then using the error_page directive will cause the given URI to be loaded again. Since the file now exists, the request should succeed.
Side note: I had thought try_files $uri #rest $uri would have worked, but an internal redirection only happens for the last argument.
The simplest option here is probably for your REST service to use X-Sendfile/X-Accel to return the relevant URI that Nginx should serve once the file is created. Your REST service could return the target URI using the header X-Accel-Redirect.
In your case, your API could actually just return the same URI it received as the X-Accel-Redirect header, and then Nginx would re-use the same location block and find the file for the subrequest occurring.
If this fails, however, using an internal Nginx location as per the examples at http://wiki.nginx.org/XSendfile and http://wiki.nginx.org/X-accel:
location /files-protected {
internal;
root /path/to/files;
}
and returning the relevant URI to that would also work.

Resources