summaryrefslogtreecommitdiffstats
path: root/nixos/modules/services/networking/tailscale-derper.nix
blob: 3f8c788f7f52289a654fe8d28343a5de7a1f457f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
{
  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.";
      };

      configureNginx = lib.mkOption {
        type = lib.types.bool;
        default = true;
        description = ''
          Whether to enable nginx reverse proxy for derper.
          When enabled, nginx will proxy requests to the derper service.
        '';
      };

      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 when configureNginx is true
      allowedUDPPorts = [ cfg.stunPort ];
    };

    services = {
      nginx = lib.mkIf cfg.configureNginx {
        enable = true;
        virtualHosts."${cfg.domain}" = {
          addSSL = true; # this cannot be forceSSL as derper sends some information over port 80, too.
          locations."/" = {
            proxyPass = "http://127.0.0.1:${toString cfg.port}";
            proxyWebsockets = true;
            extraConfig = # nginx
              ''
                proxy_buffering off;
                proxy_read_timeout 3600s;
              '';
          };
        };
      };

      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" ];
    };
  };
}