Missing custom header with django, nginx and gunicorn - django

Disclaimer:
I'm working in a project where exist an "huge" webapp that have an api for mobiles, so change the api is not an option.
This application was developed time ago and several developers have worked on it,
Having said that, the problem is this;
In the api for mobile of this site (just views than returns json data), the code is looking for a token but does in the headers of request:
token = request.META.get('HTTP_TOKEN')
When I test this api locally, works fine, but in production doesn't, so, I try to figure out whats going on and found this:
django converts headers, even custom headers to keys in request.META, I use urllib2 and requests for test the api and the problem in production is that in production server the request.META never has a key called HTTP_TOKEN, so, doing a little of debug I seriously think the problem is the way we serve the django application.
We are using django1.3, nginx, gunicorn, virtualenvwrapper, python2.7.
My prime suspect is nginx, I think, in someway nginx receive the header but don' forward it to django, I try to do some research about this, but I only found infor about security headers and custom headers from nginx, but I dont find doc or something about how to tell nginx that allows that header and don't remove it.
I need help here, the first thing is test if nginx receives the header, but I just know a little about nginx and I don't know how to tell it to log the headers of requests.
Thanks
Update
nginx conf file

If Django is accessed using uwsgi_pass, then in the appropriate location(s) ...
# All request headers should be passed on by default
# Make sure "Token" response header is passed to user
uwsgi_pass_header Token;
If Django is accessed using fastcgi_pass, then in the appropriate location(s) ...
# All request headers should be passed on by default
# Make sure "Token" response header is passed to user
fastcgi_pass_header Token;
If Django is accessed using proxy_pass, then in the appropriate location(s) ...
# All request headers should be passed on by default
# but we can make sure "Token" request header is passed to Django
proxy_set_header Token $http_token;
# Make sure "Token" response header is passed to user
proxy_pass_header Token;
These should help eliminate the possibility that Nginx is not passing things along from your issue.

In your nginx configuration file (f.e. mysite_nginx.conf) in the server section add this parameter: uwsgi_pass_request_headers on;.
For example:
server {
# the port your site will be served on
listen 8000;
...
underscores_in_headers on;
}
And if access to Django goes through uwsgi_pass, you need to add this one parameter uwsgi_pass_request_headers on; in location section.
For example:
location / {
include /etc/nginx/uwsgi_params; # the uwsgi_params file you installed
uwsgi_pass_request_headers on;
uwsgi_pass django;
}

I think this is what you need:
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$http_http_token" "$upstream_http_http_token"'
to log what is happening.
You might look deeper into the proxy_set_header section on the upstream proxy module to see how to pass on the headers you need.
You can find the documentation here:
http://wiki.nginx.org/HttpLogModule
http://wiki.nginx.org/NginxHttpUpstreamModule
http://wiki.nginx.org/NginxHttpProxyModule#proxy_set_header
http://wiki.nginx.org/HttpProxyModule#proxy_pass_request_headers
The last entry seems to indicate that nginx passes most headers by default

I didn't find a real answer, but was able to make a workaround. I was having the same problem with RFC standard headers if-none-match and if-modified-since, so my solution is tested for those headers.
Added to my nginx config:
uwsgi_param HTTP_IF_NONE_MATCH $http_if_none_match;
uwsgi_param HTTP_IF_MODIFIED_SINCE $http_if_modified_since;
I cannot explain why nginx refuses to pass these headers to uwsgi by default. This config forces it. Pages generate 304s as appropriate now.
For the original question about the non-standard "token" header, this should do the trick:
uwsgi_param HTTP_TOKEN $http_token;

The answers above are enough for us to figure out the way. But there is still an annoying point we need to know. Yes, it is very annoying.
proxy_set_header X_FORWARDED_FOR # oops it never works. 1.16.1 on centos7
proxy_set_header X-FORWARDED-FOR # this will do the job
So you get it. Underscore could never appear in the customized variable name. Use a hyphen instead.
Maybe Nginx uses underscores in some grammar cases. Someone pointed out the official reference will be appreciated.

