diff --git a/Dockerfile b/Dockerfile index c77b92834a..83114e44bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,5 +35,6 @@ ENV SCRUMLR_ANALYTICS_DATA_DOMAIN='' ENV SCRUMLR_ANALYTICS_SRC='' COPY ./nginx.conf /etc/nginx/templates/scrumlr.io.conf.template +COPY ./security-headers.conf /etc/nginx/conf.d/security-headers.conf COPY --from=build-stage /usr/src/app/build /usr/share/nginx/html RUN rm /etc/nginx/conf.d/default.conf diff --git a/deployment/docker/docker-compose.yml b/deployment/docker/docker-compose.yml index 3e63c554d5..27f3da84aa 100644 --- a/deployment/docker/docker-compose.yml +++ b/deployment/docker/docker-compose.yml @@ -4,7 +4,7 @@ services: scrumlr-backend: restart: always build: - context: ../server/src/ + context: ../../server/src/ dockerfile: Dockerfile command: - "/app/main" @@ -42,7 +42,7 @@ services: scrumlr-frontend: restart: always build: - context: ../. + context: ../../. dockerfile: Dockerfile environment: SCRUMLR_SERVER_URL: "${SCRUMLR_SERVER_URL}" diff --git a/nginx.conf b/nginx.conf index 73dcd7248c..7ad0b13156 100644 --- a/nginx.conf +++ b/nginx.conf @@ -5,14 +5,14 @@ server { server_name beta.scrumlr.io scrumler.io scrumlr.de; location / { - return 301 https://scrumlr.io$request_uri; + return 301 https://scrumlr.io$request_uri; } } server { listen ${SCRUMLR_LISTEN_PORT} default_server; listen [::]:${SCRUMLR_LISTEN_PORT} default_server; - server_name _; + server_name _; root /usr/share/nginx/html; index index.html index.htm; @@ -20,44 +20,51 @@ server { gzip on; gzip_vary on; gzip_comp_level 6; - gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; gzip_proxied no-cache no-store private expired auth; gzip_min_length 1024; gzip_disable "MSIE [1-6]\."; + include /etc/nginx/conf.d/security-headers.conf; + location / { - try_files $uri $uri/ /index.html; - add_header Cache-Control "no-store, no-cache, must-revalidate"; + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + include /etc/nginx/conf.d/security-headers.conf; } location /index.html { - # Application specific feature toggles - add_header Set-Cookie "scrumlr__show-legal-documents=${SCRUMLR_SHOW_LEGAL_DOCUMENTS};Path=/;Max-Age=3600"; - add_header Set-Cookie "scrumlr__server-url=${SCRUMLR_SERVER_URL};Path=/;Max-Age=3600"; - add_header Set-Cookie "scrumlr__websocket-url=${SCRUMLR_WEBSOCKET_URL};Path=/;Max-Age=3600"; - add_header Set-Cookie "scrumlr__analytics_data_domain=${SCRUMLR_ANALYTICS_DATA_DOMAIN};Path=/;Max-Age=3600"; - add_header Set-Cookie "scrumlr__analytics_src=${SCRUMLR_ANALYTICS_SRC};Path=/;Max-Age=3600"; - - # Disable caching for index.html - add_header Cache-Control "no-store, no-cache, must-revalidate"; - add_header Pragma no-cache; - expires 0; + # Application specific feature toggles + add_header Set-Cookie "scrumlr__show-legal-documents=${SCRUMLR_SHOW_LEGAL_DOCUMENTS};Path=/;Max-Age=3600"; + add_header Set-Cookie "scrumlr__server-url=${SCRUMLR_SERVER_URL};Path=/;Max-Age=3600"; + add_header Set-Cookie "scrumlr__websocket-url=${SCRUMLR_WEBSOCKET_URL};Path=/;Max-Age=3600"; + add_header Set-Cookie "scrumlr__analytics_data_domain=${SCRUMLR_ANALYTICS_DATA_DOMAIN};Path=/;Max-Age=3600"; + add_header Set-Cookie "scrumlr__analytics_src=${SCRUMLR_ANALYTICS_SRC};Path=/;Max-Age=3600"; + + # Disable caching for index.html + add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header Pragma no-cache; + expires 0; + include /etc/nginx/conf.d/security-headers.conf; } location ~* \.(js|json)$ { # Cache JS and JSON files for 3 days expires 3d; add_header Cache-Control "public, must-revalidate"; + include /etc/nginx/conf.d/security-headers.conf; } location ~* \.(jpg|jpeg|gif|png|svg|css)$ { # Cache other media files for 14 days expires 14d; - add_header Cache-Control "public"; + add_header Cache-Control "public, must-revalidate"; + include /etc/nginx/conf.d/security-headers.conf; } location ~* \.(ico|mp3)$ { - # Cache .mp3 and .ico files for 365 days - add_header Cache-Control "max-age=36792000, public"; + # Cache .mp3 and .ico files for 365 days + add_header Cache-Control "max-age=36792000, public"; + include /etc/nginx/conf.d/security-headers.conf; } } diff --git a/security-headers.conf b/security-headers.conf new file mode 100644 index 0000000000..fd32b5744a --- /dev/null +++ b/security-headers.conf @@ -0,0 +1,8 @@ +# Security headers +add_header X-XSS-Protection "1; mode=block" always; +add_header X-Content-Type-Options "nosniff" always; +add_header Referrer-Policy "no-referrer-when-downgrade" always; +add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always; +add_header Permissions-Policy "interest-cohort=()" always; +add_header X-Frame-Options "DENY" always; +add_header Expect-CT "max-age=86400, enforce" always;