From ae71ec26d127d0fc2b98f3d5379e1c166d4ba8e2 Mon Sep 17 00:00:00 2001
From: Robin Tissot <tissotrobin@gmail.com>
Date: Mon, 27 Jul 2020 16:07:30 +0200
Subject: [PATCH 1/4] basic ssl implementation.

---
 .env                   |  3 ++-
 docker-compose.yml     |  4 +++-
 nginx/nginx.conf       | 10 +++++++---
 production.yml_example |  6 ++++--
 4 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/.env b/.env
index 00b6a4c6..c64b65b5 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,4 @@
 CELERY_MAIN_CORES=2
 CELERY_LOW_CORES=2
-FLOWER_BASIC_AUTH=flower:whatever
\ No newline at end of file
+FLOWER_BASIC_AUTH=flower:whatever
+DOMAIN=localhost
diff --git a/docker-compose.yml b/docker-compose.yml
index 7acca060..2e600c37 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -40,8 +40,10 @@ services:
     volumes:
       - static:/usr/src/app/static
       - media:/usr/src/app/media
+      - ./nginx/certs/:/etc/certs
     ports:
       - 8080:80
+      - 443:443
     depends_on:
       - web
 
@@ -108,7 +110,7 @@ services:
     expose:
       - 25
     environment:
-      - PRIMARY_HOST=${MAIL_PRIMARY_HOST}
+      - PRIMARY_HOST=${DOMAIN}
       - ALLOWED_HOSTS=web ; celery-main ; celery-low-priority; docker0
 
 volumes:
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index 11f632c1..e19006e5 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -7,12 +7,16 @@ upstream websocket {
     server channelserver:5000;
 }
 
