conditional routing with nginx based on referer - nginx

I need to route traffic based on the http request origin. I have two environments and we need to redirect every http request for "/us-en" to Environment1 and others to Environment2 using "$http_referer".
Redirection based on location works.
location ~ /us-en {
proxy_pass Environment1;
proxy_set_header Host Environment1;
}
With '$http_referer' the below option does not work. Request your suggestion on the same.
if ($http_referer ~ ^https?://dev.xyz.com/us-en){
rewrite ^/us-en(/*)$ HOME_PAGE$1 break;
proxy_pass Environment1;
}
Error: nginx: [emerg] "proxy_pass" directive is not allowed here in /opt/nginx/conf/nginx.conf.
Note: By default all the traffic goes to Environment2 as an upstream configuration is present.

# needed if your proxy destination specified with domain name instead of IP address
resolver 8.8.8.8;
location /home/ {
proxy_set_header Host HOST1;
# setup other proxied headers if needed
if ($http_referer ~ ^https?://dev.xyz.com/home) {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_pass https://HOST1:8080; # this can be specified by IP address
}
}
With such configuration requests to your_domain.com/home/path/file from dev.xyz.com/home/... (but not from dev.xyz.com/any/other/path!) will be proxied to https://HOST1:8080/HOME_PAGE/path/file. If you specify your proxy destination with domain name instead of IP address, you'll need to specify the additional parameter resolver in your server config. You can use your local name server if you have one, or use something external like Google public DNS (8.8.8.8) or DNS provided for you by your ISP. Anyway such configuration leads to additional DNS lookups, so if you can, specify your proxy destination with IP address.
Update
There is another way to do it with the valid_referers directive:
# needed if your proxy destination specified with domain name instead of IP address
resolver 8.8.8.8;
location /home/ {
proxy_set_header Host HOST1;
# setup other proxied headers if needed
valid_referers example.com/home;
if ($invalid_referer = "") {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_pass https://HOST1:8080; # this can be specified by IP address
}
}
Update # 2020.11.11
Besides this answer somehow achieved a score of 5, the given solution has an extremely bad design (it isn't a good approach to have different content handlers in the location and the nested if block; moreover, having an if block with any directive other than from the nginx rewrite module should be avoided if possible) and won't work at all on early nginx versions (I wanna cry when I look at some of my early answers). An original OP question was
The logic should be like below but has some syntax mistakes.
if ($http_origin ~ '^http?://(dev.xyz.com/home)') {
set $flag 'true';
}
if ($flag = 'true') {
location /home/ {
proxy_pass "https://HOST1:8080/HOME PAGE/";
}
}else{
Do Not proxy pass
}
It is unclear what do not proxy pass means. If it means returning some HTTP error (for example, HTTP 403 Forbidden), it can be done with the following configuration:
location /home/ {
if ($http_referer !~ ^https?://dev.xyz.com/home) {
return 403;
}
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_set_header Host HOST1;
# setup other proxied headers if needed
proxy_pass https://HOST1:8080; # this can be specified by IP address
}
If do not proxy pass means to serve the request locally, the solution is more complex:
map $http_referer $loc {
~^https?://dev.xyz.com/home loc_proxy;
default loc_local;
}
server {
...
location /home/ {
try_files /dev/null #$loc;
}
location #loc_proxy {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_set_header Host HOST1;
# setup other proxied headers if needed
proxy_pass https://HOST1:8080;
}
location #loc_local {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
root /path/to/required/page;
...
}
The try_files /dev/null #the_named_location; trick is taken from this excellent answer.
However now the edited OP's question states for a different requirements, which also could be achieved with the map directive help:
map $http_referer $environment {
~^https?://dev.xyz.com/home Environment1;
default Environment2;
}
server {
...
location /home/ {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_set_header Host $environment;
# setup other proxied headers if needed
proxy_pass https://$environment;
}

Related

Replace first part of nginx $uri before proxy_pass

I have following location ngix config:
location /bar {
proxy_pass http://bar-api/api;
}
I can test it with curl
$ curl foo.com/bar/test
{"test passed":true}
But IP address of internal server bar-api could change, so I can not specify it directly as above.
I understand, I need to use variables in the location block and specify resolver. Resolver is already specified in http block of nginx config resolver 192.168.0.11 valid=10s;
I'm not sure how to modify location block, so it will work the same way as before.
I tried this:
location /bar {
set $target http://bar-api/api;
proxy_pass $target;
}
But test fails:
$ curl foo.com/bar/test
<getting 404>
Also tried:
location /bar {
set $target bar-api;
proxy_pass http://$target/api;
}
Test still fails:
$ curl foo.com/bar/test
<getting 404>
Probably $uri should be modified with regex. This part /bar/test should be /api/test
So I could use it in location.
location /bar {
set $target bar-api;
<some magic here>
proxy_pass http://$target/$modified_uri;
}
But how to do it?
Use rewrite...break to make the URI change. For example:
location /bar {
set $target bar-api;
rewrite ^/bar(.*)$ /api$1 break;
proxy_pass http://$target;
}
See this document for details.

How to drop path in rewrite and proxy pass the args in nginx

Example request - http://localhost/iframe?ip=192.168.0.237
I want to proxy pass the request to the value of IP and remove the path and args after localhost/ .
Ideally the proxy_pass should point to 192.168.0.237 and the URL should be http://localhost/.
localhost /iframe {
rewrite ^/(iframe/.*)$ http://localhost/ permanent;
proxy_pass $arg_ip;
}
I'm not sure whether rewrite is the proper way to address this problem.
I would use the argument ip and a rewrite to remove the iframe location
server {
listen 8085;
location /iframe {
rewrite ^/iframe(.*)$ /$1 break;
proxy_pass http://$arg_ip;
}
}
server {
listen 8080;
location / { return 200 "$host$uri"; }
}
Security Notice
I just have a feeling you should whilelist the upstream servers accepted as arguments. If not this will be a wildcard proxy to every single http-server reachable in the network. This is a easy to use SSRF attack vector. So please add some extra layer of security.
SSRF Explained:
Let's say we use this configuration without any further security. Given the folowing NGINX config:
server {
listen 8085;
location /iframe {
rewrite ^/iframe(.*)$ /$1 break;
proxy_pass http://$arg_ip;
}
}
# Server for iframe service
server {
listen 8080;
root /usr/share/nginx/;
location / { return 200 "$host$uri\n"; }
}
# Private Server Section here!
server {
listen 8086;
allow 127.0.0.1;
deny all;
.....
location / {
index welcome.html;
}
}
Trying to reach the secret server directly
curl -v EXTERNALIP:8086
will fail with HTTP 403.
The NGINX will just allow connections form localhost/127.0.0.1 as defined in the allow/deny directives.
But lets try the iframe with the ip argument.
$# curl localhost:8085/iframe?ip=127.0.0.1:8086
Welcome to our very secure server! Internals only!
It prints the content of the secret server. Whitlisting a proxy-pass like this is never a good idea regardless its working or not.

nginx as rate limiter based on http body

I'm evaluating nginx to act as rate limiter for a multi tenancy REST API system. I need to limit API calls by tenant-id.
For example i want to allow 100 r/s for tenant1 and only 50 r/s for tenant2.
It can be easily achived when there are differant urls like: "me.com/tenant1/api" and "me.com/tenant2/api" (with the location directive).
But, in my case the urls are the same for all tenants "me.com/api" (I can't change this).
To find the tenant-id I need to extract a JSON attribute from the Body of the request, and then check the DB for the real tenant-id.
Is it possible to limit_req with my requirements?
Thank for the help!
I decided to build another service getTenant for parsing the body and extracting the Tenant from the DB. This service is called internally by Nginx.
I'm not sure if that is the best nginx (/openresty) solution, but this is what i came up with:
limit_req_zone t1Limit zone=t1Zone:10m rate=200r/s;
limit_req_zone t2Limit zone=t2Zone:10m rate=90r/s;
server {
location /api{
content_by_lua_block {
ngx.req.read_body();
local reqBody = ngx.req.get_body_data()
local res = ngx.location.capture("/getTenant", {method=ngx.HTTP_POST,body=reqBody});
local tenantId= res.body;
if tenantId== "none" then
ngx.log(ngx.ERR, "Tenant not found!");
ngx.say(tenantId);
else
ngx.req.set_header("x_myTenantId", tenantId)
local res2 = ngx.location.capture("/" .. tenantId .."/doApi", {method=ngx.HTTP_POST,body=reqBody});
if res2.status == ngx.HTTP_OK then
ngx.say(res2.body);
ngx.exit(res2.status);
else
ngx.status = res2.status
ngx.exit(res2.status)
end
end;
}
}
location /getTenant {
internal; #this is not accessible from outside.
proxy_pass http://UpStream1/getCustomer;
proxy_set_header X-Original-URI $request_uri;
}
location /tenant1/doApi {
internal; #this is not accessible from outside.
# Proxy all requests to the AReqUpStream server group
proxy_pass http://UpStream2/doApi;
limit_req zone=tenant1Zone burst=25;
limit_req_log_level notice;
}
location /tenant2/doApi {
internal; #this is not accessible from outside.
# Proxy all requests to the AReqUpStream server group
proxy_pass http://UpStream2/doApi;
limit_req zone=tenant2Zone burst=10 ;#nodelay;
limit_req_status 409;
limit_req_log_level notice;
}
}
Basically, when me.com/api is called, a new subrequest is issued to service /getTenant. The response of that call is used to build another subrequest call to the /tenant[X]/doApi service. That way i can define locations per tenant and provide different rate_limis to each.
Comments on that are more than welcome!

Nginx location part not being replaced in proxy_pass when using variable

I have a service listening on myservice.mycompany.local
We're proxifying request like this
server {
listen 80;
location /myservice/ {
proxy_pass http://myservice.mycompany.local/;
}
}
it all works fine requests on public.mycompany.com/myservice/api/1/ping are correctly transformed into request to http://myservice.mycompany.local/api/1/ping as there is the trailing /
but now if we try to use a variable
server {
listen 80;
set $MY_SERVICE "myservice.mycompany.local";
location /acm/ {
proxy_pass http://$MY_SERVICE/;
}
}
the local service will only receive a requests to / with the URI part being lost
I've been able to reproduce this "problem" with several version of nginx
1.8.1-1~wheezy
1.4.6-1ubuntu3.5
I'm able also to reproduce it locally by replacing the proxified service by a simple nc -l 127.0.0.2 8080 and using it as the value of my variable, so it really seems to be something happening inside nginx
And this behaviour is not covered in http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
You may have discovered an undocumented feature, but you can always use a rewrite ... break instead of proxy_pass aliasing:
server {
listen 80;
set $MY_SERVICE "myservice.mycompany.local";
location /acm {
rewrite ^/acm(/.*)$ $1 break;
proxy_pass http://$MY_SERVICE;
}
}

configuring proxy_pass multiple params with ngnix

I need to set proxy_pass for the below url pattern.
location ~ ^/hosts/bu/(.*)/app/(.*)$ {
proxy_pass http://appserver.cnma.com:3000/hosts/bu/$1/app/$2;
}
When I try with the URL it does not pass the second parameter correctly. Please let me know where I am going wrong.
Orig docs 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;
}
So there is enough:
proxy_pass http://appserver.cnma.com:3000$request_uri;
you can use named capture in nginx while using regular expressions
location ~ ^/hosts/bu/(.*)/app/(.*)$ {
proxy_pass http://appserver.cnma.com:3000/hosts/bu/$1/app/$2;
}
You can modify block to below like this
location ~ ^/hosts/bu/(?<loc1>.+)/app/(?<loc2>.+)$ {
proxy_pass http://appserver.cnma.com:3000/hosts/bu/$1/app/$loc2;
}

Resources