From 76adaa1e208a54be38d5f43d9c2de5df37255e29 Mon Sep 17 00:00:00 2001
From: Jens Nolte <git@queezle.net>
Date: Wed, 17 Nov 2021 21:26:19 +0100
Subject: [PATCH] Add coturn server to matrix-homeserver

---
 modules/matrix-homeserver/coturn.nix  | 170 ++++++++++++++++++++++++++
 modules/matrix-homeserver/default.nix |  34 ++++++
 2 files changed, 204 insertions(+)
 create mode 100644 modules/matrix-homeserver/coturn.nix

diff --git a/modules/matrix-homeserver/coturn.nix b/modules/matrix-homeserver/coturn.nix
new file mode 100644
index 0000000..d65e8e2
--- /dev/null
+++ b/modules/matrix-homeserver/coturn.nix
@@ -0,0 +1,170 @@
+{ config, pkgs, lib, ... }:
+with lib;
+
+let
+  cfg = config.queezle.matrix-homeserver;
+  staticAuthSecretPath = cfg.coturn.authSecretPath;
+  synapseConfigPath = "/var/lib/matrix-synapse/coturn-secret.yaml";
+  configFile = pkgs.writeText "coturn.config" ''
+    # static-auth-secret is appended to config when service is started
+    use-auth-secret
+    realm=${cfg.turnRealm}
+
+    # Log to syslog
+    no-stdout-log
+    syslog
+    verbose
+
+    pidfile /run/coturn/turnserver.pid
+
+    # Hide version
+    no-software-attribute
+
+    # Only allow encrypted client connections
+    no-udp
+    no-tcp
+
+    no-cli
+    no-tcp-relay
+
+    # Modern crypto / prevent TLS downgrade to 1.0
+    no-tlsv1
+    no-tlsv1_1
+
+    secure-stun
+
+    no-multicast-peers
+    denied-peer-ip=0.0.0.0-0.255.255.255
+    denied-peer-ip=10.0.0.0-10.255.255.255
+    denied-peer-ip=100.64.0.0-100.127.255.255
+    denied-peer-ip=169.254.0.0-169.254.255.255
+    denied-peer-ip=172.16.0.0-172.31.255.255
+    denied-peer-ip=192.0.0.0-192.0.0.255
+    denied-peer-ip=192.0.2.0-192.0.2.255
+    denied-peer-ip=192.88.99.0-192.88.99.255
+    denied-peer-ip=192.168.0.0-192.168.255.255
+    denied-peer-ip=198.18.0.0-198.19.255.255
+    denied-peer-ip=198.51.100.0-198.51.100.255
+    denied-peer-ip=203.0.113.0-203.0.113.255
+    denied-peer-ip=240.0.0.0-255.255.255.255
+    denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
+    denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
+    denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
+    denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
+    denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+    denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+    denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+  '';
+in {
+  config = mkIf (cfg.enable && cfg.coturn.enable) {
+    assertions = [
+      {
+        assertion = !config.services.coturn.enable;
+        message = "Cannot use services.coturn.enable and queezle.matrix-homeserver.coturn.enable at the same time.";
+      }
+    ];
+
+    queezle.matrix-homeserver = {
+      extraConfigFiles."turn-secret" = "/var/lib/matrix-homeserver/turn-secret.yaml";
+      settings = {
+        turn_uris = [
+          "turns:${cfg.turnRealm}?transport=udp"
+          "turns:${cfg.turnRealm}?transport=tcp"
+        ];
+        # One day token lifetime
+        turn_user_lifetime = 86400000;
+      };
+    };
+
+    networking.firewall = {
+      # Default TLS TURN listener
+      allowedTCPPorts = [ 5349 5350 ];
+      # Default DTLS TURN listener
+      allowedUDPPorts = [ 5349 5350 ];
+      # Default UDP TURN relay port range
+      allowedUDPPortRanges = [
+        {
+          from = 49152;
+          to = 65535;
+        }
+      ];
+    };
+
+
+    systemd.services.coturn-generate = {
+      description = "generate shared secret for coturn and synapse";
+      before = [ "matrix-synapse.service" ];
+      wantedBy = [ "matrix-synapse.service" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = pkgs.writeScript "coturn-generate" ''
+          #!${pkgs.zsh}/bin/zsh
+          set -eu
+
+          umask u=rwx,go=
+
+          if [[ ! -e ${staticAuthSecretPath} ]] {
+            ${pkgs.pwgen}/bin/pwgen -s 64 > '${staticAuthSecretPath}'
+          }
+
+          > /var/lib/matrix-homeserver/turn-secret.yaml <<- EOF
+            turn_shared_secret: "$(< ${staticAuthSecretPath})"
+          EOF
+        '';
+
+        StateDirectory = "matrix-homeserver";
+      };
+    };
+
+    systemd.services.coturn = {
+      description = "coturn STUN/TURN server for matrix homeserver";
+
+      after = [ "network-online.target" ];
+      requires = [ "coturn-generate.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "exec";
+        ExecStart =
+          let command = pkgs.writeScriptBin "turnserver" ''
+            #!${pkgs.zsh}/bin/zsh
+            set -eu
+
+            readonly config_file=$RUNTIME_DIRECTORY/turnserver.conf
+
+            < "${configFile}" > $config_file
+            echo "static-auth-secret $(< $CREDENTIALS_DIRECTORY/staticAuthSecret)" >> $config_file
+
+            exec ${cfg.coturn.package}/bin/turnserver \
+              -c $config_file \
+              --cert $CREDENTIALS_DIRECTORY/cert \
+              --pkey $CREDENTIALS_DIRECTORY/pkey
+          '';
+          in "${command}/bin/turnserver";
+        RuntimeDirectory = "coturn";
+
+        LoadCredential = [
+          "staticAuthSecret:${staticAuthSecretPath}"
+          "cert:${config.security.acme.certs.${cfg.coturn.useACMEHost}.directory}/cert.pem"
+          "pkey:${config.security.acme.certs.${cfg.coturn.useACMEHost}.directory}/key.pem"
+        ];
+
+        # TODO
+        #Restart = "on-failure";
+
+        DynamicUser = true;
+        User = "coturn";
+        Group = "coturn";
+
+        ProtectHome = true;
+        ProtectProc = "invisible";
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        RestrictRealtime = true;
+        PrivateDevices = true;
+      };
+    };
+  };
+}
diff --git a/modules/matrix-homeserver/default.nix b/modules/matrix-homeserver/default.nix
index 0b66dcf..ee67cc5 100644
--- a/modules/matrix-homeserver/default.nix
+++ b/modules/matrix-homeserver/default.nix
@@ -11,6 +11,7 @@ in {
     ./reverse-proxy.nix
     ./element.nix
     ./well-known.nix
+    ./coturn.nix
     ./heisenbridge.nix
   ];
 
@@ -41,6 +42,11 @@ in {
       default = "element.${cfg.serverName}";
     };
 
+    turnRealm = mkOption {
+      type = types.str;
+      default = "turn.${cfg.serverName}";
+    };
+
     useACMEHost = mkOption {
       type = types.str;
       default = null;
@@ -126,6 +132,34 @@ in {
       };
     };
 
+    # Configure a TURN server to run on the same host as synapse.
+    coturn = {
+      enable = mkEnableOption "matrix-homeserver TURN server";
+
+      useACMEHost = mkOption {
+        type = types.str;
+        default = cfg.turnRealm;
+      };
+
+      authSecretPath = mkOption {
+        type = types.path;
+        default = "/var/lib/matrix-homeserver/coturn-static-auth-secret";
+        description = ''
+          File path where the coturn static-auth-secret is stored. The secret will be automatically created.
+          Ensure the diretory exists and is not publicly readable when changing the path.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.coturn;
+        defaultText = literalExpression "pkgs.coturn";
+        description = ''
+          Overridable attribute of the coturn package to use.
+        '';
+      };
+    };
+
     # Heisenbridge IRC bouncer. Has to run un the same host as synapse.
     heisenbridge = {
       enable = mkEnableOption "heisenbridge";
-- 
GitLab