It depends on how the custom header is named. My was in format "SomethingLike.this", it contains a dot. It was not possible to rename the header in the request, because it is not our code. So writing this would not work:
proxy_set_header SomethingLike.this $http_somethinglike.this;
proxy_pass_header SomethingLike.this;
Also this would not work:
underscores_in_headers on;
because I would need dots_in_headers directive which does not exist.
But I found I can pass ALL headers simply by adding:
ignore_invalid_headers off;
It may be insecure to pass all headers, please use with caution.

Related

How to remove X-Frame-Options SAMEORIGIN header from request header in NGINX?

enter image description here
Hello, everyone i need to remove X-Frame-Options SAMEORIGIN header from request header.i use nginx server and all setup is in aws.
i try following solution but it does't work for me.
proxy_hide_header 'x-frame-options';
And i got error like.
enter image description here
I think this can't be done with the out-of-the-box nginx build. Additionally, proxy_hide_header directive used to hide headers from upstream, not from client browser. One of the solutions is to use more_clear_headers directive from ngx_headers_more module, but for that you need to build nginx from sources (or use OpenResty instead).

Rewrite request on Nginx from GET to POST with body (for tracking pixel)

I'm trying to figure out if there's an easy way to convert a tracking pixel request that gets to Nginx into a POST that will go to an upstream with some added body.
for exmaple, if I get a GET request for http://domain.com/track/mail-id.gif, I'd like to configure Nginx to convert it to a POST that goes to http://upstream/mail-id with some body (let's say status:opened).
how can it be done?
Just wanted to add a more detailed example:
location /track/mail-id.gif {
proxy_pass http://upstream/mail-id;
proxy_method POST;
proxy_set_body "status:opened";
# if needed
# proxy_set_header Some-Header value;
}
Provided a url for proxy_pass here, so to ensure the exact behaviour requested.
You should be able to use Nginx proxy functionality to achieve this, specifically with the proxy_method directive.
Something like:
location /track/mail-id.gif {
proxy_pass http://upstream
proxy_method POST
}
See http://nginx.org/en/docs/http/ngx_http_proxy_module.html for more information about Nginx proxy directives.

how to break a variable into seperate variables in nginx using regex

I have a development server setup that does some dynamic rooting to allow me to set up quick test projects by detecting domain and subdomain in server_name and using it to set the root.
server_name ~^(?<subdomain>\w*?)?\.?(?<domain>\w+\.\w+)$;
This works well, allowing me to set the root path based on variables $subdomain and $domain
For a specific type of project though, I also need to be able to further split the subdomain variable into two variables base on if the subdomain contains a dash.
e.g.
mysubdomain should not split but remain as variable $subdomain,
but mysubdomain-tn would be seperated into 2 variable $subdomain and $version
You need to complicate your regular expression a bit more:
server_name ~^(?<subdomain>\w*?)(-(?<version>\w*?)?)?\.?(?<domain>\w+\.\w+)$;
EDIT:
There are several ways to debug an Nginx configuration, including the debugging log, echo module and, in some extreme situations, even using a real debugger. However, in most cases adding custom headers to the response is enough to get the necessary information.
For example, I tested the regular expression above using this simple configuration:
server {
listen 80;
server_name ~^(?<subdomain>\w*?)(-(?<version>\w*?)?)?\.?(?<domain>\w+\.\w+)$;
# Without this line your browser will try to download
# the response as if it were a file
add_header Content-Type text/plain;
# You can name your headers however you like
add_header X-subdomain "$subdomain";
add_header X-domain "$domain";
add_header X-version "$version";
return 200;
}
Then I added domains mydomain.local, mysubdomain-tn.mydomain.local and mysubdomain-tn.mydomain.local to my hosts file, opened them in a browser with open debug panel (F12 in most browsers) and got the results.

