From f67cc6d3c10b1af0d0b1dc2b10148af45da4634f Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 10 Jul 2019 11:57:29 +0200 Subject: [PATCH 1/8] Use Gunicorn/Uvicorn in dev to reflect production setup --- dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev.yml b/dev.yml index 7e4adc8ff..f95b9b5f6 100644 --- a/dev.yml +++ b/dev.yml @@ -49,7 +49,7 @@ services: args: install_dev_deps: 1 entrypoint: compose/django/dev-entrypoint.sh - command: python /app/manage.py runserver 0.0.0.0:${FUNKWHALE_API_PORT-5000} + command: uvicorn --reload config.asgi:application --host 0.0.0.0 --port 5000 --reload-dir config/ --reload-dir=funkwhale_api/ volumes: - ./api:/app - "${MUSIC_DIRECTORY_SERVE_PATH-./data/music}:/music:ro" From 039856688fd04f01f6e95b17e88c6fa76429b9c5 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 10 Jul 2019 11:58:03 +0200 Subject: [PATCH 2/8] See #880: fixed missing x-frame-options=Sameorigin header --- api/config/settings/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 08c9536f2..4c5df8727 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -222,14 +222,14 @@ INSTALLED_APPS = ( # MIDDLEWARE CONFIGURATION # ------------------------------------------------------------------------------ MIDDLEWARE = ( + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "corsheaders.middleware.CorsMiddleware", "funkwhale_api.common.middleware.SPAFallbackMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", - "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", "funkwhale_api.users.middleware.RecordActivityMiddleware", ) From ab73752f55791eae512764d51a5884002d3dedd6 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 10 Jul 2019 14:38:56 +0200 Subject: [PATCH 3/8] See #880: removed vue runtime to remove "eval()" calls Needed for CSP, and also helps with redusing JS size ;) --- front/src/embed.js | 4 +++- front/src/main.js | 4 +++- front/vue.config.js | 5 ----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/front/src/embed.js b/front/src/embed.js index 5a924a9b1..a079ee085 100644 --- a/front/src/embed.js +++ b/front/src/embed.js @@ -10,6 +10,8 @@ Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', - template: '', + render (h) { + return h('EmbedFrame') + }, components: { EmbedFrame } }) diff --git a/front/src/main.js b/front/src/main.js index dd755dee3..145fa6111 100644 --- a/front/src/main.js +++ b/front/src/main.js @@ -122,7 +122,9 @@ store.dispatch('instance/fetchFrontSettings').finally(() => { el: '#app', router, store, - template: '', + render (h) { + return h('App') + }, components: { App } }) diff --git a/front/vue.config.js b/front/vue.config.js index 4b1d9f5d4..4b149a05a 100644 --- a/front/vue.config.js +++ b/front/vue.config.js @@ -30,11 +30,6 @@ module.exports = { }, configureWebpack: { plugins: plugins, - resolve: { - alias: { - 'vue$': 'vue/dist/vue.esm.js' - } - } }, devServer: { disableHostCheck: true, From c39cd010d54c99b7b32014417d183042102fbe10 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 10 Jul 2019 14:58:20 +0200 Subject: [PATCH 4/8] See #880: allow using a production-builded frontend in dev to test CSP --- dev.yml | 1 + docker/nginx/conf.dev | 3 +++ 2 files changed, 4 insertions(+) diff --git a/dev.yml b/dev.yml index f95b9b5f6..4a61a7a87 100644 --- a/dev.yml +++ b/dev.yml @@ -119,6 +119,7 @@ services: - "${MUSIC_DIRECTORY_SERVE_PATH-./data/music}:/music:ro" - ./deploy/funkwhale_proxy.conf:/etc/nginx/funkwhale_proxy.conf:ro - "${MEDIA_ROOT-./api/funkwhale_api/media}:/protected/media:ro" + - "./front:/frontend:ro" networks: - federation - internal diff --git a/docker/nginx/conf.dev b/docker/nginx/conf.dev index 8b6eb4d8c..0ab9ec160 100644 --- a/docker/nginx/conf.dev +++ b/docker/nginx/conf.dev @@ -70,6 +70,9 @@ http { text/x-cross-domain-policy; location /front/ { + # uncomment the following line and comment the proxy-pass one + # to use the frontend build with "yarn build" + #alias /frontend/dist/; proxy_pass http://funkwhale-front/front/; } location /front-server/ { From 6290ded7fa2fce7d99a8324790d684fd6e3161d3 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 10 Jul 2019 15:08:48 +0200 Subject: [PATCH 5/8] See #880: disabled eval source maps in dev to allow for keeping CSP on --- front/vue.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/front/vue.config.js b/front/vue.config.js index 4b149a05a..a40a075d8 100644 --- a/front/vue.config.js +++ b/front/vue.config.js @@ -11,6 +11,7 @@ if (process.env.BUNDLE_ANALYZE === '1') { } module.exports = { baseUrl: process.env.BASE_URL || '/front/', + productionSourceMap: false, pages: { embed: { entry: 'src/embed.js', @@ -30,6 +31,7 @@ module.exports = { }, configureWebpack: { plugins: plugins, + devtool: false }, devServer: { disableHostCheck: true, From 49978081b0a5826bea702264a21efd901c38421c Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 10 Jul 2019 15:09:43 +0200 Subject: [PATCH 6/8] See #880: added XSS filter and content-type nosniff headers --- api/config/settings/common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 4c5df8727..076177eaf 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -222,6 +222,7 @@ INSTALLED_APPS = ( # MIDDLEWARE CONFIGURATION # ------------------------------------------------------------------------------ MIDDLEWARE = ( + "django.middleware.security.SecurityMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "corsheaders.middleware.CorsMiddleware", "funkwhale_api.common.middleware.SPAFallbackMiddleware", @@ -398,6 +399,8 @@ ASGI_APPLICATION = "config.routing.application" # This ensures that Django will be able to detect a secure connection SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +SECURE_BROWSER_XSS_FILTER = True +SECURE_CONTENT_TYPE_NOSNIFF = True # AUTHENTICATION CONFIGURATION # ------------------------------------------------------------------------------ From 9c5f623d03fba7fe924402725bc4811b03712bb3 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 10 Jul 2019 15:11:29 +0200 Subject: [PATCH 7/8] See #880: added CSP policy in deployment files --- changes/changelog.d/880.enhancement | 1 + changes/notes.rst | 15 +++++++++++++++ deploy/docker.proxy.template | 3 +++ deploy/nginx.template | 5 ++++- docker/nginx/conf.dev | 2 ++ 5 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 changes/changelog.d/880.enhancement diff --git a/changes/changelog.d/880.enhancement b/changes/changelog.d/880.enhancement new file mode 100644 index 000000000..58d308afa --- /dev/null +++ b/changes/changelog.d/880.enhancement @@ -0,0 +1 @@ +Hardened security thanks to CSP and additional HTTP headers (#880) diff --git a/changes/notes.rst b/changes/notes.rst index b52fb7897..40a1d7bbb 100644 --- a/changes/notes.rst +++ b/changes/notes.rst @@ -43,3 +43,18 @@ Then, edit your ``/etc/systemd/system/funkwhale-server.service`` and replace the ``ExecStart=/srv/funkwhale/virtualenv/bin/gunicorn config.asgi:application -w ${FUNKWHALE_WEB_WORKERS} -k uvicorn.workers.UvicornWorker -b ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}`` Then reload the configuration change with ``sudo systemctl daemon-reload`` and ``sudo systemctl restart funkwhale-server``. + + +Content-Security-Policy [manual action suggested] +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To improve the security and reduce the attack surface in case of a successfull exploit, we suggest +you add the following Content-Security-Policy to the Nginx configuration of your proxy (same value +for both Docker and non-Docker deployments):: + + server { + # Security related headers + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + } + +Then reload nginx with ``systemctl reload nginx``. diff --git a/deploy/docker.proxy.template b/deploy/docker.proxy.template index 0fbed2f73..6b0a0405a 100644 --- a/deploy/docker.proxy.template +++ b/deploy/docker.proxy.template @@ -29,6 +29,9 @@ server { # HSTS add_header Strict-Transport-Security "max-age=31536000"; + # Security related headers + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + # compression settings gzip on; gzip_comp_level 5; diff --git a/deploy/nginx.template b/deploy/nginx.template index 78b8ff3d6..b38a7e67d 100644 --- a/deploy/nginx.template +++ b/deploy/nginx.template @@ -41,6 +41,9 @@ server { # HSTS add_header Strict-Transport-Security "max-age=31536000"; + # Security related headers + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + root ${FUNKWHALE_FRONTEND_PATH}; # compression settings @@ -111,7 +114,7 @@ server { internal; alias ${MEDIA_ROOT}; } - + # Comment the previous location and uncomment this one if you're storing # media files in a S3 bucket # location ~ /_protected/media/(.+) { diff --git a/docker/nginx/conf.dev b/docker/nginx/conf.dev index 0ab9ec160..8b35430a2 100644 --- a/docker/nginx/conf.dev +++ b/docker/nginx/conf.dev @@ -69,6 +69,8 @@ http { text/x-component text/x-cross-domain-policy; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + location /front/ { # uncomment the following line and comment the proxy-pass one # to use the frontend build with "yarn build" From 53782a5eb53e4b77066284fef3b80805707508a3 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Thu, 18 Jul 2019 11:08:18 +0200 Subject: [PATCH 8/8] See #880: updated CSP, added X-Frame-Options on front-end files, ensure embeds work --- changes/notes.rst | 50 ++++++++++++++++++++++++++++++++---- deploy/docker.nginx.template | 19 ++++++++++++++ deploy/nginx.template | 16 +++++++++++- docker/nginx/conf.dev | 10 ++++++++ 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/changes/notes.rst b/changes/notes.rst index 40a1d7bbb..ef493373d 100644 --- a/changes/notes.rst +++ b/changes/notes.rst @@ -45,16 +45,56 @@ Then, edit your ``/etc/systemd/system/funkwhale-server.service`` and replace the Then reload the configuration change with ``sudo systemctl daemon-reload`` and ``sudo systemctl restart funkwhale-server``. -Content-Security-Policy [manual action suggested] -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Content-Security-Policy and additional security headers [manual action suggested] +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To improve the security and reduce the attack surface in case of a successfull exploit, we suggest -you add the following Content-Security-Policy to the Nginx configuration of your proxy (same value -for both Docker and non-Docker deployments):: +you add the following Content-Security-Policy to your nginx configuration. + +**On non-docker setups**, in ``/etc/nginx/sites-available/funkwhale.conf``:: server { - # Security related headers + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + location /front/ { + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN"; + # … existing content here + } + + # Also create a new location for the embeds to ensure external iframes work + # Simply copy-paste the /front/ location, but replace the following lines: + location /front/embed.html { + add_header X-Frame-Options "ALLOW"; + alias ${FUNKWHALE_FRONTEND_PATH}/embed.html; + } } Then reload nginx with ``systemctl reload nginx``. + +**On docker setups**, in ``/srv/funkwhalenginx/funkwhale.template``:: + + server { + + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + location /front/ { + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN"; + # … existing content here + } + + # Also create a new location for the embeds to ensure external iframes work + # Simply copy-paste the /front/ location, but replace the following lines: + location /front/embed.html { + add_header X-Frame-Options "ALLOW"; + alias /frontent/embed.html; + } + } + +Then reload nginx with ``docker-compose restart nginx``. diff --git a/deploy/docker.nginx.template b/deploy/docker.nginx.template index 431975629..a69762c19 100644 --- a/deploy/docker.nginx.template +++ b/deploy/docker.nginx.template @@ -23,6 +23,10 @@ server { root /frontend; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + location / { include /etc/nginx/funkwhale_proxy.conf; # this is needed if you have file import via upload enabled @@ -31,12 +35,27 @@ server { } location /front/ { + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + add_header X-Frame-Options "ALLOW"; alias /frontend/; expires 30d; add_header Pragma public; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; } + location /front/embed.html { + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + add_header X-Frame-Options "ALLOW"; + alias /frontend/embed.html; + expires 30d; + add_header Pragma public; + add_header Cache-Control "public, must-revalidate, proxy-revalidate"; + } + location /federation/ { include /etc/nginx/funkwhale_proxy.conf; proxy_pass http://funkwhale-api/federation/; diff --git a/deploy/nginx.template b/deploy/nginx.template index b38a7e67d..89d53ce2b 100644 --- a/deploy/nginx.template +++ b/deploy/nginx.template @@ -41,8 +41,8 @@ server { # HSTS add_header Strict-Transport-Security "max-age=31536000"; - # Security related headers add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; root ${FUNKWHALE_FRONTEND_PATH}; @@ -81,11 +81,25 @@ server { } location /front/ { + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + add_header X-Frame-Options "SAMEORIGIN"; alias ${FUNKWHALE_FRONTEND_PATH}/; expires 30d; add_header Pragma public; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; } + location /front/embed.html { + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + add_header X-Frame-Options "ALLOW"; + alias ${FUNKWHALE_FRONTEND_PATH}/embed.html; + expires 30d; + add_header Pragma public; + add_header Cache-Control "public, must-revalidate, proxy-revalidate"; + } location /federation/ { include /etc/nginx/funkwhale_proxy.conf; diff --git a/docker/nginx/conf.dev b/docker/nginx/conf.dev index 8b35430a2..976c2e435 100644 --- a/docker/nginx/conf.dev +++ b/docker/nginx/conf.dev @@ -70,13 +70,23 @@ http { text/x-cross-domain-policy; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; location /front/ { + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN"; # uncomment the following line and comment the proxy-pass one # to use the frontend build with "yarn build" #alias /frontend/dist/; proxy_pass http://funkwhale-front/front/; } + location /front/embed.html { + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "ALLOW"; + proxy_pass http://funkwhale-front/front/embed.html; + } location /front-server/ { proxy_pass http://funkwhale-front/; }