diff options
author | Dominique Martinet @ jormungand <asmadeus@codewreck.org> | 2020-09-01 20:37:01 +0200 |
---|---|---|
committer | Dominique Martinet @ jormungand <asmadeus@codewreck.org> | 2020-09-01 20:37:03 +0200 |
commit | 5c999697f8f4ed0c90f31a69f3950c19f67eb0a5 (patch) | |
tree | 8281b27ded3562ca56505147e83a4b0e8e86dfa6 /machines/jormungand/nginx.nix | |
parent | 1f40be29fe8cc60cf534e37404011f253dbb266e (diff) |
cryptpad: use nginx more profusely
There is a problem in using a simple proxy when it comes to blob, the
server does not handle $HOME and its node_modules/cryptpad dir being
different correctly.
Using nginx lets us:
- bind data read only for the server home and serve it where it expects
- disable /register kludgingly
- probably a bit more efficient? meh.
Diffstat (limited to 'machines/jormungand/nginx.nix')
-rw-r--r-- | machines/jormungand/nginx.nix | 205 |
1 files changed, 195 insertions, 10 deletions
diff --git a/machines/jormungand/nginx.nix b/machines/jormungand/nginx.nix index c10d7f0..6812a93 100644 --- a/machines/jormungand/nginx.nix +++ b/machines/jormungand/nginx.nix @@ -1,10 +1,201 @@ { config, pkgs, ... }: -{ +let + cryptpadConfig = '' + # CryptPad serves static assets over these two domains. + # `main_domain` is what users will enter in their address bar. + # Privileged computation such as key management is handled in this scope + # UI content is loaded via the `sandbox_domain`. + # "Content Security Policy" headers prevent content loaded via the sandbox + # from accessing privileged information. + # These variables must be different to take advantage of CryptPad's sandboxing techniques. + # In the event of an XSS vulnerability in CryptPad's front-end code + # this will limit the amount of information accessible to attackers. + set $main_domain "cryptpad.codewreck.org"; + set $sandbox_domain "cryptpad-sandbox.codewreck.org"; + + # CryptPad's dynamic content (websocket traffic and encrypted blobs) + # can be served over separate domains. Using dedicated domains (or subdomains) + # for these purposes allows you to move them to a separate machine at a later date + # if you find that a single machine cannot handle all of your users. + # If you don't use dedicated domains, this can be the same as $main_domain + # If you do, they'll be added as exceptions to any rules which block connections to remote domains. + set $api_domain "cryptpad.codewreck.org"; + set $files_domain "cryptpad.codewreck.org"; + + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options nosniff; + add_header Access-Control-Allow-Origin "*"; + # add_header X-Frame-Options "SAMEORIGIN"; + + # Insert the path to your CryptPad repository root here + root ${pkgs.cryptpad}/lib/node_modules/cryptpad; + index index.html; + error_page 404 /customize.dist/404.html; + + # any static assets loaded with "ver=" in their URL will be cached for a year + if ($args ~ ver=) { + set $cacheControl max-age=31536000; + } + # Will not set any header if it is emptystring + add_header Cache-Control $cacheControl; + + # CSS can be dynamically set inline, loaded from the same domain, or from $main_domain + set $styleSrc "'unsafe-inline' 'self' ''${main_domain}"; + + # connect-src restricts URLs which can be loaded using script interfaces + set $connectSrc "'self' https://''${main_domain} ''${main_domain} https://''${api_domain} blob: wss://''${api_domain} ''${api_domain} ''${files_domain}"; + + # fonts can be loaded from data-URLs or the main domain + set $fontSrc "'self' data: ''${main_domain}"; + + # images can be loaded from anywhere, though we'd like to deprecate this as it allows the use of images for tracking + set $imgSrc "'self' data: * blob: ''${main_domain}"; + + # frame-src specifies valid sources for nested browsing contexts. + # this prevents loading any iframes from anywhere other than the sandbox domain + set $frameSrc "'self' ''${sandbox_domain} blob:"; + + # specifies valid sources for loading media using video or audio + set $mediaSrc "'self' data: * blob: ''${main_domain}"; + + # defines valid sources for webworkers and nested browser contexts + # deprecated in favour of worker-src and frame-src + set $childSrc "https://''${main_domain}"; + + # specifies valid sources for Worker, SharedWorker, or ServiceWorker scripts. + # supercedes child-src but is unfortunately not yet universally supported. + set $workerSrc "https://''${main_domain}"; + + # script-src specifies valid sources for javascript, including inline handlers + set $scriptSrc "'self' resource: ''${main_domain}"; + + set $unsafe 0; + # the following assets are loaded via the sandbox domain + # they unfortunately still require exceptions to the sandboxing to work correctly. + if ($uri = "/pad/inner.html") { set $unsafe 1; } + if ($uri = "/sheet/inner.html") { set $unsafe 1; } + if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; } + + # everything except the sandbox domain is a privileged scope, as they might be used to handle keys + if ($host != $sandbox_domain) { set $unsafe 0; } + + # privileged contexts allow a few more rights than unprivileged contexts, though limits are still applied + if ($unsafe) { + set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' resource: ''${main_domain}"; + } + + # Finally, set all the rules you composed above. + add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;"; + + # The nodejs process can handle all traffic whether accessed over websocket or as static assets + # We prefer to serve static content from nginx directly and to leave the API server to handle + # the dynamic content that only it can manage. This is primarily an optimization + location ^~ /cryptpad_websocket { + proxy_pass http://[::1]:3000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # WebSocket support (nginx 1.4) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + } + + location ^~ /customize.dist/ { + # This is needed in order to prevent infinite recursion between /customize/ and the root + } + # try to load customizeable content via /customize/ and fall back to the default content + # located at /customize.dist/ + # This is what allows you to override behaviour. + location ^~ /customize/ { + rewrite ^/customize/(.*)$ $1 break; + try_files /customize/$uri /customize.dist/$uri; + } + + # /api/config is loaded once per page load and is used to retrieve + # the caching variable which is applied to every other resource + # which is loaded during that session. + location = /api/config { + proxy_pass http://[::1]:3000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # encrypted blobs are immutable and are thus cached for a year + location ^~ /blob/ { + root /www/cryptpad; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options nosniff; + add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;"; + if ($request_method = 'OPTIONS') { + add_header Cache-Control $cacheControl; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options nosniff; + add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;"; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'application/octet-stream; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + add_header Cache-Control max-age=31536000; + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; + add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; + try_files $uri =404; + } + + # the "block-store" serves encrypted payloads containing users' drive keys + # these payloads are unlocked via login credentials. They are mutable + # and are thus never cached. They're small enough that it doesn't matter, in any case. + location ^~ /block/ { + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options nosniff; + add_header Access-Control-Allow-Origin "*"; + add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;"; + add_header Cache-Control max-age=0; + try_files $uri =404; + } + + # This block provides an alternative means of loading content + # otherwise only served via websocket. This is solely for debugging purposes, + # and is thus not allowed by default. + #location ^~ /datastore/ { + #add_header Cache-Control max-age=0; + #try_files $uri =404; + #} + + # ugly hack: disable registration + location /register { + deny all; + } + + + # The nodejs server has some built-in forwarding rules to prevent + # URLs like /pad from resulting in a 404. This simply adds a trailing slash + # to a variety of applications. + location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams)$ { + rewrite ^(.*)$ $1/ redirect; + } + + # Finally, serve anything the above exceptions don't govern. + try_files /www/$uri /www/$uri/index.html /customize/$uri; + ''; +in { + imports = [ ../../profiles/nginx.nix ]; systemd.services.nginx = { - serviceConfig.BindReadOnlyPaths = [ "/home/asmadeus/www:/www/local" ]; + serviceConfig.BindReadOnlyPaths = [ + "/home/asmadeus/www:/www/local" + "/var/lib/private/cryptpad:/www/cryptpad" + ]; }; services.nginx.virtualHosts = { @@ -57,18 +248,12 @@ "cryptpad.codewreck.org" = { forceSSL = true; enableACME = true; - locations."/" = { - proxyPass = "http://[::1]:3000"; - proxyWebsockets = true; - }; + extraConfig = cryptpadConfig; }; "cryptpad-sandbox.codewreck.org" = { forceSSL = true; enableACME = true; - locations."/" = { - proxyPass = "http://[::1]:3001"; - proxyWebsockets = true; - }; + extraConfig = cryptpadConfig; }; }; } |