at 24.11-pre 9.1 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 pkg = pkgs.cjdns; 8 9 cfg = config.services.cjdns; 10 11 connectToSubmodule = 12 { ... }: 13 { options = 14 { password = mkOption { 15 type = types.str; 16 description = "Authorized password to the opposite end of the tunnel."; 17 }; 18 login = mkOption { 19 default = ""; 20 type = types.str; 21 description = "(optional) name your peer has for you"; 22 }; 23 peerName = mkOption { 24 default = ""; 25 type = types.str; 26 description = "(optional) human-readable name for peer"; 27 }; 28 publicKey = mkOption { 29 type = types.str; 30 description = "Public key at the opposite end of the tunnel."; 31 }; 32 hostname = mkOption { 33 default = ""; 34 example = "foobar.hype"; 35 type = types.str; 36 description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures."; 37 }; 38 }; 39 }; 40 41 # Additional /etc/hosts entries for peers with an associated hostname 42 cjdnsExtraHosts = pkgs.runCommand "cjdns-hosts" {} '' 43 exec >$out 44 ${concatStringsSep "\n" (mapAttrsToList (k: v: 45 optionalString (v.hostname != "") 46 "echo $(${pkgs.cjdns}/bin/publictoip6 ${v.publicKey}) ${v.hostname}") 47 (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo))} 48 ''; 49 50 parseModules = x: 51 x // { connectTo = mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo; }; 52 53 cjdrouteConf = builtins.toJSON ( recursiveUpdate { 54 admin = { 55 bind = cfg.admin.bind; 56 password = "@CJDNS_ADMIN_PASSWORD@"; 57 }; 58 authorizedPasswords = map (p: { password = p; }) cfg.authorizedPasswords; 59 interfaces = { 60 ETHInterface = if (cfg.ETHInterface.bind != "") then [ (parseModules cfg.ETHInterface) ] else [ ]; 61 UDPInterface = if (cfg.UDPInterface.bind != "") then [ (parseModules cfg.UDPInterface) ] else [ ]; 62 }; 63 64 privateKey = "@CJDNS_PRIVATE_KEY@"; 65 66 resetAfterInactivitySeconds = 100; 67 68 router = { 69 interface = { type = "TUNInterface"; }; 70 ipTunnel = { 71 allowedConnections = []; 72 outgoingConnections = []; 73 }; 74 }; 75 76 security = [ { exemptAngel = 1; setuser = "nobody"; } ]; 77 78 } cfg.extraConfig); 79 80in 81 82{ 83 options = { 84 85 services.cjdns = { 86 87 enable = mkOption { 88 type = types.bool; 89 default = false; 90 description = '' 91 Whether to enable the cjdns network encryption 92 and routing engine. A file at /etc/cjdns.keys will 93 be created if it does not exist to contain a random 94 secret key that your IPv6 address will be derived from. 95 ''; 96 }; 97 98 extraConfig = mkOption { 99 type = types.attrs; 100 default = {}; 101 example = { router.interface.tunDevice = "tun10"; }; 102 description = '' 103 Extra configuration, given as attrs, that will be merged recursively 104 with the rest of the JSON generated by this module, at the root node. 105 ''; 106 }; 107 108 confFile = mkOption { 109 type = types.nullOr types.path; 110 default = null; 111 example = "/etc/cjdroute.conf"; 112 description = '' 113 Ignore all other cjdns options and load configuration from this file. 114 ''; 115 }; 116 117 authorizedPasswords = mkOption { 118 type = types.listOf types.str; 119 default = [ ]; 120 example = [ 121 "snyrfgkqsc98qh1y4s5hbu0j57xw5s0" 122 "z9md3t4p45mfrjzdjurxn4wuj0d8swv" 123 "49275fut6tmzu354pq70sr5b95qq0vj" 124 ]; 125 description = '' 126 Any remote cjdns nodes that offer these passwords on 127 connection will be allowed to route through this node. 128 ''; 129 }; 130 131 admin = { 132 bind = mkOption { 133 type = types.str; 134 default = "127.0.0.1:11234"; 135 description = '' 136 Bind the administration port to this address and port. 137 ''; 138 }; 139 }; 140 141 UDPInterface = { 142 bind = mkOption { 143 type = types.str; 144 default = ""; 145 example = "192.168.1.32:43211"; 146 description = '' 147 Address and port to bind UDP tunnels to. 148 ''; 149 }; 150 connectTo = mkOption { 151 type = types.attrsOf ( types.submodule ( connectToSubmodule ) ); 152 default = { }; 153 example = literalExpression '' 154 { 155 "192.168.1.1:27313" = { 156 hostname = "homer.hype"; 157 password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; 158 publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; 159 }; 160 } 161 ''; 162 description = '' 163 Credentials for making UDP tunnels. 164 ''; 165 }; 166 }; 167 168 ETHInterface = { 169 bind = mkOption { 170 type = types.str; 171 default = ""; 172 example = "eth0"; 173 description = '' 174 Bind to this device for native ethernet operation. 175 `all` is a pseudo-name which will try to connect to all devices. 176 ''; 177 }; 178 179 beacon = mkOption { 180 type = types.int; 181 default = 2; 182 description = '' 183 Auto-connect to other cjdns nodes on the same network. 184 Options: 185 0: Disabled. 186 1: Accept beacons, this will cause cjdns to accept incoming 187 beacon messages and try connecting to the sender. 188 2: Accept and send beacons, this will cause cjdns to broadcast 189 messages on the local network which contain a randomly 190 generated per-session password, other nodes which have this 191 set to 1 or 2 will hear the beacon messages and connect 192 automatically. 193 ''; 194 }; 195 196 connectTo = mkOption { 197 type = types.attrsOf ( types.submodule ( connectToSubmodule ) ); 198 default = { }; 199 example = literalExpression '' 200 { 201 "01:02:03:04:05:06" = { 202 hostname = "homer.hype"; 203 password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; 204 publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; 205 }; 206 } 207 ''; 208 description = '' 209 Credentials for connecting look similar to UDP credientials 210 except they begin with the mac address. 211 ''; 212 }; 213 }; 214 215 addExtraHosts = mkOption { 216 type = types.bool; 217 default = false; 218 description = '' 219 Whether to add cjdns peers with an associated hostname to 220 {file}`/etc/hosts`. Beware that enabling this 221 incurs heavy eval-time costs. 222 ''; 223 }; 224 225 }; 226 227 }; 228 229 config = mkIf cfg.enable { 230 231 boot.kernelModules = [ "tun" ]; 232 233 # networking.firewall.allowedUDPPorts = ... 234 235 systemd.services.cjdns = { 236 description = "cjdns: routing engine designed for security, scalability, speed and ease of use"; 237 wantedBy = [ "multi-user.target" "sleep.target"]; 238 after = [ "network-online.target" ]; 239 bindsTo = [ "network-online.target" ]; 240 241 preStart = optionalString (cfg.confFile == null) '' 242 [ -e /etc/cjdns.keys ] && source /etc/cjdns.keys 243 244 if [ -z "$CJDNS_PRIVATE_KEY" ]; then 245 shopt -s lastpipe 246 ${pkg}/bin/makekeys | { read private ipv6 public; } 247 248 install -m 600 <(echo "CJDNS_PRIVATE_KEY=$private") /etc/cjdns.keys 249 install -m 444 <(echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public") /etc/cjdns.public 250 fi 251 252 if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then 253 echo "CJDNS_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" \ 254 >> /etc/cjdns.keys 255 fi 256 ''; 257 258 script = ( 259 if cfg.confFile != null then "${pkg}/bin/cjdroute < ${cfg.confFile}" else 260 '' 261 source /etc/cjdns.keys 262 (cat <<'EOF' 263 ${cjdrouteConf} 264 EOF 265 ) | sed \ 266 -e "s/@CJDNS_ADMIN_PASSWORD@/$CJDNS_ADMIN_PASSWORD/g" \ 267 -e "s/@CJDNS_PRIVATE_KEY@/$CJDNS_PRIVATE_KEY/g" \ 268 | ${pkg}/bin/cjdroute 269 '' 270 ); 271 272 startLimitIntervalSec = 0; 273 serviceConfig = { 274 Type = "forking"; 275 Restart = "always"; 276 RestartSec = 1; 277 CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID"; 278 ProtectSystem = true; 279 # Doesn't work on i686, causing service to fail 280 MemoryDenyWriteExecute = !pkgs.stdenv.isi686; 281 ProtectHome = true; 282 PrivateTmp = true; 283 }; 284 }; 285 286 networking.hostFiles = mkIf cfg.addExtraHosts [ cjdnsExtraHosts ]; 287 288 assertions = [ 289 { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile != null ); 290 message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined."; 291 } 292 { assertion = config.networking.enableIPv6; 293 message = "networking.enableIPv6 must be enabled for CJDNS to work"; 294 } 295 ]; 296 297 }; 298 299}