Cannot activate HTTPS links with Django and Nginx - ssl

I have a django rest framework app running on a secured Nginx server.
When I navigate on the DRF api, the protocol in the navigation bar is well redirected to https.
My problem is that all the generated urls are in http instead of https.
I watched in the code, the differents url are build with this method:
def build_absolute_uri(self, location=None):
"""
Build an absolute URI from the location and the variables available in
this request. If no ``location`` is specified, bulid the absolute URI
using request.get_full_path(). If the location is absolute, convert it
to an RFC 3987 compliant URI and return it. If location is relative or
is scheme-relative (i.e., ``//example.com/``), urljoin() it to a base
URL constructed from the request variables.
"""
if location is None:
# Make it an absolute url (but schemeless and domainless) for the
# edge case that the path starts with '//'.
location = '//%s' % self.get_full_path()
bits = urlsplit(location)
if not (bits.scheme and bits.netloc):
current_uri = '{scheme}://{host}{path}'.format(scheme=self.scheme,
host=self.get_host(),
path=self.path)
# Join the constructed URL with the provided location, which will
# allow the provided ``location`` to apply query strings to the
# base path as well as override the host, if it begins with //
location = urljoin(current_uri, location)
return iri_to_uri(location)
In my case, this method always returns unsecured urls (HTTP instead of HTTPS).
I have edited my settings like it's described in this post:
https://security.stackexchange.com/questions/8964/trying-to-make-a-django-based-site-use-https-only-not-sure-if-its-secure
But the generated url are still in HTTP.
Here is my Nginx configuration:
server {
listen 80;
listen [::]:80;
server_name backend.domaine.na.me www.backend.domaine.na.me server_ip:8800;
return 301 https://$server_name$request_uri;
}
server {
# SSL configuration
listen 443 ssl http2;
listen [::]:443 ssl http2;
include snippets/ssl-backend.domaine.na.me.conf;
include snippets/ssl-params.conf;
location / {
proxy_pass http://server_ip:8800/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
server {
listen 8800;
server_name server_ip;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /projects/my_django_app;
}
location /media/ {
root /projects/my_django_app;
}
location / {
include proxy_params;
proxy_pass http://unix:/projects/my_django_app.sock;
}
location ~ /.well-known {
allow all;
}
}
And my django configuration:
In wsgi.py:
os.environ['HTTPS'] = "on"
os.environ['wsgi.url_scheme'] = "https"
In settings.py(Note: I've tried both first two lines):
#SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_PROXY_SSL_HEADER = ('HTTP_X_SCHEME', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
I really need to fix this probem because my DRF api returns some image URL wich must be in HTTPS, else the site cannot be considered 100% secure.
What is wrong with my configuration? Am I missing some options?

The indirection isn't needed; you can merge the settings from the SSL block and the :8800 block to give something like this:
server {
listen 80;
listen [::]:80;
server_name backend.domaine.na.me www.backend.domaine.na.me;
return 301 https://$server_name$request_uri;
}
server {
# SSL configuration
listen 443 ssl http2;
listen [::]:443 ssl http2;
include snippets/ssl-backend.domaine.na.me.conf;
include snippets/ssl-params.conf;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /projects/my_django_app;
}
location /media/ {
root /projects/my_django_app;
}
location ~ /.well-known {
allow all;
}
location / {
proxy_pass http://unix:/projects/my_django_app.sock;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
Then, set SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') and it should work.
I can't find anything about include proxy_params, and I suspect that the headers that you set in the first proxy block aren't forwarded to the Django app at all.

Related

Host static website and api service on same server using nginx

I want to serve my static website and API service from same machine using nginx.
Website is present in /var/www/html
API service is running on port 8000
http://localhost should open static website
http://localhost/api should proxy api service which is running on port 8000
With the help of http://nginx.org/en/docs/beginners_guide.html I tried this config
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
location /api {
proxy_pass http://localhost:8000;
}
}
http://localhost is working fine but http://localhost/api is giving me error 404.
What should be correct configuration to achive such infrastucture?
Here I am writing a conf that can perform the operation you need:
server {
listen 80;
server_name <server_name_you_prefers>;
location / {
alias /var/www/html/; # Static directory's complete path from root
}
location /api {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_pass_request_headers on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET,PUT,PATCH,POST,DELETE,OPTIONS,HEAD';
add_header 'Access-Control-Expose-Headers' 'Origin,Content-Length,Content-Range,Authorization,Content-Type';
add_header 'Access-Control-Allow-Headers' 'Content-Length,Content-Range,Authorization,Content-Type,x-json-response';
add_header 'Access-Control-Allow-Credentials' 'true' always;
}
}
Your nginx reverse-proxy will pass all the requests contains (or start by, I'm not sure) "/api" to your API service.
When you send the request to http://localhost/api, the route "/api" in your code is invoked. I guess this route does not exist because you have 404.
To access your API, 1 simple solution is to prefix all your API by "/api".
For example, if you define the API GET "/version", it should become "/api/version", then you can access this API at http://localhost/api/version.

How to serve devpi with https?

I have an out-of-the-box devpi-server running on http://
I need to get it to work on https:// instead.
I already have the certificates for the domain.
I followed the documentation for nginx-site-config, and created the /etc/nginx/conf.d/domain.conf file that has the server{} block that points to my certificates (excerpt below).
However, my devpi-server --start --init is totally ignoring any/all nginx configurations.
How do i point the devpi-server to use the nginx configurations? Is it even possible, or am I totally missing the point?
/etc/nginx/conf.d/domain.conf file contents:
server {
server_name localhost $hostname "";
listen 8081 ssl default_server;
listen [::]:8081 ssl default_server;
server_name domain;
ssl_certificate /root/certs/domain/domain.crt;
ssl_certificate_key /root/certs/domain/domain.key;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
gzip on;
gzip_min_length 2000;
gzip_proxied any;
gzip_types application/json;
proxy_read_timeout 60s;
client_max_body_size 64M;
# set to where your devpi-server state is on the filesystem
root /root/.devpi/server;
# try serving static files directly
location ~ /\+f/ {
# workaround to pass non-GET/HEAD requests through to the named location below
error_page 418 = #proxy_to_app;
if ($request_method !~ (GET)|(HEAD)) {
return 418;
}
expires max;
try_files /+files$uri #proxy_to_app;
}
# try serving docs directly
location ~ /\+doc/ {
try_files $uri #proxy_to_app;
}
location / {
# workaround to pass all requests to / through to the named location below
error_page 418 = #proxy_to_app;
return 418;
}
location #proxy_to_app {
proxy_pass https://localhost:8081;
proxy_set_header X-outside-url $scheme://$host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}
This is the answer I gave to the same question on superuser.
Devpi doesn't know anything about Nginx, it will just serve HTTP traffic. When we want to interact with a web-app via HTTPS instead, we as the client need to talk to a front-end which can handle it (Nginx) which will in turn communicate with our web-app. This application of Nginx is known as a reverse proxy. As a reverse proxy we can also benefit from Nginx's ability to serve static files more efficiently than getting our web-app to do it itself (hence the "try serving..." location blocks).
Here is a complete working Nginx config that I use for devpi. Note that this is /etc/nginx/nginx.conf file rather than a domain config like yours because I'm running Nginx and Devpi in docker with compose but you should be able to pull out what you need:
worker_processes 1;
events {
worker_connections 1024;
}
http {
# Define the location for devpi
upstream pypi-backend {
server localhost:8080;
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.co.uk; # This is the accessing address eg. https://example.co.uk
root /devpi/server; # This is where your devpi server directory is
gzip on;
gzip_min_length 2000;
gzip_proxied any;
proxy_read_timeout 60s;
client_max_body_size 64M;
ssl_certificate /etc/nginx/certs/cert.crt; Path to certificate
ssl_certificate_key /etc/nginx/certs/cert.key; Path to certificate key
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/pypi.access.log;
# try serving static files directly
location ~ /\+f/ {
error_page 418 = #pypi_backend;
if ($request_method !~ (GET)|(HEAD)) {
return 418;
}
expires max;
try_files /+files$uri #pypi_backend;
}
# try serving docs directly
location ~ /\+doc/ {
try_files $uri #pypi_backend;
}
location / {
error_page 418 = #pypi_backend;
return 418;
}
location #pypi_backend {
proxy_pass http://pypi-backend; # Using the upstream definition
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-outside-url $scheme://$host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
}
With Nginx using this configuration and devpi running on http://localhost:8080, you should be able to access https://localhost or with your machine with appropriate DNS https://example.co.uk. A request will be:
client (HTTPS) > Nginx (HTTP) > devpi (HTTP) > Nginx (HTTPS) > client
This also means that you will need to make sure that Nginx is running yourself, as devpi start won't know any better. You should at the very least see an Nginx welcome page.

Nginx keeps redirecting infinitely

I've followed the instructions from here on setting up nginx to redirect all non-www requests into www but I keep getting ERR_TOO_MANY_REDIRECTS in my browser when I try to hit any page.
My goal is twofold:
All requests that don't have www should be redirected to www
All requests that aren't HTTPS should be redirected to HTTPS
My nginx config looks like this:
upstream mywebsite_proxy {
server unix:/home/deploy/mywebsite/tmp/sockets/puma.sock;
}
server {
listen 80;
listen [::]:80;
listen 443 default_server ssl;
server_name www.mywebsite.com;
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
location / {
proxy_pass http://mywebsite_proxy;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ~ ^/(robots.txt|sitemap.xml.gz)/ {
root /home/deploy/mywebsite/public;
}
}
Notice that there isn't any reference to SSL certificates. I'm using Cloudflare with SSL enabled and HTTPS seemed to just work right out the gate when my config looked like the one below. The non-www to www and non http to https redirects obviously didn't work though...
upstream mywebsite_proxy {
server unix:/home/deploy/mywebsite/tmp/sockets/puma.sock;
}
server {
listen 80;
listen 443;
server_name www.mywebsite.com mywebsite.com;
root /home/deploy/mywebsite/public;
location / {
proxy_pass http://mywebsite_proxy;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ~ ^/(robots.txt|sitemap.xml.gz)/ {
root /home/deploy/mywebsite/public;
}
}
In my opinion you do not need the if part in:
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
The redirect should look like this:
return 301 https://$server_name$request_uri;
Use 301 if you want to do a permanent redirect, which will be stored in the cache of your browser or 302 if you do not want it to be permanent. Furthermore, you can remove the www. part in the server_name and use return 301 https://www.$server_name$request_uri;
I did some thing similar in one of my previous project, here are the steps:
Left default config 'nginx.conf' as it is.
modified /etc/nginx/sites-available/default
(Gist: https://gist.github.com/faizulhaque-tp/db576dc6f22c820a0e23f7a6e1c8b740)
Apart from non-www to www, above configuration works.

Is possible to use SSL in Odoo with NginX avoiding the standard ports (80 and 443)?

Following this tutorial I configured my Nginx like this:
upstream odoo8 {
server 127.0.0.1:8069 weight=1 fail_timeout=0;
}
upstream odoo8-im {
server 127.0.0.1:8072 weight=1 fail_timeout=0;
}
server {
# server port and name (instead of 443 port)
listen 22443;
server_name _;
# Specifies the maximum accepted body size of a client request,
# as indicated by the request header Content-Length.
client_max_body_size 2000m;
# add ssl specific settings
keepalive_timeout 60;
ssl on;
ssl_certificate /etc/ssl/nginx/server.crt;
ssl_certificate_key /etc/ssl/nginx/server.key;
error_page 497 https://$host:22443$request_uri;
# limit ciphers
ssl_ciphers HIGH:!ADH:!MD5;
ssl_protocols SSLv3 TLSv1;
ssl_prefer_server_ciphers on;
# increase proxy buffer to handle some Odoo web requests
proxy_buffers 16 64k;
proxy_buffer_size 128k;
# general proxy settings
# force timeouts if the backend dies
proxy_connect_timeout 3600s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
# set headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
# Let the Odoo web service know that we’re using HTTPS, otherwise
# it will generate URL using http:// and not https://
proxy_set_header X-Forwarded-Proto https;
# by default, do not forward anything
proxy_redirect off;
proxy_buffering off;
location / {
proxy_pass http://odoo8;
}
location /longpolling {
proxy_pass http://odoo8-im;
}
# cache some static data in memory for 60mins.
# under heavy load this should relieve stress on the Odoo web interface a bit.
location /web/static/ {
proxy_cache_valid 200 60m;
proxy_buffering on;
expires 864000;
proxy_pass http://odoo8;
}
}
And I have this ports in my Odoo configuration
longpolling_port = 8072
xmlrpc_port = 8069
xmlrpcs_port = 22443
proxy_mode = True
When I load https://my_domain:22443/web/database/selector in the browser it loads well. But when I choose a database or I make any action, the address loses the https and the port, so it's loaded through the port 80. Then I would need to add this to the NginX configuration and the port 80 should be open
## http redirects to https ##
server {
listen 80;
server_name _;
# Strict Transport Security
add_header Strict-Transport-Security max-age=2592000;
rewrite ^/.*$ https://$host:22443$request_uri? permanent;
}
Is there a way to avoid this redirection? Like that I could keep the port 80 closed in order to avoid spoofing
Update
I can open the login screen with the address https://my_domain:22443/web/login?db=dabatase_name and I can work well inside, but if I log out in order to choose another database in the droplist, it loses again the port and the ssl
Please, try to use this construction:
## http redirects to https ##
server
{
listen 80;
server_name _;
if ($http_x_forwarded_proto = 'http')
{
return 301 https://my_domain.com$request_uri;
}
}

redirect with https only for specific url on nginx

I'm trying to get the https working with some urls. but it seems that the https goes everywhere. In details, I have created 2 vhosts on Nginx. The first virtual host with port 80 and the other one with 443 containing SSL. now my site .i.e domain.com works for both http and https and this is not what I want. I want the https working on one some urls I specify with rules in Nginx vhost.
The main issue is when I try that I get my main site first with http then when I go to a url that contains https "secure_area", it works fine. However, whenever I go after that somewhere else in my site, the https keep going on all other urls.
here is my 443 vhost config:
ssl_session_cache shared:SSL:5m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
server {
listen 443 ssl spdy;
listen [::]:443 ssl spdy;
#server_name www.mydomain.com;
ssl_session_timeout 5m;
root /vars/www/public_html/;
index index.php index.html index.htm;
ssl_certificate /path_to_ssl/certificate.pem;
ssl_certificate_key /path_to_key/server.key;
ssl_ciphers 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS';
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080;
}
location ~ /\.ht {
deny all;
}
# Serve static files directly
location ~* \.(png|jpe?g|gif|ico)$ {
expires 1y; access_log off; try_files $uri $uri/ #rewrite; gzip off;
}
location ~* \.(css)$ {
expires 1d; access_log off;
}
location ~* \.(js)$ {
expires 1h; access_log off;
}
location /secure_area/ {
auth_basic "Restricted";
auth_basic_user_file htpasswd;
rewrite ^ https://$http_host$request_uri? permanent;
}
}
and here is my 80 vhost config:
server {
listen 80 default_server;
server_name mydomain.com;
return 301 http://www.mydomain.com;
}
server {
listen 80;
server_name www.mydomain.com;
root /vars/www/public_html/;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080;
}
location ~ /\.ht {
deny all;
}
location /secure_area/ {
rewrite ^ https://$http_host$request_uri? permanent;
}
}
in case no one noticed, Nginx is working as reverse proexy at front end Apache
now does anyone have any idea how to force https only on some urls and in my case secure_area and force http on all other urls?
Thanks
You can tell the SSL server to redirect back to http if any other URL is visited
server {
listen 80;
server_name example.com;
# normal http settings
location /secure_area/ {
return 301 https://$http_host$request_uri$is_args$query_string;
}
}
server {
listen 443 ssl spdy;
server_name example.com;
# ssl settings;
location /secure_area/ {
#serve secure area content
}
location / {
return 301 http://$http_host$request_uri$is_args$query_string;
}
}