From 5c0c048b5414d99aec3f67e307c873672897b259 Mon Sep 17 00:00:00 2001 From: Julien Moutinho <julm@sourcephile.fr> Date: Thu, 24 Sep 2020 16:37:29 +0200 Subject: [PATCH] cryptpad: add service --- machines/losurdo/fileSystems.nix | 5 - machines/losurdo/nginx.nix | 3 + machines/losurdo/nginx/sourcephile.fr.nix | 3 +- .../losurdo/nginx/sourcephile.fr/cryptpad.nix | 298 ++++++++++++++++++ .../losurdo/nginx/sourcephile.fr/losurdo.nix | 3 +- machines/losurdo/postgresql.nix | 8 +- machines/losurdo/syncoid.nix | 4 - machines/mermet/knot/sourcephile.fr.nix | 4 + nixos/profiles/services/nginx.nix | 26 +- 9 files changed, 328 insertions(+), 26 deletions(-) create mode 100644 machines/losurdo/nginx/sourcephile.fr/cryptpad.nix diff --git a/machines/losurdo/fileSystems.nix b/machines/losurdo/fileSystems.nix index 473450b..fb8b124 100644 --- a/machines/losurdo/fileSystems.nix +++ b/machines/losurdo/fileSystems.nix @@ -4,11 +4,6 @@ imports = [ ../../nixos/profiles/systems/zfs.nix ]; -boot.kernelParams = [ - # Use arc_summary to print stats - "zfs.zfs_arc_max=${toString (1500 * 1024 * 1024)}" # bytes -]; - fileSystems."/" = { device = "${machineName}/root"; fsType = "zfs"; diff --git a/machines/losurdo/nginx.nix b/machines/losurdo/nginx.nix index d49b434..6e2fd5f 100644 --- a/machines/losurdo/nginx.nix +++ b/machines/losurdo/nginx.nix @@ -11,6 +11,7 @@ imports = [ users.groups."acme".members = [nginx.user]; users.groups."transmission".members = [nginx.user]; networking.nftables.ruleset = '' + add rule inet filter net2fw tcp dport 443 counter accept comment "HTTPS" add rule inet filter net2fw tcp dport 8443 counter accept comment "HTTPS" ''; services.nginx = { @@ -24,11 +25,13 @@ services.nginx = { addresses = [ "127.0.0.1:53" ]; valid = ""; }; + /* virtualHosts."_" = { listen = [ { addr = "0.0.0.0"; port = 8443; ssl = true; } ]; onlySSL = true; #forceSSL = true; useACMEHost = networking.domain; }; + */ }; } diff --git a/machines/losurdo/nginx/sourcephile.fr.nix b/machines/losurdo/nginx/sourcephile.fr.nix index 0a08fef..c0510e9 100644 --- a/machines/losurdo/nginx/sourcephile.fr.nix +++ b/machines/losurdo/nginx/sourcephile.fr.nix @@ -3,11 +3,12 @@ let domain = "sourcephile.fr"; in { imports = map (m: import m {inherit domain;}) [ sourcephile.fr/losurdo.nix + sourcephile.fr/cryptpad.nix ]; security.acme.certs."${domain}" = { postRun = "systemctl reload nginx"; }; -systemd.services.dovecot2 = { +systemd.services.nginx = { wants = [ "acme-selfsigned-${domain}.service" "acme-${domain}.service"]; after = [ "acme-selfsigned-${domain}.service" ]; }; diff --git a/machines/losurdo/nginx/sourcephile.fr/cryptpad.nix b/machines/losurdo/nginx/sourcephile.fr/cryptpad.nix new file mode 100644 index 0000000..2fb37e3 --- /dev/null +++ b/machines/losurdo/nginx/sourcephile.fr/cryptpad.nix @@ -0,0 +1,298 @@ +{ domain, ... }: +{ pkgs, lib, config, machineName, ... }: +let + inherit (config) networking; + inherit (config.services) nginx; + srv = "cryptpad"; + + # 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. + main_domain = "${srv}.${domain}"; + sandbox_domain = "${srv}-sandbox.${domain}"; + + # 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. + api_domain = "${srv}-api.${domain}"; + files_domain = "${srv}-files.${domain}"; + + # CSS can be dynamically set inline, loaded from the same domain, or from $main_domain + styleSrc = "'unsafe-inline' 'self' ${main_domain}"; + + # connect-src restricts URLs which can be loaded using script interfaces + 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 + 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 + 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 + frameSrc = "'self' ${sandbox_domain} blob:"; + + # specifies valid sources for loading media using video or audio + mediaSrc = "'self' data: * blob: ${main_domain}"; + + # defines valid sources for webworkers and nested browser contexts + # deprecated in favour of worker-src and frame-src + childSrc = "https://${main_domain}"; + + # specifies valid sources for Worker, SharedWorker, or ServiceWorker scripts. + # supercedes child-src but is unfortunately not yet universally supported. + workerSrc = "https://${main_domain}"; + + # add_header clear all parent add_header + # and thus must be repeated in sub-blocks + add_headers = '' + ${nginx.configs.https_add_headers} + add_header Access-Control-Allow-Origin "*"; + add_header Cache-Control $cacheControl; + add_header X-Frame-Options ""; + 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};"; + ''; + cryptpad_root = "/var/lib/nginx/cryptpad"; +in +{ +services.cryptpad = { + enable = true; + # DOC: https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js + configFile = pkgs.writeText "config.js" '' + module.exports = { + httpUnsafeOrigin: 'https://${main_domain}/', + httpSafeOrigin: "https://${sandbox_domain}/", + httpAddress: '::1', + httpPort: 3000, + httpSafePort: 3001, + maxWorkers: 1, + + /* ===================== + * Admin + * ===================== */ + adminKeys: [ + "https://cryptpad.sourcephile.fr/user/#/1/julm/s9y2aE8C5INMZSrR2yQbWBNcGpB8nSLZGFVhGHE8Nn8=", + ], + // supportMailboxPublicKey: "", + + removeDonateButton: true, + disableAnonymousStore: true, + disableCrowdfundingMessages: true, + adminEmail: 'root+cryptpad@sourcephile.fr', + blockDailyCheck: true, + defaultStorageLimit: 1024 * 1024 * 1024, + + /* ===================== + * STORAGE + * ===================== */ + //inactiveTime: 90, // days + //archiveRetentionTime: 15, + maxUploadSize: 256 * 1024 * 1024, + + /* + customLimits: { + "https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": { + limit: 20 * 1024 * 1024 * 1024, + plan: 'insider', + note: 'storage space donated by my.awesome.website' + }, + "https://my.awesome.website/user/#/1/cryptpad-user2/GdflkgdlkjeworijfkldfsdflkjeEAsdlEnkbx1vVOo=": { + limit: 10 * 1024 * 1024 * 1024, + plan: 'insider', + note: 'storage space donated by my.awesome.website' + } + }, + */ + + //premiumUploadSize: 100 * 1024 * 1024, + + /* ===================== + * DATABASE VOLUMES + * ===================== */ + filePath: './datastore/', + archivePath: './data/archive', + pinPath: './data/pins', + taskPath: './data/tasks', + blockPath: './block', + blobPath: './blob', + blobStagingPath: './data/blobstage', + logPath: './data/logs', + + /* ===================== + * Debugging + * ===================== */ + logToStdout: false, + logLevel: 'info', + logFeedback: false, + verbose: false, + }; + ''; +}; + +fileSystems."/var/lib/private/cryptpad" = { + device = "${machineName}/var/cryptpad"; + fsType = "zfs"; +}; + +services.syncoid.commands = { + "${machineName}/var/cryptpad" = { + sendOptions = "raw"; + target = "backup@mermet.${networking.domain}:rpool/backup/${machineName}/var/cryptpad"; + }; +}; + +systemd.services.nginx = { + #after = [ "cryptpad.service" ]; + #wants = [ "cryptpad.service" ]; + serviceConfig = { + BindReadOnlyPaths = [ + "/var/lib/private/cryptpad:${cryptpad_root}" + ]; + LogsDirectory = lib.mkForce [ + "nginx/${domain}/${srv}" + ]; + }; +}; + +services.nginx = { + virtualHosts.${main_domain} = { + serverAliases = [ sandbox_domain ]; + listen = [ + { addr="0.0.0.0"; port = 443; ssl = true; } + { addr="[::]"; port = 443; ssl = true; } + ]; + useACMEHost = domain; + forceSSL = true; + + # DOC: https://github.com/xwiki-labs/cryptpad/blob/master/docs/example.nginx.conf + root = "${pkgs.cryptpad}/lib/node_modules/cryptpad"; + + # 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 + locations."^~ /cryptpad_websocket" = { + proxyPass = "http://[::1]:3000"; + proxyWebsockets = true; + }; + + locations."^~ /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. + locations."^~ /customize/".extraConfig = '' + 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. + locations."= /api/config" = { + proxyPass = "http://[::1]:3000"; + }; + + # encrypted blobs are immutable and are thus cached for a year + locations."^~ /blob/" = { + root = cryptpad_root; + extraConfig = '' + ${add_headers} + if ($request_method = 'OPTIONS') { + ${add_headers} + 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. + locations."^~ /block/" = { + root = cryptpad_root; + extraConfig = '' + ${add_headers} + add_header Cache-Control max-age=0; + try_files $uri =404; + ''; + }; + + # ugly hack: disable registration + /* + locations."/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. + locations."~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams)$".extraConfig = '' + rewrite ^(.*)$ $1/ redirect; + ''; + + extraConfig = '' + access_log /var/log/nginx/${domain}/${srv}/access.log json; + error_log /var/log/nginx/${domain}/${srv}/error.log warn; + + index index.html; + error_page 404 /customize.dist/404.html; + + # add_header will not set any header if it is emptystring + set $cacheControl ""; + # any static assets loaded with "ver=" in their URL will be cached for a year + if ($args ~ ver=) { + set $cacheControl max-age=31536000; + } + + 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; } + + # script-src specifies valid sources for javascript, including inline handlers + set $scriptSrc "'self' resource: ${main_domain}"; + + # 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}"; + } + + ${add_headers} + + # Finally, serve anything the above exceptions don't govern. + try_files /www/$uri /www/$uri/index.html /customize/$uri; + ''; + }; +}; +} diff --git a/machines/losurdo/nginx/sourcephile.fr/losurdo.nix b/machines/losurdo/nginx/sourcephile.fr/losurdo.nix index 83f9ca3..6e5d3d1 100644 --- a/machines/losurdo/nginx/sourcephile.fr/losurdo.nix +++ b/machines/losurdo/nginx/sourcephile.fr/losurdo.nix @@ -28,8 +28,7 @@ services.tor = { security.gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key" = {}; security.gnupg.secrets."tor/auth/julm" = {}; services.nginx = { - virtualHosts."${srv}" = { - serverName = "${srv}.${domain}"; + virtualHosts."${srv}.${domain}" = { serverAliases = [ domain "${onion}.onion" ]; listen = [ { addr="127.0.0.1"; port = 80; ssl = false; } diff --git a/machines/losurdo/postgresql.nix b/machines/losurdo/postgresql.nix index 99a4ac4..5bfb45e 100644 --- a/machines/losurdo/postgresql.nix +++ b/machines/losurdo/postgresql.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, ... }: +{ pkgs, lib, config, machineName, ... }: let inherit (config) networking; inherit (config.services) postgresql; @@ -58,6 +58,12 @@ services.postgresql = { #user /^(.*)$ \1 ''; }; +services.syncoid.commands = { + "${machineName}/var/postgresql" = { + sendOptions = "raw"; + target = "backup@mermet.${networking.domain}:rpool/backup/${machineName}/var/postgresql"; + }; +}; systemd.services.postgresql = { # DOC: https://wiki.postgresql.org/wiki/Shared_Database_Hosting postStart = '' diff --git a/machines/losurdo/syncoid.nix b/machines/losurdo/syncoid.nix index a28ad05..ed18277 100644 --- a/machines/losurdo/syncoid.nix +++ b/machines/losurdo/syncoid.nix @@ -27,10 +27,6 @@ services.syncoid = { sendOptions = "raw"; target = "${user}@mermet.${networking.domain}:rpool/backup/${machineName}/home/julm/work"; }; - "${machineName}/var/postgresql" = { - sendOptions = "raw"; - target = "${user}@mermet.${networking.domain}:rpool/backup/${machineName}/var/postgresql"; - }; "${user}@mermet.${networking.domain}:rpool/var/mail" = { sendOptions = "raw"; target = "${machineName}/backup/mermet/var/mail"; diff --git a/machines/mermet/knot/sourcephile.fr.nix b/machines/mermet/knot/sourcephile.fr.nix index 82d6dfe..2309940 100644 --- a/machines/mermet/knot/sourcephile.fr.nix +++ b/machines/mermet/knot/sourcephile.fr.nix @@ -102,6 +102,10 @@ services.knot.zones."${domain}" = { stun A ${machines.mermet.extraArgs.ipv4} turn A ${machines.mermet.extraArgs.ipv4} proxy65 A ${machines.losurdo.extraArgs.ipv4} + cryptpad A ${machines.losurdo.extraArgs.ipv4} + cryptpad-api A ${machines.losurdo.extraArgs.ipv4} + cryptpad-files A ${machines.losurdo.extraArgs.ipv4} + cryptpad-sandbox A ${machines.losurdo.extraArgs.ipv4} ; SPF (Sender Policy Framework) @ 3600 IN SPF "v=spf1 mx ip4:${machines.mermet.extraArgs.ipv4} -all" diff --git a/nixos/profiles/services/nginx.nix b/nixos/profiles/services/nginx.nix index 1498645..d14c9f7 100644 --- a/nixos/profiles/services/nginx.nix +++ b/nixos/profiles/services/nginx.nix @@ -56,29 +56,29 @@ services.nginx = { sslProtocols = "TLSv1.3 TLSv1.2"; configs = rec { http_add_headers = '' - # Add HSTS header with preloading to HTTPS requests. - # Adding this header to HTTP requests is discouraged - # DOC: https://blog.qualys.com/securitylabs/2016/03/28/the-importance-of-a-proper-http-strict-transport-security-implementation-on-your-web-server - add_header Strict-Transport-Security $hsts_header; - - # Enable CSP for your services. + # Enable CSP #add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always; + # Enable XSS protection of the browser. + # May be unnecessary when CSP is configured properly (see above) + add_header X-XSS-Protection "1; mode=block"; + # Minimize information leaked to other domains add_header 'Referrer-Policy' 'origin-when-cross-origin'; - # Disable embedding as a frame - add_header X-Frame-Options DENY; + # Restrict embedding as a frame + #add_header X-Frame-Options SAMEORIGIN; # Prevent injection of code in other mime types (XSS Attacks) add_header X-Content-Type-Options nosniff; - - # Enable XSS protection of the browser. - # May be unnecessary when CSP is configured properly (see above) - add_header X-XSS-Protection "1; mode=block"; ''; https_add_headers = '' ${http_add_headers} + # Add HSTS header with preloading to HTTPS requests. + # Adding this header to HTTP requests is discouraged, + # as doing so makes the connection vulnerable to SSL stripping attacks + # DOC: https://blog.qualys.com/securitylabs/2016/03/28/the-importance-of-a-proper-http-strict-transport-security-implementation-on-your-web-server + add_header Strict-Transport-Security $hsts_header; ''; }; commonHttpConfig = '' @@ -115,7 +115,7 @@ services.nginx = { ${nginx.configs.http_add_headers} # This might create errors - proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; + #proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; ''; log = '' access_log /var/log/nginx/access.log main buffer=32k; -- 2.47.2