at 23.05-pre 7.0 kB view raw
1{ config, lib, pkgs, ... }: 2with lib; 3let 4 keysPath = "/var/lib/yggdrasil/keys.json"; 5 6 cfg = config.services.yggdrasil; 7 settingsProvided = cfg.settings != { }; 8 configFileProvided = cfg.configFile != null; 9 10 format = pkgs.formats.json { }; 11in { 12 imports = [ 13 (mkRenamedOptionModule 14 [ "services" "yggdrasil" "config" ] 15 [ "services" "yggdrasil" "settings" ]) 16 ]; 17 18 options = with types; { 19 services.yggdrasil = { 20 enable = mkEnableOption (lib.mdDoc "the yggdrasil system service"); 21 22 settings = mkOption { 23 type = format.type; 24 default = {}; 25 example = { 26 Peers = [ 27 "tcp://aa.bb.cc.dd:eeeee" 28 "tcp://[aaaa:bbbb:cccc:dddd::eeee]:fffff" 29 ]; 30 Listen = [ 31 "tcp://0.0.0.0:xxxxx" 32 ]; 33 }; 34 description = lib.mdDoc '' 35 Configuration for yggdrasil, as a Nix attribute set. 36 37 Warning: this is stored in the WORLD-READABLE Nix store! 38 Therefore, it is not appropriate for private keys. If you 39 wish to specify the keys, use {option}`configFile`. 40 41 If the {option}`persistentKeys` is enabled then the 42 keys that are generated during activation will override 43 those in {option}`settings` or 44 {option}`configFile`. 45 46 If no keys are specified then ephemeral keys are generated 47 and the Yggdrasil interface will have a random IPv6 address 48 each time the service is started, this is the default. 49 50 If both {option}`configFile` and {option}`settings` 51 are supplied, they will be combined, with values from 52 {option}`configFile` taking precedence. 53 54 You can use the command `nix-shell -p yggdrasil --run "yggdrasil -genconf"` 55 to generate default configuration values with documentation. 56 ''; 57 }; 58 59 configFile = mkOption { 60 type = nullOr path; 61 default = null; 62 example = "/run/keys/yggdrasil.conf"; 63 description = lib.mdDoc '' 64 A file which contains JSON configuration for yggdrasil. 65 See the {option}`settings` option for more information. 66 ''; 67 }; 68 69 group = mkOption { 70 type = types.nullOr types.str; 71 default = null; 72 example = "wheel"; 73 description = lib.mdDoc "Group to grant access to the Yggdrasil control socket. If `null`, only root can access the socket."; 74 }; 75 76 openMulticastPort = mkOption { 77 type = bool; 78 default = false; 79 description = lib.mdDoc '' 80 Whether to open the UDP port used for multicast peer 81 discovery. The NixOS firewall blocks link-local 82 communication, so in order to make local peering work you 83 will also need to set `LinkLocalTCPPort` in your 84 yggdrasil configuration ({option}`settings` or 85 {option}`configFile`) to a port number other than 0, 86 and then add that port to 87 {option}`networking.firewall.allowedTCPPorts`. 88 ''; 89 }; 90 91 denyDhcpcdInterfaces = mkOption { 92 type = listOf str; 93 default = []; 94 example = [ "tap*" ]; 95 description = lib.mdDoc '' 96 Disable the DHCP client for any interface whose name matches 97 any of the shell glob patterns in this list. Use this 98 option to prevent the DHCP client from broadcasting requests 99 on the yggdrasil network. It is only necessary to do so 100 when yggdrasil is running in TAP mode, because TUN 101 interfaces do not support broadcasting. 102 ''; 103 }; 104 105 package = mkOption { 106 type = package; 107 default = pkgs.yggdrasil; 108 defaultText = literalExpression "pkgs.yggdrasil"; 109 description = lib.mdDoc "Yggdrasil package to use."; 110 }; 111 112 persistentKeys = mkEnableOption (lib.mdDoc '' 113 If enabled then keys will be generated once and Yggdrasil 114 will retain the same IPv6 address when the service is 115 restarted. Keys are stored at ${keysPath}. 116 ''); 117 118 }; 119 }; 120 121 config = mkIf cfg.enable (let binYggdrasil = cfg.package + "/bin/yggdrasil"; 122 in { 123 assertions = [{ 124 assertion = config.networking.enableIPv6; 125 message = "networking.enableIPv6 must be true for yggdrasil to work"; 126 }]; 127 128 system.activationScripts.yggdrasil = mkIf cfg.persistentKeys '' 129 if [ ! -e ${keysPath} ] 130 then 131 mkdir --mode=700 -p ${builtins.dirOf keysPath} 132 ${binYggdrasil} -genconf -json \ 133 | ${pkgs.jq}/bin/jq \ 134 'to_entries|map(select(.key|endswith("Key")))|from_entries' \ 135 > ${keysPath} 136 fi 137 ''; 138 139 systemd.services.yggdrasil = { 140 description = "Yggdrasil Network Service"; 141 after = [ "network-pre.target" ]; 142 wants = [ "network.target" ]; 143 before = [ "network.target" ]; 144 wantedBy = [ "multi-user.target" ]; 145 146 preStart = 147 (if settingsProvided || configFileProvided || cfg.persistentKeys then 148 "echo " 149 150 + (lib.optionalString settingsProvided 151 "'${builtins.toJSON cfg.settings}'") 152 + (lib.optionalString configFileProvided "$(cat ${cfg.configFile})") 153 + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})") 154 + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf" 155 else 156 "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"; 157 158 serviceConfig = { 159 ExecStart = 160 "${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf"; 161 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 162 Restart = "always"; 163 164 DynamicUser = true; 165 StateDirectory = "yggdrasil"; 166 RuntimeDirectory = "yggdrasil"; 167 RuntimeDirectoryMode = "0750"; 168 BindReadOnlyPaths = lib.optional configFileProvided cfg.configFile 169 ++ lib.optional cfg.persistentKeys keysPath; 170 ReadWritePaths = "/run/yggdrasil"; 171 172 AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"; 173 CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"; 174 MemoryDenyWriteExecute = true; 175 ProtectControlGroups = true; 176 ProtectHome = "tmpfs"; 177 ProtectKernelModules = true; 178 ProtectKernelTunables = true; 179 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; 180 RestrictNamespaces = true; 181 RestrictRealtime = true; 182 SystemCallArchitectures = "native"; 183 SystemCallFilter = [ "@system-service" "~@privileged @keyring" ]; 184 } // (if (cfg.group != null) then { 185 Group = cfg.group; 186 } else {}); 187 }; 188 189 networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces; 190 networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ]; 191 192 # Make yggdrasilctl available on the command line. 193 environment.systemPackages = [ cfg.package ]; 194 }); 195 meta = { 196 doc = ./yggdrasil.xml; 197 maintainers = with lib.maintainers; [ gazally ehmry ]; 198 }; 199}