diff --git a/changes/changelog.d/nginxtemplates.feature b/changes/changelog.d/nginxtemplates.feature new file mode 100644 index 000000000..69755d4ed --- /dev/null +++ b/changes/changelog.d/nginxtemplates.feature @@ -0,0 +1 @@ +Generate all nginx configurations from one template diff --git a/deploy/docker.proxy.template b/deploy/docker.proxy.template index a4b297f20..e208c5ef2 100644 --- a/deploy/docker.proxy.template +++ b/deploy/docker.proxy.template @@ -1,7 +1,8 @@ upstream fw { - # depending on your setup, you may want to update this server ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}; } + +# Required for websocket support. map $http_upgrade $connection_upgrade { default upgrade; '' close; @@ -10,15 +11,31 @@ map $http_upgrade $connection_upgrade { server { listen 80; listen [::]:80; + # update this to match your instance name server_name ${FUNKWHALE_HOSTNAME}; - location / { return 301 https://$host$request_uri; } + + # useful for Let's Encrypt + location /.well-known/acme-challenge/ { + allow all; + } + + location / { + return 301 https://$host$request_uri; + } } + server { listen 443 ssl http2; listen [::]:443 ssl http2; + server_name ${FUNKWHALE_HOSTNAME}; # TLS + # Feel free to use your own configuration for SSL here or simply remove the + # lines and move the configuration to the previous server block if you + # don't want to run funkwhale behind https (this is not recommended) + # have a look here for let's encrypt configuration: + # https://certbot.eff.org/all-instructions/#debian-9-stretch-nginx ssl_protocols TLSv1.2; ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; ssl_prefer_server_ciphers on; @@ -29,12 +46,10 @@ server { # HSTS add_header Strict-Transport-Security "max-age=31536000"; - # Security related headers - # If you are using S3 to host your files, remember to add your S3 URL to the - # media-src and img-src headers (e.g. img-src 'self' https:// data:) - - 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:"; + # General configs + client_max_body_size ${NGINX_MAX_BODY_SIZE}; + charset utf-8; # compression settings gzip on; @@ -42,7 +57,6 @@ server { gzip_min_length 256; gzip_proxied any; gzip_vary on; - gzip_types application/javascript application/vnd.geo+json @@ -61,10 +75,13 @@ server { text/vtt text/x-component text/x-cross-domain-policy; + # end of compression settings + + location / { - include /etc/nginx/funkwhale_proxy.conf; - client_max_body_size ${NGINX_MAX_BODY_SIZE}; - proxy_pass http://fw; + expires 1d; + proxy_pass http://fw } + } diff --git a/deploy/nginx.template b/deploy/nginx.template index 5ba2ccfa1..d8ba11962 100644 --- a/deploy/nginx.template +++ b/deploy/nginx.template @@ -1,10 +1,15 @@ -# This file was generated from Funkwhale's nginx.template upstream funkwhale-api { # depending on your setup, you may want to update this server ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}; } +# Required for websocket support. +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + server { listen 80; listen [::]:80; @@ -21,16 +26,10 @@ server { } } -# Required for websocket support. -map $http_upgrade $connection_upgrade { - default upgrade; - '' close; -} - server { listen 443 ssl http2; listen [::]:443 ssl http2; - charset utf-8; + server_name ${FUNKWHALE_HOSTNAME}; # TLS @@ -49,12 +48,11 @@ server { # HSTS add_header Strict-Transport-Security "max-age=31536000"; - add_header Content-Security-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'"; - 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}; + # General configs + root ${FUNKWHALE_FRONTEND_PATH}, + client_max_body_size ${NGINX_MAX_BODY_SIZE}; + charset utf-8; # compression settings gzip on; @@ -62,7 +60,6 @@ server { gzip_min_length 256; gzip_proxied any; gzip_vary on; - gzip_types application/javascript application/vnd.geo+json @@ -83,6 +80,12 @@ server { text/x-cross-domain-policy; # end of compression settings + # headers + add_header Content-Security-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'"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Service-Worker-Allowed "/"; + location /api/ { include /etc/nginx/funkwhale_proxy.conf; # This is needed if you have file import via upload enabled. @@ -91,16 +94,16 @@ server { } location / { - alias ${FUNKWHALE_FRONTEND_PATH}/; expires 1d; + alias ${FUNKWHALE_FRONTEND_PATH}/, try_files $uri $uri/ /index.html; } location ~ "/(front/)?embed.html" { + alias ${FUNKWHALE_FRONTEND_PATH}/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; } @@ -158,7 +161,7 @@ server { # has been checked on API side. # Set this to the same value as your MUSIC_DIRECTORY_PATH setting. internal; - alias ${MUSIC_DIRECTORY_SERVE_PATH}/; + alias ${MUSIC_DIRECTORY_PATH}/; add_header Access-Control-Allow-Origin '*'; } @@ -166,4 +169,5 @@ server { # If the reverse proxy is terminating SSL, nginx gets confused and redirects to http, hence the full URL return 302 ${FUNKWHALE_PROTOCOL}://${FUNKWHALE_HOSTNAME}/api/v1/instance/spa-manifest.json; } + } diff --git a/docker/nginx/conf.dev b/docker/nginx/conf.dev index e4717bc2f..aecf2688d 100644 --- a/docker/nginx/conf.dev +++ b/docker/nginx/conf.dev @@ -1,6 +1,9 @@ + upstream funkwhale-api { + # depending on your setup, you may want to update this server ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}; } + upstream funkwhale-front { server ${FUNKWHALE_FRONT_IP}:${FUNKWHALE_FRONT_PORT}; } @@ -11,17 +14,18 @@ map $http_upgrade $connection_upgrade { '' close; } + server { listen 80; listen [::]:80; - charset utf-8; - client_max_body_size ${NGINX_MAX_BODY_SIZE}; - include /etc/nginx/funkwhale_proxy.conf; - add_header Content-Security-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'"; - add_header Referrer-Policy "strict-origin-when-cross-origin"; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header Service-Worker-Allowed "/"; + server_name _; + + + # General configs + root /usr/share/nginx/html; + client_max_body_size ${NGINX_MAX_BODY_SIZE}; + charset utf-8; # compression settings gzip on; @@ -29,7 +33,6 @@ server { gzip_min_length 256; gzip_proxied any; gzip_vary on; - gzip_types application/javascript application/vnd.geo+json @@ -50,6 +53,12 @@ server { text/x-cross-domain-policy; # end of compression settings + # headers + add_header Content-Security-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'"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Service-Worker-Allowed "/"; + location /api/ { include /etc/nginx/funkwhale_proxy.conf; # This is needed if you have file import via upload enabled. @@ -58,15 +67,15 @@ server { } location / { - proxy_pass http://funkwhale-front; expires 1d; + proxy_pass http://funkwhale-front; } location = /embed.html { + proxy_pass http://funkwhale-front; 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"; - proxy_pass http://funkwhale-front; expires 1d; } @@ -81,14 +90,6 @@ server { proxy_pass http://funkwhale-api/api/subsonic/rest/; } - location /media/__sized__/ { - alias /protected/media/__sized__/; - } - - location /media/attachments/ { - alias /protected/media/attachments/; - } - location /.well-known/ { include /etc/nginx/funkwhale_proxy.conf; proxy_pass http://funkwhale-api; @@ -96,13 +97,13 @@ server { # Allow direct access to only specific subdirectories in /media location /media/__sized__/ { - alias /protected/media/__sized__/; + alias ${MEDIA_ROOT}/__sized__/; add_header Access-Control-Allow-Origin '*'; } # Allow direct access to only specific subdirectories in /media location /media/attachments/ { - alias /protected/media/attachments/; + alias ${MEDIA_ROOT}/attachments/; add_header Access-Control-Allow-Origin '*'; } @@ -119,10 +120,10 @@ server { # if you're storing media files in a S3 bucket. location ~ /_protected/media/(.+) { internal; - alias /protected/media/$1; # NON-S3 + alias ${MEDIA_ROOT}/$1; # NON-S3 # Needed to ensure DSub auth isn't forwarded to S3/Minio, see #932. -# proxy_set_header Authorization ""; # S3 -# proxy_pass $1; # S3 +# proxy_set_header Authorization ""; # S3 +# proxy_pass $1; # S3 add_header Access-Control-Allow-Origin '*'; } @@ -132,7 +133,7 @@ server { # has been checked on API side. # Set this to the same value as your MUSIC_DIRECTORY_PATH setting. internal; - alias /music/; + alias ${MUSIC_DIRECTORY_PATH}/; add_header Access-Control-Allow-Origin '*'; } @@ -142,7 +143,6 @@ server { } location /staticfiles/ { - alias /staticfiles/; + alias /usr/share/nginx/html/staticfiles/; } - } diff --git a/front/docker/funkwhale.conf.template b/front/docker/funkwhale.conf.template index af2fac74d..abbf636d5 100644 --- a/front/docker/funkwhale.conf.template +++ b/front/docker/funkwhale.conf.template @@ -1,5 +1,7 @@ + upstream funkwhale-api { - server ${FUNKWHALE_API_HOST}:${FUNKWHALE_API_PORT}; + # depending on your setup, you may want to update this + server ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}; } # Required for websocket support. @@ -8,18 +10,18 @@ map $http_upgrade $connection_upgrade { '' close; } + server { listen 80; listen [::]:80; - charset utf-8; + server_name _; - add_header Content-Security-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'"; - add_header Referrer-Policy "strict-origin-when-cross-origin"; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header Service-Worker-Allowed "/"; + # General configs root /usr/share/nginx/html; + client_max_body_size ${NGINX_MAX_BODY_SIZE}; + charset utf-8; # compression settings gzip on; @@ -27,7 +29,6 @@ server { gzip_min_length 256; gzip_proxied any; gzip_vary on; - gzip_types application/javascript application/vnd.geo+json @@ -48,6 +49,12 @@ server { text/x-cross-domain-policy; # end of compression settings + # headers + add_header Content-Security-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'"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Service-Worker-Allowed "/"; + location /api/ { include /etc/nginx/funkwhale_proxy.conf; # This is needed if you have file import via upload enabled. @@ -56,16 +63,16 @@ server { } location / { - alias /usr/share/nginx/html/; expires 1d; + alias /usr/share/nginx/html/; try_files $uri $uri/ /index.html; } location ~ "/(front/)?embed.html" { + alias /usr/share/nginx/html/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 /usr/share/nginx/html/embed.html; expires 1d; } @@ -131,4 +138,5 @@ server { # If the reverse proxy is terminating SSL, nginx gets confused and redirects to http, hence the full URL return 302 ${FUNKWHALE_PROTOCOL}://${FUNKWHALE_HOSTNAME}/api/v1/instance/spa-manifest.json; } + } diff --git a/scripts/compile-templates.py b/scripts/compile-templates.py new file mode 100644 index 000000000..596e7bb70 --- /dev/null +++ b/scripts/compile-templates.py @@ -0,0 +1,38 @@ +from jinja2 import Environment, FileSystemLoader + +file_loader = FileSystemLoader("templates") +env = Environment( + loader=file_loader, trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True +) + +files = [ + { + "output": "docker/nginx/conf.dev", + "config": {"proxy_frontend": True, "inside_docker": True}, + }, + { + "output": "front/docker/funkwhale.conf.template", + "config": {"proxy_frontend": False, "inside_docker": True}, + }, + { + "output": "deploy/nginx.template", + "config": {"proxy_frontend": False, "inside_docker": False}, + }, + { + "output": "deploy/docker.proxy.template", + "config": { + "proxy_frontend": False, + "inside_docker": False, + "reverse_proxy": True, + }, + }, +] + +template = env.get_template("nginx.conf.j2") +for f in files: + print(f["output"]) + output = template.render(config=f["config"]) + + output_file = open(f["output"], "w") + output_file.write(output) + output_file.close() diff --git a/templates/nginx.conf.j2 b/templates/nginx.conf.j2 new file mode 100644 index 000000000..8b88ced42 --- /dev/null +++ b/templates/nginx.conf.j2 @@ -0,0 +1,231 @@ +{% if config.reverse_proxy %} +upstream fw { + server ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}; +} +{% else %} + +upstream funkwhale-api { + # depending on your setup, you may want to update this + server ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT}; +} +{% endif %} +{% if config.proxy_frontend %} + +upstream funkwhale-front { + server ${FUNKWHALE_FRONT_IP}:${FUNKWHALE_FRONT_PORT}; +} +{% endif %} + +# Required for websocket support. +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +{% if not config.inside_docker %} +server { + listen 80; + listen [::]:80; + # update this to match your instance name + server_name ${FUNKWHALE_HOSTNAME}; + + # useful for Let's Encrypt + location /.well-known/acme-challenge/ { + allow all; + } + + location / { + return 301 https://$host$request_uri; + } +} +{% endif %} + +server { +{% if not config.inside_docker %} + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name ${FUNKWHALE_HOSTNAME}; + + # TLS + # Feel free to use your own configuration for SSL here or simply remove the + # lines and move the configuration to the previous server block if you + # don't want to run funkwhale behind https (this is not recommended) + # have a look here for let's encrypt configuration: + # https://certbot.eff.org/all-instructions/#debian-9-stretch-nginx + ssl_protocols TLSv1.2; + ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_certificate /etc/letsencrypt/live/${FUNKWHALE_HOSTNAME}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/${FUNKWHALE_HOSTNAME}/privkey.pem; + + # HSTS + add_header Strict-Transport-Security "max-age=31536000"; + +{% else %} + listen 80; + listen [::]:80; + + server_name _; + +{% endif %} + + # General configs +{% if not config.reverse_proxy %} +{% if config.inside_docker %} + root /usr/share/nginx/html; +{% else %} + root ${FUNKWHALE_FRONTEND_PATH}, +{% endif %} +{% endif %} + client_max_body_size ${NGINX_MAX_BODY_SIZE}; + charset utf-8; + + # compression settings + gzip on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_vary on; + gzip_types + application/javascript + application/vnd.geo+json + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + font/opentype + image/bmp + image/svg+xml + image/x-icon + text/cache-manifest + text/css + text/plain + text/vcard + text/vnd.rim.location.xloc + text/vtt + text/x-component + text/x-cross-domain-policy; + # end of compression settings + +{% if not config.reverse_proxy %} + # headers + add_header Content-Security-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'"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Service-Worker-Allowed "/"; +{% endif %} + +{% if not config.reverse_proxy %} + location /api/ { + include /etc/nginx/funkwhale_proxy.conf; + # This is needed if you have file import via upload enabled. + client_max_body_size ${NGINX_MAX_BODY_SIZE}; + proxy_pass http://funkwhale-api; + } +{% endif %} + + location / { + expires 1d; +{% if config.proxy_frontend and not config.reverse_proxy %} + proxy_pass http://funkwhale-front; +{% elif not config.proxy_frontend and config.reverse_proxy %} + proxy_pass http://fw +{% else %} +{% if config.inside_docker %} + alias /usr/share/nginx/html/; +{% else %} + alias ${FUNKWHALE_FRONTEND_PATH}/, +{% endif %} + try_files $uri $uri/ /index.html; +{% endif %} + } + +{% if not config.reverse_proxy %} +{% if config.proxy_frontend %} + location = /embed.html { + proxy_pass http://funkwhale-front; +{% else %} + location ~ "/(front/)?embed.html" { +{% if config.inside_docker %} + alias /usr/share/nginx/html/embed.html; +{% else %} + alias ${FUNKWHALE_FRONTEND_PATH}/embed.html, +{% endif %} +{% endif %} + 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"; + + expires 1d; + } + + location /federation/ { + include /etc/nginx/funkwhale_proxy.conf; + proxy_pass http://funkwhale-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/api/subsonic/rest/; + } + + location /.well-known/ { + include /etc/nginx/funkwhale_proxy.conf; + proxy_pass http://funkwhale-api; + } + + # Allow direct access to only specific subdirectories in /media + location /media/__sized__/ { + alias ${MEDIA_ROOT}/__sized__/; + add_header Access-Control-Allow-Origin '*'; + } + + # Allow direct access to only specific subdirectories in /media + location /media/attachments/ { + alias ${MEDIA_ROOT}/attachments/; + add_header Access-Control-Allow-Origin '*'; + } + + # Allow direct access to only specific subdirectories in /media + location /media/dynamic_preferences/ { + alias ${MEDIA_ROOT}/dynamic_preferences/; + add_header Access-Control-Allow-Origin '*'; + } + + # This is an internal location that is used to serve + # media (uploaded) files once correct permission / authentication + # has been checked on API side. + # Comment the "NON-S3" commented lines and uncomment "S3" commented lines + # if you're storing media files in a S3 bucket. + location ~ /_protected/media/(.+) { + internal; + alias ${MEDIA_ROOT}/$1; # NON-S3 + # 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 '*'; + } + + 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. + # Set this to the same value as your MUSIC_DIRECTORY_PATH setting. + internal; + alias ${MUSIC_DIRECTORY_PATH}/; + add_header Access-Control-Allow-Origin '*'; + } + + location /manifest.json { + # If the reverse proxy is terminating SSL, nginx gets confused and redirects to http, hence the full URL + return 302 ${FUNKWHALE_PROTOCOL}://${FUNKWHALE_HOSTNAME}/api/v1/instance/spa-manifest.json; + } + +{% if config.proxy_frontend %} + location /staticfiles/ { + alias /usr/share/nginx/html/staticfiles/; + } +{% endif %} +{% endif %} +}