diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 08c9536f2..076177eaf 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -222,14 +222,15 @@ INSTALLED_APPS = (
# MIDDLEWARE CONFIGURATION
# ------------------------------------------------------------------------------
MIDDLEWARE = (
+ "django.middleware.security.SecurityMiddleware",
+ "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",
)
@@ -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
# ------------------------------------------------------------------------------
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..ef493373d 100644
--- a/changes/notes.rst
+++ b/changes/notes.rst
@@ -43,3 +43,58 @@ 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 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 your nginx configuration.
+
+**On non-docker setups**, in ``/etc/nginx/sites-available/funkwhale.conf``::
+
+ 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 ${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/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..89d53ce2b 100644
--- a/deploy/nginx.template
+++ b/deploy/nginx.template
@@ -41,6 +41,9 @@ server {
# HSTS
add_header Strict-Transport-Security "max-age=31536000";
+ 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};
# compression settings
@@ -78,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;
@@ -111,7 +128,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/dev.yml b/dev.yml
index 7e4adc8ff..4a61a7a87 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"
@@ -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..976c2e435 100644
--- a/docker/nginx/conf.dev
+++ b/docker/nginx/conf.dev
@@ -69,9 +69,24 @@ 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:";
+ 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/;
}
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..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,11 +31,7 @@ module.exports = {
},
configureWebpack: {
plugins: plugins,
- resolve: {
- alias: {
- 'vue$': 'vue/dist/vue.esm.js'
- }
- }
+ devtool: false
},
devServer: {
disableHostCheck: true,