aboutsummaryrefslogtreecommitdiff
path: root/nix/wesher_service.nix
blob: d269a2f246128c33939877f2eaa142feb2e1e3f8 (plain) (blame)
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
137
{ config, lib, pkgs, ... }:
with lib;
let
  keysPath = "/var/lib/wesher/secrets";
  cfg = config.services.wesher;
in {
  options = with types; {
    services.wesher = {
      enable = mkEnableOption "wesher wireguard overlay mesh network manager";

      package = mkOption {
        type = package;
        default = pkgs.wesher;
        defaultText = literalExpression "pkgs.wesher";
        description = "Wesher package to use.";
      };

      clusterKey = mkOption {
        type = nullOr str;
        default = null;
        description = "shared key for cluster membership to use on first initialization, if no key was previously used by Wesher. Must be 32 bytes base64 encoded; will be generated if not provided. Setting this parameter value will not overwrite an existing cluster key; to do so please delete ${keysPath}";
      };

      bindAddr = mkOption {
        type = nullOr str;
        default = null;
        description = "IP address to bind to for cluster membership (cannot be used with --bind-iface)";
      };

      bindIface = mkOption {
        type = nullOr str;
        default = null;
        description = "Interface to bind to for cluster membership (cannot be used with --bind-addr)";
      };

      join = mkOption {
        type = listOf str;
        default = [];
        description = "list of hostnames or IP addresses to existing cluster members; if not provided, will attempt resuming any known state or otherwise wait for further members";
      };

      clusterPort = mkOption {
        type = port;
        default = 7946;
        description = "port used for membership gossip traffic (both TCP and UDP); must be the same accross cluster";
      };

      wireguardPort = mkOption {
        type = port;
        default = 51820;
        description = "port used for wireguard traffic (UDP); must be the same accross cluster";
      };

      overlayNet = mkOption {
        type = str;
        default = "10.0.0.0/8";
        description = "the network in which to allocate addresses for the overlay mesh network (CIDR format); smaller networks increase the chance of IP collision";
      };

      interface = mkOption {
        type = str;
        default = "wgoverlay";
        description = "name of the wireguard interface to create and manage";
      };

      logLevel = mkOption {
        type = str;
        default = "warn";
        description = "set the verbosity (one of debug/info/warn/error)";
      };
      
    };
  };

  config = mkIf cfg.enable (let binWesher = cfg.package + "/bin/wesher";
  in {
    system.activationScripts.wesher = if (cfg.clusterKey != null) then ''
      if [ ! -e ${keysPath} ]
      then
        mkdir --mode=700 -p ${builtins.dirOf keysPath}
        echo "WESHER_CLUSTER_KEY=${cfg.clusterKey}" > ${keysPath}
      fi
    '' else ''
      if [ ! -e ${keysPath} ]
      then
        mkdir --mode=700 -p ${builtins.dirOf keysPath}
        echo "WESHER_CLUSTER_KEY=$(head -c 32 /dev/urandom | base64)" > ${keysPath}
      fi
    '';

    systemd.services.wesher = {
      description = "wesher wireguard overlay mesh network manager";
      bindsTo = [ "network-online.target" ];
      after = [ "network-online.target" ];
      wantedBy = [ "multi-user.target" ];

      environment = {
        WESHER_JOIN = builtins.concatStringsSep "," cfg.join;
        WESHER_CLUSTER_PORT = builtins.toString cfg.clusterPort;
        WESHER_WIREGUARD_PORT = builtins.toString cfg.wireguardPort;
        WESHER_OVERLAY_NET = cfg.overlayNet;
        WESHER_INTERFACE = cfg.interface;
        WESHER_LOG_LEVEL = cfg.logLevel;
        WESHER_NO_ETC_HOSTS = "true";
      } 
      // (if (cfg.bindAddr != null) then { WESHER_BIND_ADDR = cfg.bindAddr; } else {})
      // (if (cfg.bindIface != null) then { WESHER_BIND_IFACE = cfg.bindIface; } else {})
      ;

      serviceConfig = {
        ExecStart = "${binWesher}";
        Restart = "always";

        EnvironmentFile = keysPath;

        User = "wesher";
        DynamicUser = true;
        StateDirectory = "wesher";

        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
        MemoryDenyWriteExecute = true;
        ProtectControlGroups = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
        RestrictNamespaces = true;
        RestrictRealtime = true;
        SystemCallArchitectures = "native";
        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @resources";
      };
    };

    networking.firewall.allowedUDPPorts = mkIf cfg.enable [ cfg.clusterPort cfg.wireguardPort ];
    networking.firewall.allowedTCPPorts = mkIf cfg.enable [ cfg.clusterPort ];
  });
}