217 lines
7.4 KiB
Plaintext
Executable File
217 lines
7.4 KiB
Plaintext
Executable File
# 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;
|
|
}
|
|
}
|
|
|
|
location /assets {
|
|
root /home/admin/inventaire/client/public;
|
|
include /etc/nginx/snippets/security_headers.conf;
|
|
gzip_static on;
|
|
add_header Access-Control-Allow-Origin "*";
|
|
add_header Access-Control-Allow-Methods "GET,HEAD,OPTIONS";
|
|
add_header Access-Control-Allow-Headers "content-type";
|
|
}
|
|
|
|
# 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/html;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|