diff --git a/defaults/main.yml b/defaults/main.yml index 006ce89a4cfa6e86aed99b7ccbdb9d7f7bc7106c..108e4578f6e658b2ea59d4c96e38e1232b0dd2ed 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -40,7 +40,7 @@ funkwhale_letsencrypt_certbot_flags: funkwhale_letsencrypt_enabled: true funkwhale_letsencrypt_skip_cert: false -funkwhale_nginx_csp_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:" +funkwhale_nginx_csp_policy: "default-src 'self'; connect-src https: wss: http: ws: 'self' 'unsafe-eval'; script-src 'self' 'wasm-unsafe-eval'; style-src https: http: 'self' 'unsafe-inline'; img-src https: http: 'self' data:; font-src https: http: 'self' data:; media-src https: http: 'self' data:; object-src 'none'" funkwhale_redis_managed: true funkwhale_api_ip: 127.0.0.1 funkwhale_api_port: 5000 diff --git a/templates/funkwhale_proxy.conf.j2 b/templates/funkwhale_proxy.conf.j2 index fadc957a7e3028397b68e57f2e0e11ce05fa9f6b..2651ab8133244f73edf4fb016f43e144209358bc 100644 --- a/templates/funkwhale_proxy.conf.j2 +++ b/templates/funkwhale_proxy.conf.j2 @@ -1,15 +1,16 @@ # global proxy conf 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-Proto $scheme; +proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; {% if not funkwhale_nginx_tls_termination -%} -proxy_set_header X-Forwarded-Host $host:$server_port; -proxy_set_header X-Forwarded-Port $server_port; +proxy_set_header X-Forwarded-Host $http_x_forwarded_host; +proxy_set_header X-Forwarded-Port $http_x_forwarded_port; proxy_redirect off; {% endif -%} # websocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; -proxy_set_header Connection "upgrade"; +proxy_set_header Connection $funkwhale_connection_upgrade; diff --git a/templates/nginx.conf.j2 b/templates/nginx.conf.j2 index 6712e059c9be72749fdd11dce6599a2ff8614657..612039bb7171938ea867cb2c8490831622df493a 100644 --- a/templates/nginx.conf.j2 +++ b/templates/nginx.conf.j2 @@ -1,16 +1,38 @@ # {{ ansible_managed }} +# This template was based on Funkwhale's nginx.template at ae2402618846d414cb1b4e7237c4ce43d8c8837c + +upstream funkwhale-api { + server {{ funkwhale_api_ip }}:{{ funkwhale_api_port }}; +} + {% if funkwhale_nginx_tls_termination -%} server { listen 80; listen [::]:80; server_name {{ funkwhale_hostname }}; - location / { return 301 https://$host$request_uri; } + + location /.well-known/ { + allow all; + include /etc/nginx/funkwhale_proxy.conf; + proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/.well-known/; + } + + location / { + return 301 https://$host$request_uri; + } } {% endif -%} +# Required for websocket support. +map $http_upgrade $funkwhale_connection_upgrade { + default upgrade; + '' close; +} + server { listen {% if funkwhale_nginx_tls_termination %}443 ssl http2{% else %}80{% endif %}; listen [::]:{% if funkwhale_nginx_tls_termination %}443 ssl http2{% else -%}80{% endif %}; + charset utf-8; server_name {{ funkwhale_hostname }}; {% if funkwhale_nginx_tls_termination -%} {% if funkwhale_ssl_key_path -%} @@ -19,25 +41,37 @@ server { {% else -%} ssl_certificate /etc/letsencrypt/live/{{ funkwhale_hostname }}/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/{{ funkwhale_hostname }}/privkey.pem; - {% endif -%} + {% endif -%} {# funkwhale_ssl_key_path #} + {% if funkwhale_nginx_tls_configure_ciphers -%} - # from https://cipherli.st/ - ssl_prefer_server_ciphers on; - ssl_ciphers EECDH+AESGCM:EDH+AESGCM; - ssl_ecdh_curve secp384r1; - ssl_session_timeout 10m; + # Many of these are overridden by matching settings outside of any server{} block! + # https://github.com/mozilla/ssl-config-generator/issues/76 + ssl_ecdh_curve X25519:prime256v1:secp384r1; + # https://ssl-config.mozilla.org/#server=nginx&config=modern + ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; + ssl_protocols TLSv1.3; + ssl_prefer_server_ciphers off; ssl_stapling on; ssl_stapling_verify on; - {% endif -%} - add_header Strict-Transport-Security "max-age=63072000; preload"; - {% endif -%} + {% if ansible_os_family in ["Debian", "Archlinux"] -%} + ssl_trusted_certificates /etc/ssl/certs/ca-certificates.crt; + {% elif ansible_os_family in ["RedHat"] -%} + ssl_trusted_certificates /etc/ssl/certs/ca-bundle.crt; + {% else -%} {# Missing: Suse, Gentoo, Mandrake #} + # Please set ssl_trusted_certificates using funkwhale_nginx_additional_config and open an issue + {% endif -%} {# ansible_os_family #} + {% endif -%} {# funkwhale_nginx_tls_configure_ciphers #} + add_header Strict-Transport-Security "max-age=31536000" always; + {% endif -%} {# funkwhale_nginx_tls_termination #} {% if funkwhale_nginx_csp_policy -%} - # Security-related headers add_header Content-Security-Policy "{{ funkwhale_nginx_csp_policy }}"; {% endif -%} + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Service-Worker-Allowed "/"; root {{ funkwhale_frontend_path }}; @@ -72,79 +106,101 @@ server { {% endif -%} # end of compression settings - location / { + location /api/ { include /etc/nginx/funkwhale_proxy.conf; - # this is needed if you have file import via upload enabled + # This is needed if you have file import via upload enabled. client_max_body_size {{ funkwhale_nginx_max_body_size }}; - proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/; + proxy_pass http://funkwhale-api; + } + {% if funkwhale_disable_django_admin -%} + location /api/admin/ { + # disable access to API admin dashboard + return 403; } + {% endif -%} - location /front/ { + location / { alias {{ funkwhale_frontend_path }}/; - expires 30d; + expires 1d; add_header Pragma public; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; add_header Service-Worker-Allowed "/"; + try_files $uri $uri/ /index.html; + } + + location ~ "/(front/)?embed.html" { + add_header Content-Security-Policy "connect-src https: http: 'self'; default-src 'self'; script-src 'self' unpkg.com 'unsafe-inline' 'unsafe-eval'; style-src https: http: 'self' 'unsafe-inline'; img-src https: http: 'self' data:; font-src https: http: 'self' data:; object-src 'none'; media-src https: http: 'self' data:"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + alias {{ funkwhale_frontend_path }}/embed.html; + expires 1d; } location /federation/ { include /etc/nginx/funkwhale_proxy.conf; - proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/federation/; + proxy_pass http://funkwhale-api; } - # You can comment this if you do not plan to use the Subsonic API + # You can comment this if you do not plan to use the Subsonic API. location /rest/ { include /etc/nginx/funkwhale_proxy.conf; - proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/api/subsonic/rest/; + proxy_pass http://funkwhale-api/api/subsonic/rest/; } location /.well-known/ { + allow all; include /etc/nginx/funkwhale_proxy.conf; - proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/.well-known/; + proxy_pass http://funkwhale-api; } - location /media/ { - alias {{ funkwhale_media_path }}/; + # Allow direct access to only specific subdirectories in /media + location /media/__sized__/ { + alias {{ funkwhale_media_path }}/__sized__/; + add_header Access-Control-Allow-Origin '*'; + } + + # Allow direct access to only specific subdirectories in /media + location /media/attachments/ { + alias {{ funkwhale_media_path }}/attachments/; + add_header Access-Control-Allow-Origin '*'; + } + + # Allow direct access to only specific subdirectories in /media + location /media/dynamic_preferences/ { + alias {{ funkwhale_media_path }}/dynamic_preferences/; + add_header Access-Control-Allow-Origin '*'; } {% if funkwhale_external_storage_enabled -%} - # Comment the previous location and uncomment this one if you're storing - # media files in a S3 bucket + # This is an internal location that is used to serve + # media (uploaded) files once correct permission / authentication + # has been checked on API side. location ~ /_protected/media/(.+) { - # Needed to ensure DSub auth isn't forwarded to S3/Minio, see #932 - proxy_set_header Authorization ""; internal; - proxy_pass $1; + # Needed to ensure DSub auth isn't forwarded to S3/Minio, see #932. + proxy_set_header Authorization ""; # S3 + proxy_pass $1; # S3 + add_header Access-Control-Allow-Origin '*'; } {% else -%} - location /_protected/media { - # this is an internal location that is used to serve - # audio files once correct permission / authentication - # has been checked on API side + location /_protected/media/ { internal; - alias {{ funkwhale_media_path }}; + alias {{ funkwhale_media_path }}/; + add_header Access-Control-Allow-Origin '*'; } {% endif %} - location /_protected/music { - # this is an internal location that is used to serve - # audio files once correct permission / authentication - # has been checked on API side - # Set this to the same value as your MUSIC_DIRECTORY_PATH setting + location /_protected/music/ { + # This is an internal location that is used to serve + # local music files once correct permission / authentication + # has been checked on API side. internal; - alias {{ funkwhale_music_path }}; + alias {{ funkwhale_music_path }}/; + add_header Access-Control-Allow-Origin '*'; } - location /staticfiles/ { - # django static files - alias {{ funkwhale_static_path }}/; + location /manifest.json { + return 302 /api/v1/instance/spa-manifest.json; } -{% if funkwhale_disable_django_admin -%} - - location /api/admin/ { - # disable access to API admin dashboard - return 403; - } -{% endif -%} {{ funkwhale_nginx_additional_config }} }