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