Django CSRF Error Casused by Nginx X-Forwarded-host

I've been working on a django app recently and it is finally ready to get deployed to a qa and production environment. Everything worked perfectly locally, but since adding the complexity of the real world deployment I've had a few issues.
First my tech stack is a bit complicated. For deployments I am using aws for everything with my site deployed on multiple ec2's backed by a load balancer. The load balancer is secured with ssl, but the connections to the load balancer are forwarded to the ec2's over standard http on port 80. After hitting an ec2 on port 80 they are forwarded to a docker container on port 8000 (if you are unfamiliar with docker just consider it to be a standard vm). Inside the container nginx listens on port 8000, it handles a redirection for the static files in django and for web requests it forwards the request to django running on 127.0.0.1:8001. Django is being hosted by uwsgi listening on port 8001.
server {
listen 8000;
server_name localhost;
location /static/ {
alias /home/library/deploy/thelibrary/static/;
}
location / {
proxy_set_header X-Forwarded-Host $host:443;
proxy_pass http://127.0.0.1:8001/;
}
}
I use X-Forwarded host because I was having issues with redirects from google oauth and redirects to prompt the user to login making the browser request the url 127.0.0.1:8001 which will obviously not work. Within my settings.py file I also included
USE_X_FORWARDED_HOST = True
to force django to use the correct host for redirects.
Right now general browsing of the site works perfectly, static files load, redirects work and the site is secured with ssl. The problem however is that CSRF verification fails.
On a form submission I get the following error
Referer checking failed - https://qa-load-balancer.com/projects/new does not match https://qa-load-balancer.com:443/.
I'm really not sure what to do about this, its really through stackoverflow questions that I got everything working so far.
Rather than doing an HTTP proxy, I would use Nginx's built-in capacity to communicate with uWSGI. (This will still work if you are using separate Docker containers for Nginx and uWSGI since the communication is done over TCP)
A typical configuration (mine) looks like this:
location / {
uwsgi_pass http://127.0.0.1:8001;
include uwsgi_params;
}
You will have to remove the --http argument (or config-file equivalent) from your uWSGI invocation.
Additionally, in uwsgi_params (found in /etc/nginx or a custom location you specify) there are several directives to pass meta data through. Here's an excerpt from mine that looks like it could be related to your problem:
...
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param HTTPS $https if_not_empty;
Relevant docs: http://uwsgi-docs.readthedocs.org/en/latest/WSGIquickstart.html#putting-behind-a-full-webserver
For users who cannot use Nginx's built-in facility, here's the root cause:
Starting in ~Djagno 1.9, the CSRF check requires that the Referer and Host match unless you specify a CSRF_TRUSTED_ORIGINS (see the code around REASON_BAD_REFERER here)
If you don't specify CSRF_TRUSTED_ORIGINS, the system falls back on request.get_host()
request.get_host() uses request._get_raw_host()
request._get_raw_host() checks sequentially HTTP_X_FORWARDED_HOST (if USE_X_FORWARDED_HOST is set), HTTP_HOST, and SERVER_NAME
Most recommended Nginx configurations suggest an entry like proxy_set_header X-Forwarded-Host $host:$server_port;
Eventually, the referrer (e.g. <host>) is compared to X-Forwarded-Host (e.g. <host>:<port>). These do not match so CSRF fails.
There isn't a lot of discussion about this, but Django ticket #26037 references RFC2616. The ticket states that a host without a port is "against spec", but that's not true as the spec actually says:
A "host" without any trailing port information implies the default port for the service requested
This leads to (at minimum) the following options (safest first):
include host and port in CSRF_TRUSTED_ORIGINS
remove port from X-Forwarded-Host in nginx configuration (on the assumption that the non-spec X-Forwarded-Host follows the same semantics as Host)
To avoid hard-coding domains in CSRF_TRUSTED_ORIGINS, the second option is attractive, but it may come with security caveats. Speculatively:
X-Forwarded-Proto should be used to clarify the protocol (since the absence of a port implies a default protocol)
The reverse proxy MUST use port 443 for HTTPS (i.e. the default for the protocol) and disallow non-HTTPS connection types (X-Forwarded-Proto might fix this).
I had the same issue running a Django project on GitPod: the X-Forwarded-Host was in the form hostname:443, causing the CSRF error.
I solved it with a custom middleware that strips the port from the header:
# myproject/middleware.py
from django.utils.deprecation import MiddlewareMixin
class FixForwardedHostMiddleware(MiddlewareMixin):
def process_request(self, request):
forwarded_host = request.META.get('HTTP_X_FORWARDED_HOST')
if forwarded_host:
forwarded_host = forwarded_host.split(':')[0]
request.META['HTTP_X_FORWARDED_HOST'] = forwarded_host
To use this middleware, you need to edit your settings.py, and insert the new middleware before the CSRF one, like so:
# myproject/settings.py
MIDDLEWARE = [
...
'myproject.middleware.FixForwardedHostMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
...
]
See clayton's answer to understand why this fixes the CSRF error.
I don't think this middleware introduces any security issue; please comment if you think otherwise.

