Multiple variables in a single map directive - nginx

I have the following map based on the value of a cookie:
map $cookie_version $version_host {
default 172.17.0.1:8001;
2.65.1 172.17.0.1:8001;
2.65.2 172.17.0.1:8003;
}
However I need the same map for the value of a header:
map $http_app_version $version_api_host {
default 172.17.0.1:8001;
2.65.1 172.17.0.1:8001;
2.65.2 172.17.0.1:8003;
}
Is there a way to not duplicate the map?
My server block:
server {
listen 443 ssl;
ssl_certificate /etc/ssl/nexello.crt;
ssl_certificate_key /etc/ssl/nexello.key;
server_name _;
proxy_set_header Host $host;
proxy_set_header X-FORWARDED-FOR $remote_addr;
charset utf-8;
location / {
# Web based access
# Cookies are used instead of a header because the request passes through here before any javascript can even set a Header.
if ($cookie_token) {
proxy_pass https://$version_host;
}
# API based access
# Here we use headers because anyone that is using the API has full control of the headers.
if ($http_authorization) {
proxy_pass https://$version_api_host;
}
# Auth
# In case there is no header or cookie we assume that it has no access token and should be redirected to the auth machine.
proxy_pass https://172.17.0.1:7001;
}
}

You could use
map $cookie_version$http_app_version $version_host {
default 172.17.0.1:8001;
# your complicated regex here since two variables will be concatenated.
}
and use $version_host in proxy_pass.
Also offtopic, since you're using if be careful with it
https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/

I somewhat solved this as while there still is two maps, the options are stored in another file so there won't be any reptition:
map $cookie_version $version_cookie_host {
include /etc/nginx/versions.conf;
}
map $http_authorization $version_header_host {
include /etc/nginx/versions.conf;
}
Contents of /etc/nginx/versions.conf:
default 172.17.0.1:8001;
2.65.1 172.17.0.1:8001;
2.65.2 172.17.0.1:8003;

Related

replace part of request_uri before passing it to proxy_pass

i'm trying to replace specific part or request_uri using rewrite, but it won't work for some reason
example url: http://example.com:3000/?soft=55191&src1=changethis&src2=HOME&type=0&id=7700458
server {
server_name example.com;
listen 3000;
location / {
resolver 8.8.8.8;
rewrite ^(?<=&src1=)(.*)(?=&src2)$ changewiththis$1 break;
proxy_pass http://example2.com;
}
}
so the Goal here is to replace the exact string between 'src1=' and '&src2' so it can be passed to proxy_pass with the changed string
The location and rewrite directives use a normalised URI which does not include the query string (anything from the ? onwards).
To manipulate the query string, you will need to look at the $request_uri or $args variables, or the individual parameters using the $arg_ family of variables (e.g. $arg_src1).
The simplest solution may be to use a map directive to manipulate $request_uri before passing the new value upstream.
For example:
map $request_uri $changethis {
default $request_uri;
~(?<prefix>.*[?](|.*&)src1)=[^&]*(?<suffix>.*)$ $prefix=newvalue$suffix;
}
server {
...
location / {
resolver ...;
proxy_pass http://example.com$changethis;
}
}
See this document for details.

conditional routing with nginx based on referer

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;
}

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;
}

Nginx locations configuration

Let's say I have two upstreams: upstream1 and upstream2.
Also I have API endpount /api/thing.
I want to configure two locations in nginx like this:
location /api/thing/? {
proxy_intercept_errors on;
proxy_pass http://upstream1;
}
location /api/thing/* {
proxy_intercept_errors on;
proxy_pass http://upstream2; }
In locations I used regular regexps (non-nginx configuration syntax):
Requests to /api/thing, /api/thing?param1=val1&param2=val2, etc should be proxied to upstream1
Requests to /api/thing/subthing1?a=b, /api/thing/subthing2/, etc should be proxied to upstream2
Is it possible in terms of nginx configuration?
I would write:
location = /api/thing {
# upstream1
}
location = /api/thing/ {
# upstream1
}
location /api/thing/ {
# upstream2
}

How to extract some value from cookie in nginx

I am new to Nginx and hope to get some help.
I want to extract certain data (certain fields set by my PHP scripts) from browser cookie in nginx so that I can log it. If possible, I want to do this just by modifying nginx configuration.
Any pointer/help would be greatly appreciated.
You can access cookie values by using the $cookie_COOKIE_NAME_GOES_HERE variable.
See Nginx Documentation
If anyone is using the previous answer with several different cookies in the response the correct regex is:
map $http_cookie $auth_header {
default "";
"~*OAuth.AccessToken=(?<token>[^;]+)" "Bearer $token";
}
or more general usage:
map $http_cookie $auth_header {
default "";
"~*yourCookieName=(?<variable>[^;]+)" "the value you wanna set $variable";
}
Here's an example to extract an HttpOnly cookie and pass it on to a RESTful api as an OAuth Bearer token:
http {
map $http_cookie $auth_header {
default "";
"~*OAuth.AccessToken=(?<token>.+)" "Bearer $token";
}
server {
listen 443 ssl;
ssl_certificate /etc/nginx/certs/nginx.crt;
ssl_certificate_key /etc/nginx/certs/nginx.key;
proxy_set_header Authorization $auth_header;
location / {
proxy_pass https://rest-api-host.domain.com/;
}
}
}

Resources