How to make a conditional proxy_pass within NGINX - if-statement

I am running our front and backend on K8s, this question is regarding our front-end setup using NGINX.
I am trying to find a way to gather metrics of my main front-end container (nginx + front-end) using a sidecar container that adjusts the stub_status running on /stub_status on port 8080 values to key value pairs for my application. Just so that I can scrape them using Prometheus on the sidecars /metrics on port 9113.
I want to block traffic for stub_staus outside of the pod, as only the sidecar needs to be able to reach it and I am trying to block /metrics from outside the platform (hence an example random 172 range address as an example). If I instead of a proxy pass (see below) use return 444 or 404 I get a nice big fat error. However, our front-end can handle 404 in a nice (graceful) way making it so you do not even exit the front-end but simply get a user friendly 404 message whilst staying in the front-end application, and end up on location/404. This also makes it appear like there is nothing on the /stub_status or /metrics. Which is nice to have.
I tried to do a rewrite ^/metrics$ to a /404 (for example) but that simply got me an NGINX 404 instead. Maybe it has something to do with the fact that /metrics runs on 9113 and there is nothing listening tot /404 on 9113. I am not sure about this.
I know the proxy pass example below is not possible within an IF statement, as I get the following error "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in". However, below is to illustrate what I am trying to do. I am just not sure how to get it to behave like this.
Thanks in advance!
HTTP Block
http {
#Block traffic from outside platform for /stub_status
map $remote_addr $block_outside_pod {
default 1;
127.0.0.1 0;
}
#Block traffic from outside platform for /metrics
map $http_x_forwarded_for $block_outside_platform {
default 1;
~^172\.30\.\d*.\d*$ 0;
}
}
Server Block
(What I am trying to accomplish)
server {
location /stub_status {
#sidecar only
if ($block_outside_pod = 1) {
proxy_pass http://localhost:8080/404;
}
stub_status;
}
location /metrics {
#platform only
if ($block_outside_platform = 1) {
proxy_pass http://localhost:8080/404;
}
}
}

After more testing I found out the following works really well:
HTTP BLOCK
http {
#Block traffic from outside platform for /stub_status
map $remote_addr $block_outside_pod {
default 1;
127.0.0.1 0;
::1 0;
}
#Block traffic from outside platform for /metrics
map $http_x_forwarded_for $block_outside_platform {
default 1;
~^172\.30\.\d*.\d*$ 0;
}
}
SERVER BLOCK
server {
listen 8080 default_server;
server_name _;
root /<insert your location>;
error_page 418 = #block_traffic;
location /stub_status {
#sidecar only
if ($block_outside_pod = 1) {
return 418;
}
stub_status;
}
error_page 418 = #block_traffic;
location /metrics {
#platform only
if ($block_outside_platform = 1) {
return 418;
}
}
location #block_traffic {
rewrite ^ /404$1;
proxy_pass http://localhost:8080;
}
location / {
index.html etc...
add_header etc...
}
This means that anything coming in on 8080 via k8s to this pod from outside the platform will get a nice fancy 404 page from our website making it seem like it is not there =)
Thanks for the headsup from the person in the NGINX Slack!

Related

Cookies with non-ASCII causing all endpoints to give status code 400

I have a website using aspnetcore 2.1 and Kestrel (IIS as proxy) and someone else on our domain sets a cookie that contains non-ASCII characters. I know this is wrong, but I can't make sure they don't and I still want the cookie (otherwise it is easy just to remove it in my web.config).
The result being: Cookie is set incorrectly on one site and when the user "unknowingly" goes to our website, our website gives an error 400 on all requests.
Is there a way of removing the bad characters in the cookie header before it hits Kestrel? I thought I could use a HttpModule/Middleware/etc to remove it, but it looks like Kestrel is the first one to get a HttpContext.
No workaround at the side of dotnetcore. If you are using nginx behind of your dotnetcore application, you can delete all of the non-ascii character like that:
set_by_lua_block $cookie_ascii {
local cookie = ngx.var.http_cookie
if cookie == nil or cookie == '' then return cookie end
local cookie_ascii, n, err = ngx.re.gsub(cookie, "[^\\x00-\\x7F]", "")
return cookie_ascii
}
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:5000;
...
proxy_set_header Cookie $cookie_ascii;
...
}
}

