I have SSL certification at the ELB level for my site hosted on Amazon. I used the following site to setup a middle ware to forward all http requests to https:
http://djangosnippets.org/snippets/2472/
It's working great. But here's my question. EACH request is getting forwarded, so I notice a slight lag when clicking links, etc. Nothing extreme. But is there a way to force django to do everything via https? When I have code to HttpResponse and HttpResponseRedirect, how can I have it default to https instead of http? I tried to search for this and was unsuccessful...
I know it's possible if I type https://www... for each URL for redirect and on the links for the pages, but I wanted to avoid doing it that way if possible.
Looking at the middleware you posted, it is doing exactly what you mentioned you did not want to manually do i.e append https to every incoming http request from your domain. I would recommend you offload this job to the front-end server (Either nginx or apache) .
Example with
Nginx
Apache
When Django builds absolute URIs to redirect to, it checks request.is_secure to decide what protocol scheme it should be using (http, https, or ftp).
Django defaults to doing this based on the protocol used for the request, but as you've identified, when behind an LB or proxy this can be wrong due to SSL termination at the LB/proxy level.
You can configure Django to detect this exact scenario using the SECURE_PROXY_SSL_HEADER setting.
We use Nginx currently to load balance, force SSL on requests, and terminate SSL connections as they are proxied to internal app servers. It doesn't have as fancy load balancing capabilities, but Nginx is small and fast enough to put anywhere.
Here's the code bits you may need:
# listen on port 80 and redirect to SSL.
server {
listen 80;
server_name site.com;
rewrite ^ https://$server_name$request_uri? permanent;
}
# listen on port 443, terminate SSL, and proxy to internal web app
# can be node, rails, whatever.
server {
listen 443;
server_name site.com;
gzip on;
client_max_body_size 250M;
ssl on;
ssl_certificate /etc/nginx/site.com.crt;
ssl_certificate_key /etc/nginx/site.com.key;
keepalive_timeout 70;
location / {
proxy_pass http://127.0.0.1:8080;
# We add this extra header just so proxied web app
# knows this used to be an SSL connection.
proxy_set_header x-https 1;
include /etc/nginx/conf.d/proxy.conf;
}
}
Related
What is the best and secure way to redirects from one domain to another domain using the LB rule or NGINX Config ?
In our Config we are using AWS , Php-fpm with Nginx
I am trying to redirect my new site subdomin in nginx
www.test.com/video/ >>> www.test.com/v/
and others like
www.test.com/blog/category/educator-blog/
blog.test.com/educator
I've implemented a temporary fix via the load balancer to have '/video/' with 'www.test.com' redirected to review.test.com/, which allows apache redirects to work. how to implement permanent solution for this though.
example : IF
Path is/video*
Host is www.test.com
THEN
Redirect to https://review.test.com:443/#{path}?#{query}
Status code:HTTP_302
I have tried different config in nginx , but its always return a fixed url
server {
listen 80;
listen [::]:80;
server_name www.test.com;
location /video {
rewrite /video(.*)$ /v$1 redirect;
}
}
output
curl -I http://www.test.com/v/
Location: https://www.test.com:443/v/
Can anyone help me what is the best way to solve this , do i need to continue with LB or nginx rule ?
Any help would be appreciated.
We use an Tomcat application which has been migrated to another system. Also attached is a MSSQL report server. The application needs to be read-only eventually. At this moment we still need to enter some data for certain customers. I use nginx so that only one server is needed for both Tomcat service and Report service. Now I'd like to use nginx location method to block certain requests.
With the help of regex101.com I created a regex to use in nginx. I already tried to change the order of the location blocks. However, I can't get it to work. Here's the link to the regex where you can see that only the blocked URL's are colored: https://regex101.com/r/w3oy0M/1/
Here's the code:
server {
listen 443 ssl;
server_name subdomain.domainname.com;
ssl_certificate c:\cert\public.crt;
ssl_certificate_key c:\cert\private.rsa;
location / {
proxy_pass https://subdomain.domainname.com:8443;
}
location ~ "https:\/\/subdomain\.domainname\.com:8443\/(?!.*customer\/510|.*customer\/10638)(.*\/edit|.*\/new|.*\/archive)" {
# TEST URL FOR NOT ALLOWED REQUESTS
return 301 https://www.google.com;
}
location /Reports {
return 301 https://subdomain.domainname.com:7443/Reports;
}
}
Try
location ~ /.+?(?=customer\/)(?!customer\/510|customer\/10638)(.*\/edit|.*\/new|.*\/archive) {
# TEST URL FOR NOT ALLOWED REQUESTS
return 301 https://www.google.com;
}
However please note anything after # will be dropped as it's an anchor element so a request like
https://subdomain.domainname.com/#/customer/521/installations/new will be sent to nginx as GET / and gets routed to your first location block
But, a request like https://subdomain.domainname.com/hello/customer/521/installations/new will be routed to https://google.com
My server runs Django + Gunicorn + nginx.
I have added an SSL certificate and configured nginx to redirect http to https. When an https request is received, nginx passes it to Gunicorn as http.
My program sometimes returns HttpResponseRedirect, and the browser gets a redirect response and re-requests as http, so nginx redirects to https.
How can I avoid this? How can I configure the server so that the first redirection points directly to an https URL?
In the nginx configuration (inside the location block), specify this:
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
The proxy_redirect tells nginx that, if the backend returns an HTTP redirect, it should leave it as is. By default, nginx assumes the backend is stupid and tries to be smart; if the backend returns an HTTP redirect that says "redirect to http://localhost:8000/somewhere", nginx replaces it with something similar to http://yourowndomain.com/somewhere". But Django isn't stupid (or it can be configured to not be stupid).
Django does not know whether the request has been made through HTTPS or plain HTTP; nginx knows that, but the request it subsequently makes to the Django backend is always plain HTTP. We tell nginx to pass this information with the X-Forwarded-Proto HTTP header, so that related Django functionality such as request.is_secure() works properly. You will also need to set SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') in your settings.py.
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.
I have a django project set up with nginx+apache. The http port for outside access is 20111 which is then forwarded to the server machine (which has an internal IP) to port 80. So nginx listens on port 80 (and passes relevant requests to apache on port 5000).
Now the initial login can be reached from the outside via http://externalip:20111 - but when I complete an admin action, like saving an entry, I get redirected to http://externalip/path/to/model -- without the port 20111. The result is a timeout. How can I tell django to use a specific hostname/port (i.e. http://externalip:20111) for all admin redirects?
When deploying applications behind a proxy or load balancer, it is common to rely on the X-Forwarded-Host header. Django has support for it
First of all, you have to setup nginx to send the proper headers. Add to your nginx host configuration (inside your location section):
proxy_set_header X-Forwarded-Host $host:20111;
Second, add to your settings.py:
USE_X_FORWARDED_HOST = True
It will allow django to trust X-Forwarded-Host headers from a request.
It should make it work for you. For security reasons, you should not trust every value sent in X-Forwarded-Host, so add your trusted domains/IPs to ALLOWED_HOSTS in settings.py