Unverified Commit d82d6897 authored by Sandro Jäckel's avatar Sandro Jäckel
Browse files

nixos/tailscale-derper: init

parent 5789db3b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1230,6 +1230,7 @@
  ./services/networking/syncthing.nix
  ./services/networking/tailscale.nix
  ./services/networking/tailscale-auth.nix
  ./services/networking/tailscale-derper.nix
  ./services/networking/tayga.nix
  ./services/networking/tcpcrypt.nix
  ./services/networking/teamspeak3.nix
+132 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  pkgs,
  ...
}:

let
  cfg = config.services.tailscale.derper;
in
{
  meta.maintainers = with lib.maintainers; [ SuperSandro2000 ];

  options = {
    services.tailscale.derper = {
      enable = lib.mkEnableOption "Tailscale Derper. See upstream doc <https://tailscale.com/kb/1118/custom-derp-servers> how to configure it on clients";

      domain = lib.mkOption {
        type = lib.types.str;
        description = "Domain name under which the derper server is reachable.";
      };

      openFirewall = lib.mkOption {
        type = lib.types.bool;
        default = true;
        description = ''
          Whether to open the firewall for the specified port.
          Derper requires the used ports to be opened, otherwise it doesn't work as expected.
        '';
      };

      package = lib.mkPackageOption pkgs [
        "tailscale"
        "derper"
      ] { };

      stunPort = lib.mkOption {
        type = lib.types.port;
        default = 3478;
        description = ''
          STUN port to listen on.
          See online docs <https://tailscale.com/kb/1118/custom-derp-servers#prerequisites> on how to configure a different external port.
        '';
      };

      port = lib.mkOption {
        type = lib.types.port;
        default = 8010;
        description = "The port the derper process will listen on. This is not the port tailscale will connect to.";
      };

      verifyClients = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Whether to verify clients against a locally running tailscale daemon if they are allowed to connect to this node or not.
        '';
      };
    };
  };

  config = lib.mkIf cfg.enable {
    networking.firewall = lib.mkIf cfg.openFirewall {
      # port 80 and 443 are opened by nginx already
      allowedUDPPorts = [ cfg.stunPort ];
    };

    services = {
      nginx = {
        enable = true;
        upstreams.tailscale-derper = {
          servers."127.0.0.1:${toString cfg.port}" = { };
          extraConfig = ''
            keepalive 64;
          '';
        };
        virtualHosts."${cfg.domain}" = {
          addSSL = true; # this cannot be forceSSL as derper sends some information over port 80, too.
          locations."/" = {
            proxyPass = "http://tailscale-derper";
            proxyWebsockets = true;
            extraConfig = ''
              keepalive_timeout 0;
              proxy_buffering off;
            '';
          };
        };
      };

      tailscale.enable = lib.mkIf cfg.verifyClients true;
    };

    systemd.services.tailscale-derper = {
      serviceConfig = {
        ExecStart =
          "${lib.getExe' cfg.package "derper"} -a :${toString cfg.port} -c /var/lib/derper/derper.key -hostname=${cfg.domain} -stun-port ${toString cfg.stunPort}"
          + lib.optionalString cfg.verifyClients " -verify-clients";
        DynamicUser = true;
        Restart = "always";
        RestartSec = "5sec"; # don't crash loop immediately
        StateDirectory = "derper";
        Type = "simple";

        CapabilityBoundingSet = [ "" ];
        DeviceAllow = null;
        LockPersonality = true;
        NoNewPrivileges = true;
        MemoryDenyWriteExecute = true;
        PrivateDevices = true;
        PrivateUsers = true;
        ProcSubset = "pid";
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        RestrictAddressFamilies = [
          "AF_INET"
          "AF_INET6"
          "AF_UNIX"
        ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        SystemCallArchitectures = "native";
        SystemCallFilter = [ "@system-service" ];
      };
      wantedBy = [ "multi-user.target" ];
    };
  };
}