# The INVENTAIRE_PORT and PUBLIC_HOSTNAME variables are set with nginx image function, # which will extract environment variables before nginx starts # See https://hub.docker.com/_/nginx upstream inv { server inventaire:${INVENTAIRE_PORT} fail_timeout=5s; } # Using error_page as a way to have a named location that can # then be shared between several locations, see: # https://serverfault.com/questions/908086/nginx-directly-send-from-location-to-another-named-location # https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/#what-to-do-instead # Contrary to what the documentation says, the HTTP verbs aren't all converted to GET # http://nginx.org/en/docs/http/ngx_http_core_module.html#error_page error_page 543 = @invserver; server { listen 80; listen [::]:80; # Required to be able to run `certbot -w /var/www/html/` location /.well-known/ { root /var/www/html/; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name ${PUBLIC_HOSTNAME}; ssl_certificate /etc/letsencrypt/live/${PUBLIC_HOSTNAME}/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/${PUBLIC_HOSTNAME}/privkey.pem; include /etc/nginx/snippets/ssl.conf; client_max_body_size 25M; # Disabling compression to mitigate BREACH exploit # https://en.wikipedia.org/wiki/BREACH_(security_exploit)#Mitigation # http://security.stackexchange.com/questions/39925/breach-a-new-attack-against-http-what-can-be-done # until we can confidently say that HTTP/2 solves the issue? https://blog.cloudflare.com/hpack-the-silent-killer-feature-of-http-2 gzip off; # On-The-Fly Image Resizer # URLs look like /img/users/300x1200/8185d4e039f52b4faa06a1c277133e9a8232551b # for locally hosted images # or /img/remote/300x1200/630022006?href=http%3A%2F%2Fescaped.url # for remote images, with 630022006 being the hash of the passed href # generated by [hashCode](https://git.inventaire.io/inventaire/blob/35b1e63/server/lib/utils/base.js#L69-L80) # The hack: I couldn't make the proxy_store work: it never hits the cache, but # it does put the resized images in /tmp/nginx/resize, so using a try_files # directive instead # Sometimes, for some unidentified reason, the cached files end up empty, so it can be useful to add a root cron to remove those files: # 0 4 * * * /usr/bin/find /tmp/nginx -type f -size 0 -delete # Do not remove the (.*) capture group as it seems to be required by the try_files location ~ ^/img/(groups|users|entities|assets)/(.*) { include /etc/nginx/snippets/security_headers.conf; root /tmp/nginx/resize; default_type "image/jpeg"; add_header Cache-Control "public, max-age=31536000, immutable"; add_header X-File-Cache "hit"; add_header Content-Security-Policy "sandbox"; try_files $uri @invimg; limit_except GET { deny all; } } # Same as above, but without the immutable location ~ ^/img/remote/(.*) { include /etc/nginx/snippets/security_headers.conf; root /tmp/nginx/resize; default_type "image/jpeg"; add_header X-File-Cache "hit"; add_header Content-Security-Policy "sandbox"; try_files $uri @invimg; limit_except GET { deny all; } } location ~ ^/img/ { return 404; } location @invimg { include /etc/nginx/snippets/security_headers.conf; default_type "image/jpeg"; # add_header Content-Type "image/jpeg"; add_header X-File-Cache "miss"; add_header Content-Security-Policy "sandbox"; proxy_temp_path /tmp/nginx/tmp; proxy_store /tmp/nginx/resize/$uri; proxy_store_access user:rw group:rw all:r; proxy_http_version 1.1; proxy_pass http://inv; } root /opt/inventaire/client; location /public { include /etc/nginx/snippets/security_headers.conf; limit_except GET { deny all; } gzip_static on; # Let resources that can't be cache busted # - such as opensearch.xml or robots.txt - # out of this caching policy if ($uri ~ "^/public/(dist|fonts)/" ) { include /etc/nginx/snippets/security_headers.conf; add_header Cache-Control "public, max-age=31536000, immutable"; # All headers that aren't in the last block won't be taken in account # thus the need to have CORS headers here too add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET' always; } } # Pass the request to the node.js server # with some correct headers for proxy-awareness location /api { return 543; } location /.well-known/webfinger { return 543; } # Let the API server handle all but /public JSON and RSS requests location ~ "^/[^p].*\.(json|rss)$" { limit_except GET { deny all; } return 543; } location @invserver { include /etc/nginx/snippets/security_headers.conf; # Let the server decide when CORS headers should be added proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto https; proxy_set_header Host $host; # Set a large value to let the API determine the appropriate # timeout per endpoint # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout proxy_read_timeout 3600; proxy_redirect off; proxy_http_version 1.1; proxy_pass http://inv; } location = /favicon.ico { include /etc/nginx/snippets/security_headers.conf; try_files /public/$uri /public/images/$uri; expires 30d; add_header Cache-Control "public"; } location = /robots.txt { include /etc/nginx/snippets/security_headers.conf; gzip_static on; try_files /public/$uri /$uri; expires 1d; add_header Cache-Control "public"; } # Prevent exposing git folders such as /public/i18n/.git # For why this rule takes precedence over location /public/ # see http://stackoverflow.com/a/34262192/3324977 location ~ /\.git { deny all; } location ^~ '/.well-known/acme-challenge' { include /etc/nginx/snippets/security_headers.conf; default_type "text/plain"; root /var/www/certbot; } location / { include /etc/nginx/snippets/security_headers.conf; gzip_static on; limit_except GET { deny all; } # index.html should always be fresh out of the server # time is negative => “Cache-Control: no-cache” # http://nginx.org/en/docs/http/ngx_http_headers_module.html # Those headers should be set here and not at "location /" as they would be ignored (cf http://serverfault.com/a/786248) expires -1; # The remaining routes (/users, /entity, etc) should be handled by the client router rewrite .* /public/index.html break; } }