How to use django-sslify to force https on my Django+nginx+gunicorn web app, and rely on Cloudflare's new free SSL?

Intro
Cloudflare's providing SSL for free now, and I would be a fool to not take advantage of this on my site, and a downright dickhead to break everything in the process of trying to.
I can code apps just fine, but when it comes to setting up or configuring https/nginx/gunicorn/etc/idon'tknowtheterminology, I know barely enough to follow Googled instructions.
Question
I would like to use django-sslify to force https on my Django web app. How may I achieve this without upsetting the balance in my life, given the following known facts?
Known facts
I'm using Django 1.7, running on a DigitalOcean server hooked up
to a (free) Cloudflare DNS. Django is fitted (served?) with nginx
and gunicorn. Basically followed this guide to get it all set up.
Accessing my website currently defaults to a regular http://example.com header.
Manually accessing https://example.com works with
the green lock and all, but this breaks all form submissions with
the error "(403) CSRF verification failed. Request aborted.".
In my Cloudflare site settings, the domain is currently configured to "Flexible SSL".
Trying to use django-sslify with my existing setup totally breaks everything, and the browser is unable to return a response.
This info nugget tells me that I should use the "Full SSL" configuration setting when using django-sslify with Cloudflare's SSL.
Cause for hesitation found here where it is mentioned that a $20/mo Pro Cloudflare account is needed to handle SSL termination. So I really don't want to screw this up :/
There was only 1 mention of "http" or "https" anywhere in my nginx and gunicorn configuration, specifically in my nginx config:
location / {
proxy_pass http://127.0.0.1:8001; ... }
Ok I think that's all I have
Also, my server is providing an Django Rest Framework api for a Phonegap app, does that need to be taken in to consideration? If I need to provide addtional information do let me know and I'll get back to you. Thank you for taking a look at this! :)
CloudFlare allows you to enable specific page rules, one of which is to force SSL (by doing a hard redirect). This is a great thing to use in addition to django-sslify or django-secure
In addition to setting up your SSL redirect, you also need to tell Django to handle secure requests. Luckily, Django provides a decent guide for doing this, but there are a few things that it doesn't mention but I've had to do with nginx.
In your Django settings, you need to tell Django how to detect a secure request
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
In your nginx configuration you need to set up the X-Forwarded-Protocol header (and the X-Forwarded-For/X-Scheme headers are also useful).
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
You also need to proxy the Host header down, so Django is able to read the correct host and port, which is used in generating absolute urls and CSRF, among other things.
proxy_set_header Host $http_host;
Note that I used the $http_host variable instead of $host or $host:$server_port. This will ensure that Django will still respect CSRF requests on non-standard ports, while still giving you the correct absolute urls.
As with most things related to nginx and gunicorn, YMMV and it gets easier after you do it a few times.