at 23.11-pre 9.4 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 = lib.mdDoc "Authorized password to the opposite end of the tunnel."; 17 }; 18 login = mkOption { 19 default = ""; 20 type = types.str; 21 description = lib.mdDoc "(optional) name your peer has for you"; 22 }; 23 peerName = mkOption { 24 default = ""; 25 type = types.str; 26 description = lib.mdDoc "(optional) human-readable name for peer"; 27 }; 28 publicKey = mkOption { 29 type = types.str; 30 description = lib.mdDoc "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 = lib.mdDoc "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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 lib.mdDoc '' 175 Bind to this device for native ethernet operation. 176 `all` is a pseudo-name which will try to connect to all devices. 177 ''; 178 }; 179 180 beacon = mkOption { 181 type = types.int; 182 default = 2; 183 description = lib.mdDoc '' 184 Auto-connect to other cjdns nodes on the same network. 185 Options: 186 0: Disabled. 187 1: Accept beacons, this will cause cjdns to accept incoming 188 beacon messages and try connecting to the sender. 189 2: Accept and send beacons, this will cause cjdns to broadcast 190 messages on the local network which contain a randomly 191 generated per-session password, other nodes which have this 192 set to 1 or 2 will hear the beacon messages and connect 193 automatically. 194 ''; 195 }; 196 197 connectTo = mkOption { 198 type = types.attrsOf ( types.submodule ( connectToSubmodule ) ); 199 default = { }; 200 example = literalExpression '' 201 { 202 "01:02:03:04:05:06" = { 203 hostname = "homer.hype"; 204 password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; 205 publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; 206 }; 207 } 208 ''; 209 description = lib.mdDoc '' 210 Credentials for connecting look similar to UDP credientials 211 except they begin with the mac address. 212 ''; 213 }; 214 }; 215 216 addExtraHosts = mkOption { 217 type = types.bool; 218 default = false; 219 description = lib.mdDoc '' 220 Whether to add cjdns peers with an associated hostname to 221 {file}`/etc/hosts`. Beware that enabling this 222 incurs heavy eval-time costs. 223 ''; 224 }; 225 226 }; 227 228 }; 229 230 config = mkIf cfg.enable { 231 232 boot.kernelModules = [ "tun" ]; 233 234 # networking.firewall.allowedUDPPorts = ... 235 236 systemd.services.cjdns = { 237 description = "cjdns: routing engine designed for security, scalability, speed and ease of use"; 238 wantedBy = [ "multi-user.target" "sleep.target"]; 239 after = [ "network-online.target" ]; 240 bindsTo = [ "network-online.target" ]; 241 242 preStart = if cfg.confFile != null then "" else '' 243 [ -e /etc/cjdns.keys ] && source /etc/cjdns.keys 244 245 if [ -z "$CJDNS_PRIVATE_KEY" ]; then 246 shopt -s lastpipe 247 ${pkg}/bin/makekeys | { read private ipv6 public; } 248 249 umask 0077 250 echo "CJDNS_PRIVATE_KEY=$private" >> /etc/cjdns.keys 251 echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public" > /etc/cjdns.public 252 253 chmod 600 /etc/cjdns.keys 254 chmod 444 /etc/cjdns.public 255 fi 256 257 if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then 258 echo "CJDNS_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" \ 259 >> /etc/cjdns.keys 260 fi 261 ''; 262 263 script = ( 264 if cfg.confFile != null then "${pkg}/bin/cjdroute < ${cfg.confFile}" else 265 '' 266 source /etc/cjdns.keys 267 (cat <<'EOF' 268 ${cjdrouteConf} 269 EOF 270 ) | sed \ 271 -e "s/@CJDNS_ADMIN_PASSWORD@/$CJDNS_ADMIN_PASSWORD/g" \ 272 -e "s/@CJDNS_PRIVATE_KEY@/$CJDNS_PRIVATE_KEY/g" \ 273 | ${pkg}/bin/cjdroute 274 '' 275 ); 276 277 startLimitIntervalSec = 0; 278 serviceConfig = { 279 Type = "forking"; 280 Restart = "always"; 281 RestartSec = 1; 282 CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID"; 283 ProtectSystem = true; 284 # Doesn't work on i686, causing service to fail 285 MemoryDenyWriteExecute = !pkgs.stdenv.isi686; 286 ProtectHome = true; 287 PrivateTmp = true; 288 }; 289 }; 290 291 networking.hostFiles = mkIf cfg.addExtraHosts [ cjdnsExtraHosts ]; 292 293 assertions = [ 294 { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile != null ); 295 message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined."; 296 } 297 { assertion = config.networking.enableIPv6; 298 message = "networking.enableIPv6 must be enabled for CJDNS to work"; 299 } 300 ]; 301 302 }; 303 304}