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