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