Do i need to declare if statement in each server block

I have a bot blocking map that I am using that makes nginx respond with 444. I have "if" blocks in each server block and it works, but i want to know if just putting it at the top in its own server block would be better, or how exactly should i be doing it?
Currently, my domainname.org.conf file has 3 server blocks. The first block does insecure and secure redirect from old domain requests to the new domain (we rebranded). The second block handles redirecting domainname.org to www.domainname.org. The third block actually has the meat and potatoes for PHP and whatnot.
here is the conf, with some data omitted and obfuscated.
server {
listen 80;
listen 443;
MY CERT STUFF IS HERE BUT I'M NOT SHARING DETAILS
server_name olddomainname.org *.olddomainname.org;
#blocks blank user_agents
if ($limit_bots = 1) {
return 444;
}
return 301 https://www.newdomainname.org$request_uri;
}
#############################################################################################
# THIS SERVER BLOCK SIMPLY ACCEPTS ANY INCOMING NON-SUBDOMAININSECURE DOMAIN AND REROUTES IT TO A SECURE ONE
# IT ONLY AFFECTS THE PROD WEBSITE. IT SHOULD NOT INTERFERE WITH ANY DEVELOPMENT SUBDOMAINS
server {
listen 80;
listen 443;
server_name newdomainname.org;
#blocks blank user_agents
if ($limit_bots = 1) {
return 444;
}
return 301 $scheme://www.newdomainname.org$request_uri;
}
#############################################################################################
# THIS SERVER BLOCK HANDLES ANY INCOMING INSECURE SUBDOMAIN
server {
listen 80;
listen 443 default ssl;
MY CERT STUFF IS HERE BUT I'M NOT SHARING DETAILS
server_name www.newdomainname.org;
#blocks blank user_agents
if ($limit_bots = 1) {
return 444;
}
## Redirect 80 to 443
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
root /var/www;
This works, but adds repetition.
i want to know if just putting it at the top in its own server block would be better
No - it would either be ignored, or it would break your existing server blocks.
nginx chooses one server block to process a request, based on the values of the listen and server_name directives. See this document for details.
If you have a lot of common code, shared across multiple server blocks, place it in a separate file and use an include statement. See this document for more.

How to avoid basic authentication for AWS ELB health-check with nginx configuration

I'm having a trouble trying to implement basic authentication for ELB healthcheck.
I've searched quite a bit to figure out the nginx file configuration to avoid 401 error shown below, which ELB returns due to basic authentication
unhealthy in target-group hogehoge due to (reason Health checks failed with these codes: [401])
I've tried to modify nginx.conf so as to avoid it, but it doesn't work.
The code below gives me [emerg] "server" directive is not allowed here error.
http {
server {
location / {
if (!$http_x_forwarded_for) {
auth_basic 'Please enter ID and password';
auth_basic_user_file /usr/src/redmine/.htpasswd;
}
}
}
}
How can I avoid 401 error by ELB healthcheck due to basic authentication?
Thanks for the help.
The easiest approach would be to create a location for the ELB, for example:
location /elb-status {
access_log off;
return 200;
}
You will just need to change the Ping Path to be /elb-status
If you want to see something on your browser while testing you may need to change the content-type since defaults to application/octet-stream and the browser will offer to save the file, so something like this should work:
location /elb-status {
access_log off;
return 200 'your text goes here';
add_header Content-Type text/plain;
}
If you would like to check against the user-agent something like this could be used:
set $block 1;
# Allow all the ELB health check agents.
if ($http_user_agent ~* '^ELB-HealthChecker\/.*$') {
set $block 0;
}
if (!$http_x_forwarded_for) {
set $block 1
}
if ($block = 1) {
auth_basic 'Please enter ID and password';
auth_basic_user_file /usr/src/redmine/.htpasswd;
}

Can NGINX change the response code after a proxy_pass?

So I have an internal API server namespaced under /api/, and I want to pass all other requests to an Amazon S3 static site using proxy_pass. This all works fine, it's just since Amazon is serving a single page app, I want to always return the same HTML file. They way I did this with the S3 server, was to set the index and error page as the same file. It all looks fine on the surface, but for all other requests besides /, the S3 instance returns a 404. Can I use NGINX to change this to a 200 before returning it to the client?
server {
listen 80;
server_name example.com;
location /api/ {
# serve internal app
}
location / {
proxy_pass http://example.amazonaws.com/;
# ALWAYS RETURN A 200
}
}
You should be able to use the error_page and proxy_intercept_errors directives to achieve this. Something like this should do the trick.
location / {
proxy_pass http://example.amazonaws.com/;
proxy_intercept_errors on;
error_page 404 =302 /your_html_file
}
error_page
proxy_intercept_errors
You can internally rewrite all URLs to the document you want served. This avoids the error handling cycle and problematic redirects.
It would be something like (untested):
location / {
proxy_pass http://example.amazonaws.com/;
rewrite ^.* /index.html
}
Note that you will want to only use full or root-relative URLs in your doc, because you don't know if the docs is served from a subdirectory.
You'd also be wise to have JS code validate the URL and optionally redirect to one you consider valid. Otherwise 3rd party sites could link to offensive URLs and get them in search indexes!

Django Nginx X-Accel-Redirect for protected files on Webfaction

If you want to torment someone until the end of time, just get them to configure Django and Nginx X-Accel-Redirect. This is literally impossible, I have been trying for days.
I am trying to only allow certain files to be downloaded from logged in views in django using Nginx on webfaction. Here is what I have:
Custom Nginx app listening on port 27796 under /static. Here is the conf.
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 27796;
server_name myurl.com;
root /home/ucwsri/webapps/static_media_ucwsri_nginx;
location / {
autoindex on;
}
location ^.*/protected-files {
internal;
alias /home/ucwsri/webapps/static_media_ucwsri_nginx/protected;
}
All static content is in /home/ucwsri/webapps/static_media_ucwsri_nginx, and is being correctly served by this Nginx app.
The files I want protected are here:
/home/ucwsri/webapps/static_media_ucwsri_nginx/protected
Which is the alias listed under the location ^.*/protected-files block in Nginx.
The view simply makes an Http Response thus:
response = HttpResponse()
url = "/static/protected-files/some-file.pdf"
response['X-Accel-Redirect'] = url
return response
Where the 'some-file.pdf' file exists in
/home/ucwsri/webapps/static_media_ucwsri_nginx/protected
Whatever I try I get a 404 from Nginx when trying to get that file as a POST request that goes to that view. I have tried everything I can think of, every location combination block, nothing works. Always a 404.
Someone please put me out of my misery and tell me what I have done wrong. This is truly brutal for something seemingly so simple.
First, your location ^.*/protected-files is nonsense. I guess, you've missed ~ modifier, but even in that case it would be useless.
Second, you have not protected /protected/ folder. Direct request to /protected/some-file.pdf will download that file without any protection.
Third, you have /static/protected-files/some-file.pdf in X-Accel-Redirect, but you didn't mention any static folder before.
So, I would suggest following config:
server {
listen 27796;
server_name myurl.com;
root /home/ucwsri/webapps/static_media_ucwsri_nginx;
location / {
autoindex on;
}
location ^~ /protected/ {
internal;
}
And django should be:
response = HttpResponse()
url = "/protected/some-file.pdf"
response['X-Accel-Redirect'] = url
return response
Summary:
Protect real folder.
X-Accel-Redirect is URI, just think about it as if user put that URI in browser address bar. The only difference is that internal will allow access with X-Accel-Redirect while forbid direct user access from browser.