-server {
 
+server {
     listen 80 default_server;
     charset     utf-8;
     client_max_body_size 150M;
-    
+
+    listen 443 ssl;
+    ssl_certificate /etc/certs/fullchain.pem;
+    ssl_certificate_key /etc/certs/privkey.pem;
+
     location /ws/ {
         proxy_pass http://websocket;
         proxy_http_version 1.1;
@@ -47,4 +51,4 @@ server {
     location = /robots.txt {
         alias /usr/src/app/static/robots.txt;
     }
-}
\ No newline at end of file
+}
diff --git a/production.yml_example b/production.yml_example
index 85e4caf1..768c6636 100644
--- a/production.yml_example
+++ b/production.yml_example
@@ -12,8 +12,10 @@ services:
 
     nginx:
       restart: always
-      ports:
-        - 80:80
+      volumes:
+        - static:/usr/src/app/static
+        - media:/usr/src/app/media
+        - /etc/letsencrypt/live/$DOMAIN/:/etc/certs
 
     flower:
       restart: always
-- 
GitLab


From d7ccb15002f4fca35bd1658f5de7b3d2f6243d56 Mon Sep 17 00:00:00 2001
From: Robin Tissot <tissotrobin@gmail.com>
Date: Mon, 27 Jul 2020 16:09:58 +0200
Subject: [PATCH 2/4] Revert removed ports from production conf.

---
 production.yml_example | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/production.yml_example b/production.yml_example
index 768c6636..503524c0 100644
--- a/production.yml_example
+++ b/production.yml_example
@@ -12,6 +12,9 @@ services:
 
     nginx:
       restart: always
+      ports:
+        - 80:80
+        - 443:443
       volumes:
         - static:/usr/src/app/static
         - media:/usr/src/app/media
-- 
GitLab


From 7b91efcd4db8730bcd559c4c07c09f6aca560af8 Mon Sep 17 00:00:00 2001
From: Robin Tissot <tissotrobin@gmail.com>
Date: Fri, 31 Jul 2020 13:22:21 +0200
Subject: [PATCH 3/4] Make ssl config optional and easy to setup.

---
 .env                         |  4 --
 app/escriptorium/settings.py |  3 ++
 docker-compose.yml           |  7 +--
 nginx/Dockerfile             |  9 +++-
 nginx/nginx.conf             | 10 ++--
 nginx/ssl.conf               | 98 ++++++++++++++++++++++++++++++++++++
 production.yml_example       | 17 ++++---
 variables.env_example        |  1 +
 8 files changed, 129 insertions(+), 20 deletions(-)
 delete mode 100644 .env
 create mode 100644 nginx/ssl.conf

diff --git a/.env b/.env
deleted file mode 100644
index c64b65b5..00000000
--- a/.env
+++ /dev/null
@@ -1,4 +0,0 @@
-CELERY_MAIN_CORES=2
-CELERY_LOW_CORES=2
-FLOWER_BASIC_AUTH=flower:whatever
-DOMAIN=localhost
diff --git a/app/escriptorium/settings.py b/app/escriptorium/settings.py
index c683d0a5..ebc81f80 100644
--- a/app/escriptorium/settings.py
+++ b/app/escriptorium/settings.py
@@ -34,6 +34,9 @@ sys.path.append(APPS_DIR)
 SITE_ID = 1
 SECRET_KEY = os.getenv('SECRET_KEY', 'a-beautiful-snowflake')
 
+# SECURE_SSL_REDIRECT = os.getenv('SECURE_SSL_REDIRECT', False) == 'True'  # should be done by  nginx
+SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+
 # SECURITY WARNING: don't run with debug turned on in production!
 DEBUG = os.getenv('DEBUG', False)
 
diff --git a/docker-compose.yml b/docker-compose.yml
index 2e600c37..a0922a7f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -37,15 +37,16 @@ services:
 
   nginx:
     build: ./nginx
+    environment:
+      - SERVERNAME=${DOMAIN:-localhost}
     volumes:
       - static:/usr/src/app/static
       - media:/usr/src/app/media
-      - ./nginx/certs/:/etc/certs
     ports:
       - 8080:80
-      - 443:443
     depends_on:
       - web
+      - channelserver
 
   # elasticsearch:
   #   image: docker.elastic.co/elasticsearch/elasticsearch:6.5.1
@@ -110,7 +111,7 @@ services:
     expose:
       - 25
     environment:
-      - PRIMARY_HOST=${DOMAIN}
+      - PRIMARY_HOST=${DOMAIN:-localhost}
       - ALLOWED_HOSTS=web ; celery-main ; celery-low-priority; docker0
 
 volumes:
diff --git a/nginx/Dockerfile b/nginx/Dockerfile
index b4c0590a..7e501a78 100644
--- a/nginx/Dockerfile
+++ b/nginx/Dockerfile
@@ -1,4 +1,11 @@
 FROM nginx:1.15.0-alpine
 
 RUN rm /etc/nginx/conf.d/default.conf
-COPY nginx.conf /etc/nginx/conf.d
\ No newline at end of file
+
+ARG NGINX_CONF=nginx.conf
+ARG SSL_CERT=./certs/fullchain.pem
+ARG SSL_KEY=./certs/privkey.pem
+
+COPY ${NGINX_CONF} /etc/nginx/conf.d/nginx.conf
+COPY ${SSL_CERT} /etc/certs/cert.pem
+COPY ${SSL_KEY} /etc/certs/key.pem
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index e19006e5..4eedfc31 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -9,13 +9,11 @@ upstream websocket {
 
 
 server {
-    listen 80 default_server;
+    listen 80;
     charset     utf-8;
     client_max_body_size 150M;
 
-    listen 443 ssl;
-    ssl_certificate /etc/certs/fullchain.pem;
-    ssl_certificate_key /etc/certs/privkey.pem;
+    server_name ${SERVERNAME:-localhost};
 
     location /ws/ {
         proxy_pass http://websocket;
@@ -32,9 +30,11 @@ server {
 
     location / {
         uwsgi_pass  escriptorium;
-        # include     /usr/src/app/escriptorium/uwsgi_params;
+        include     uwsgi_params;
+        uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
 
         proxy_pass http://escriptorium;
+        proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header Host $host;
         proxy_redirect off;
diff --git a/nginx/ssl.conf b/nginx/ssl.conf
new file mode 100644
index 00000000..49d9ce82
--- /dev/null
+++ b/nginx/ssl.conf
@@ -0,0 +1,98 @@
+upstream escriptorium {
+    server web:8000;
+    # server unix:/usr/src/app/escriptorium/app.sock;
+}
+
+upstream websocket {
+    server channelserver:5000;
+}
+
+# include /etc/nginx/auth_part1.conf;
+
+server {
+    listen 443 ssl http2;
+
+    charset     utf-8;
+    client_max_body_size 150M;
+
+    server_name ${SERVERNAME:-localhost};
+    ssl_certificate /etc/certs/cert.pem;
+    ssl_certificate_key /etc/certs/key.pem;
+
+    # Performance + Privacy improvements
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    ssl_trusted_certificate /etc/certs/cert.pem;
+    resolver 8.8.8.8 208.67.222.222 valid=300s;
+    resolver_timeout 5s;
+
+    # https://mozilla.github.io/server-side-tls/ssl-config-generator/
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+    ssl_prefer_server_ciphers on;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 5m;
+    # ssl_dhparam /etc/nginx/dhparams.pem;
+
+    location /.well-known/acme-challenge {
+        add_header Strict-Transport-Security "max-age=31536000" always;
+        add_header X-Content-Type-Options "nosniff";
+        add_header X-Frame-Options "SAMEORIGIN";
+        add_header X-XSS-Protection "1; mode=block";
+        default_type "text/plain";
+        root /usr/share/nginx/html;
+        try_files $uri =404;
+    }
+
+    location / {
+        uwsgi_pass  escriptorium;
+        include     uwsgi_params;
+        uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
+
+        add_header Strict-Transport-Security "max-age=31536000" always;
+        add_header X-Content-Type-Options "nosniff";
+        add_header X-Frame-Options "SAMEORIGIN";
+        add_header X-XSS-Protection "1; mode=block";
+        proxy_set_header Host $host;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header X-Forwarded-Port $server_port;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_pass http://escriptorium;
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+        proxy_read_timeout 900s;
+    }
+
+    location /static/ {
+        alias /usr/src/app/static/;
+    }
+
+    location /media/ {
+        alias /usr/src/app/media/;
+    }
+
+    location = /robots.txt {
+        alias /usr/src/app/static/robots.txt;
+    }
+}
+
+server {
+    listen 80;
+    server_name ${SERVERNAME:-localhost};
+
+    location /.well-known/acme-challenge {
+        default_type "text/plain";
+        root /usr/share/nginx/html;
+        try_files $uri =404;
+    }
+
+    location = /silent_liveness_check {
+        access_log off;
+        return 301 https://$server_name$request_uri;
+    }
+
+    location / {
+        return 301 https://$server_name$request_uri;
+    }
+}
diff --git a/production.yml_example b/production.yml_example
index 503524c0..171d033c 100644
--- a/production.yml_example
+++ b/production.yml_example
@@ -13,17 +13,20 @@ services:
     nginx:
       restart: always
       ports:
-        - 80:80
-        - 443:443
-      volumes:
-        - static:/usr/src/app/static
-        - media:/usr/src/app/media
-        - /etc/letsencrypt/live/$DOMAIN/:/etc/certs
+       - "80:80"
+      # - "443:443"
+
+      ### To enable SSL, generate keys with letsencrypt/certbot
+      ### and uncomment this block and the port 443
+      # build:
+      #   args:
+      #     - NGINX_CONF=ssl.conf
+      #     - SSL_CERT=/etc/letsencrypt/live/$DOMAIN/fullchain.pem
+      #     - SSL_KEY=/etc/letsencrypt/live/$DOMAIN/privkey.pem
 
     flower:
       restart: always
 
-
     # cpus and mem_limit imposes a hard limit on cpus usage,
     # needed to keep some for http/db when working with a single machine
     #
diff --git a/variables.env_example b/variables.env_example
index e277afe9..326e1526 100644
--- a/variables.env_example
+++ b/variables.env_example
@@ -1,3 +1,4 @@
+DOMAIN=localhost
 SECRET_KEY=changeme
 SQL_ENGINE=django.db.backends.postgresql
 SQL_HOST=db
-- 
GitLab


From 52303e52d285ded6ebae74694e1734a704013202 Mon Sep 17 00:00:00 2001
From: Robin Tissot <tissotrobin@gmail.com>
Date: Fri, 31 Jul 2020 14:52:51 +0200
Subject: [PATCH 4/4] HTTPS Redirect & websockets fixes.

---
 nginx/Dockerfile |  5 +++--
 nginx/nginx.conf |  2 --
 nginx/ssl.conf   | 20 ++++++++++++++++----
 3 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/nginx/Dockerfile b/nginx/Dockerfile
index 7e501a78..8c8477ff 100644
--- a/nginx/Dockerfile
+++ b/nginx/Dockerfile
@@ -3,8 +3,9 @@ FROM nginx:1.15.0-alpine
 RUN rm /etc/nginx/conf.d/default.conf
 
 ARG NGINX_CONF=nginx.conf
-ARG SSL_CERT=./certs/fullchain.pem
-ARG SSL_KEY=./certs/privkey.pem
+
+ARG SSL_CERT
+ARG SSL_KEY
 
 COPY ${NGINX_CONF} /etc/nginx/conf.d/nginx.conf
 COPY ${SSL_CERT} /etc/certs/cert.pem
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index 4eedfc31..b0ed6e4f 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -13,8 +13,6 @@ server {
     charset     utf-8;
     client_max_body_size 150M;
 
-    server_name ${SERVERNAME:-localhost};
-
     location /ws/ {
         proxy_pass http://websocket;
         proxy_http_version 1.1;
diff --git a/nginx/ssl.conf b/nginx/ssl.conf
index 49d9ce82..345d3d6d 100644
--- a/nginx/ssl.conf
+++ b/nginx/ssl.conf
@@ -15,7 +15,6 @@ server {
     charset     utf-8;
     client_max_body_size 150M;
 
-    server_name ${SERVERNAME:-localhost};
     ssl_certificate /etc/certs/cert.pem;
     ssl_certificate_key /etc/certs/key.pem;
 
@@ -44,6 +43,20 @@ server {
         try_files $uri =404;
     }
 
+    location /ws/ {
+        proxy_pass http://websocket;
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection "upgrade";
+
+        proxy_redirect off;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Host $server_name;
+    }
+
+
     location / {
         uwsgi_pass  escriptorium;
         include     uwsgi_params;
@@ -79,7 +92,6 @@ server {
 
 server {
     listen 80;
-    server_name ${SERVERNAME:-localhost};
 
     location /.well-known/acme-challenge {
         default_type "text/plain";
@@ -89,10 +101,10 @@ server {
 
     location = /silent_liveness_check {
         access_log off;
-        return 301 https://$server_name$request_uri;
+        return 301 https://$host$request_uri;
     }
 
     location / {
-        return 301 https://$server_name$request_uri;
+        return 301 https://$host$request_uri;
     }
 }
-- 
GitLab