From 60d7c5d82c231089efe82aa0ddc756141dd95b6e Mon Sep 17 00:00:00 2001 From: a-kenji Date: Wed, 28 Aug 2024 10:54:40 +0200 Subject: [PATCH] add clanModule for zerotier inventory --- clanModules/flake-module.nix | 1 + clanModules/zerotier/README.md | 7 ++ clanModules/zerotier/default.nix | 2 + clanModules/zerotier/roles/controller.nix | 47 +++++++++++ clanModules/zerotier/roles/moon.nix | 17 ++++ clanModules/zerotier/roles/peer.nix | 5 ++ clanModules/zerotier/shared.nix | 95 +++++++++++++++++++++++ docs/mkdocs.yml | 10 ++- pkgs/zerotier-members/zerotier-members.py | 6 +- 9 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 clanModules/zerotier/README.md create mode 100644 clanModules/zerotier/default.nix create mode 100644 clanModules/zerotier/roles/controller.nix create mode 100644 clanModules/zerotier/roles/moon.nix create mode 100644 clanModules/zerotier/roles/peer.nix create mode 100644 clanModules/zerotier/shared.nix diff --git a/clanModules/flake-module.nix b/clanModules/flake-module.nix index ca4149039..dcee6a103 100644 --- a/clanModules/flake-module.nix +++ b/clanModules/flake-module.nix @@ -36,5 +36,6 @@ xfce = ./xfce; zerotier-static-peers = ./zerotier-static-peers; zt-tcp-relay = ./zt-tcp-relay; + zerotier = ./zerotier; }; } diff --git a/clanModules/zerotier/README.md b/clanModules/zerotier/README.md new file mode 100644 index 000000000..40a58b512 --- /dev/null +++ b/clanModules/zerotier/README.md @@ -0,0 +1,7 @@ +--- +description = "Statically configure the `zerotier` peers of a clan network." +features = [ "inventory" ] +--- +Statically configure the `zerotier` peers of a clan network. + +Requires a machine, that is the zerotier controller configured in the network. diff --git a/clanModules/zerotier/default.nix b/clanModules/zerotier/default.nix new file mode 100644 index 000000000..8007b6f11 --- /dev/null +++ b/clanModules/zerotier/default.nix @@ -0,0 +1,2 @@ +# TODO: only kept this file to not break documentation generation. +{ } diff --git a/clanModules/zerotier/roles/controller.nix b/clanModules/zerotier/roles/controller.nix new file mode 100644 index 000000000..27dd01d6d --- /dev/null +++ b/clanModules/zerotier/roles/controller.nix @@ -0,0 +1,47 @@ +{ + config, + lib, + pkgs, + ... +}: +let + instanceNames = builtins.attrNames config.clan.inventory.services.zerotier; + instanceName = builtins.head instanceNames; + zeroTierInstance = config.clan.inventory.services.zerotier.${instanceName}; + roles = zeroTierInstance.roles; + stringSet = list: builtins.attrNames (builtins.groupBy lib.id list); +in +{ + imports = [ + ../shared.nix + ]; + config = { + systemd.services.zerotier-inventory-autoaccept = + let + machines = stringSet (roles.moon.machines ++ roles.controller.machines ++ roles.peer.machines); + networkIps = builtins.foldl' ( + ips: name: + if builtins.pathExists "${config.clan.core.clanDir}/machines/${name}/facts/zerotier-ip" then + ips + ++ [ + (builtins.readFile "${config.clan.core.clanDir}/machines/${name}/facts/zerotier-ip") + ] + else + ips + ) [ ] machines; + allHostIPs = config.clan.zerotier.networkIps ++ networkIps; + in + { + wantedBy = [ "multi-user.target" ]; + after = [ "zerotierone.service" ]; + path = [ config.clan.core.clanPkgs.zerotierone ]; + serviceConfig.ExecStart = pkgs.writeShellScript "zerotier-inventory-autoaccept" '' + ${lib.concatMapStringsSep "\n" (host: '' + ${config.clan.core.clanPkgs.zerotier-members}/bin/zerotier-members allow --member-ip ${host} + '') allHostIPs} + ''; + }; + + clan.core.networking.zerotier.controller.enable = lib.mkDefault true; + }; +} diff --git a/clanModules/zerotier/roles/moon.nix b/clanModules/zerotier/roles/moon.nix new file mode 100644 index 000000000..81f11cd9d --- /dev/null +++ b/clanModules/zerotier/roles/moon.nix @@ -0,0 +1,17 @@ +{ config, lib, ... }: +{ + imports = [ + ../shared.nix + ]; + options.clan.zerotier.moon.stableEndpoints = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = '' + Make this machine a moon. + Other machines can join this moon by adding this moon in their config. + It will be reachable under the given stable endpoints. + ''; + }; + # TODO, we want to remove these options from clanCore + config.clan.core.networking.zerotier.moon.stableEndpoints = + config.clan.zerotier.moon.stableEndpoints; +} diff --git a/clanModules/zerotier/roles/peer.nix b/clanModules/zerotier/roles/peer.nix new file mode 100644 index 000000000..a56780406 --- /dev/null +++ b/clanModules/zerotier/roles/peer.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ../shared.nix + ]; +} diff --git a/clanModules/zerotier/shared.nix b/clanModules/zerotier/shared.nix new file mode 100644 index 000000000..cb4dc3492 --- /dev/null +++ b/clanModules/zerotier/shared.nix @@ -0,0 +1,95 @@ +{ + lib, + config, + pkgs, + ... +}: +let + instanceNames = builtins.attrNames config.clan.inventory.services.zerotier; + instanceName = builtins.head instanceNames; + zeroTierInstance = config.clan.inventory.services.zerotier.${instanceName}; + roles = zeroTierInstance.roles; + controllerMachine = builtins.head roles.controller.machines; + networkIdPath = "${config.clan.core.clanDir}/machines/${controllerMachine}/facts/zerotier-network-id"; + networkId = if builtins.pathExists networkIdPath then builtins.readFile networkIdPath else null; + moons = roles.moon.machines; + moonIps = builtins.foldl' ( + ips: name: + if builtins.pathExists "${config.clan.core.clanDir}/machines/${name}/facts/zerotier-ip" then + ips + ++ [ + (builtins.readFile "${config.clan.core.clanDir}/machines/${name}/facts/zerotier-ip") + ] + else + ips + ) [ ] moons; +in +{ + options.clan.zerotier = { + excludeHosts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ config.clan.core.machineName ]; + description = "Hosts that should be excluded"; + }; + networkIps = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Extra zerotier network Ips that should be accepted"; + }; + networkIds = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Extra zerotier network Ids that should be accepted"; + }; + }; + + config = { + assertions = [ + { + assertion = builtins.length instanceNames == 1; + message = "The zerotier module currently only supports one instance per machine, but found ${builtins.toString instanceNames}"; + } + { + assertion = builtins.length roles.controller.machines == 1; + message = "The zerotier module requires exactly one controller, but found ${builtins.toString roles.controller.machines}"; + } + ]; + + clan.core.networking.zerotier.networkId = networkId; + clan.core.networking.zerotier.name = instanceName; + + # TODO: in future we want to have the node id of our moons in our facts + systemd.services.zerotierone.serviceConfig.ExecStartPost = lib.mkIf (moonIps != [ ]) ( + lib.mkAfter [ + "+${pkgs.writeScript "orbit-moons-by-ip" '' + #!${pkgs.python3.interpreter} + import json + import ipaddress + import subprocess + + def compute_member_id(ipv6_addr: str) -> str: + addr = ipaddress.IPv6Address(ipv6_addr) + addr_bytes = bytearray(addr.packed) + + # Extract the bytes corresponding to the member_id (node_id) + node_id_bytes = addr_bytes[10:16] + node_id = int.from_bytes(node_id_bytes, byteorder="big") + + member_id = format(node_id, "x").zfill(10)[-10:] + + return member_id + def main() -> None: + ips = json.loads(${builtins.toJSON (builtins.toJSON moonIps)}) + for ip in ips: + member_id = compute_member_id(ip) + res = subprocess.run(["zerotier-cli", "orbit", member_id, member_id]) + if res.returncode != 0: + print(f"Failed to add {member_id} to orbit") + if __name__ == "__main__": + main() + ''}" + ] + ); + + }; +} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 94ec72b8a..804258d2d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -66,22 +66,23 @@ nav: - Reference: - Overview: reference/index.md - Clan Modules: - - reference/clanModules/index.md - reference/clanModules/admin.md + - reference/clanModules/borgbackup-static.md - reference/clanModules/borgbackup.md - reference/clanModules/deltachat.md - - reference/clanModules/dyndns.md - reference/clanModules/disk-id.md + - reference/clanModules/dyndns.md - reference/clanModules/ergochat.md - reference/clanModules/garage.md - reference/clanModules/golem-provider.md - reference/clanModules/heisenbridge.md + - reference/clanModules/index.md - reference/clanModules/iwd.md - reference/clanModules/localbackup.md - reference/clanModules/localsend.md - - reference/clanModules/matrix-synapse.md - reference/clanModules/machine-id.md + - reference/clanModules/matrix-synapse.md - reference/clanModules/moonlight.md - reference/clanModules/mumble.md - reference/clanModules/nginx.md @@ -101,9 +102,11 @@ nav: - reference/clanModules/vaultwarden.md - reference/clanModules/xfce.md - reference/clanModules/zerotier-static-peers.md + - reference/clanModules/zerotier.md - reference/clanModules/zt-tcp-relay.md - CLI: - reference/cli/index.md + - reference/cli/backups.md - reference/cli/facts.md - reference/cli/flakes.md @@ -117,6 +120,7 @@ nav: - reference/cli/vms.md - Clan Core: - reference/clan-core/index.md + - reference/clan-core/backups.md - reference/clan-core/facts.md - reference/clan-core/sops.md diff --git a/pkgs/zerotier-members/zerotier-members.py b/pkgs/zerotier-members/zerotier-members.py index a29e1aea9..87a6a12b9 100755 --- a/pkgs/zerotier-members/zerotier-members.py +++ b/pkgs/zerotier-members/zerotier-members.py @@ -65,10 +65,10 @@ def get_network_id() -> str: def allow_member(args: argparse.Namespace) -> None: - member_id = args.member_id if args.member_ip: - member_ip = args.member_id - member_id = compute_member_id(member_ip) + member_id = compute_member_id(args.member_id) + else: + member_id = args.member_id network_id = get_network_id() token = ZEROTIER_STATE_DIR.joinpath("authtoken.secret").read_text() conn = http.client.HTTPConnection("localhost", 9993)