diff --git a/nixos/modules/services/networking/cjdns.nix b/nixos/modules/services/networking/cjdns.nix index 9306ffd5a18a..0519172db914 100644 --- a/nixos/modules/services/networking/cjdns.nix +++ b/nixos/modules/services/networking/cjdns.nix @@ -1,13 +1,3 @@ -# You may notice the commented out sections in this file, -# it would be great to configure cjdns from nix, but cjdns -# reads its configuration from stdin, including the private -# key and admin password, all nested in a JSON structure. -# -# Until a good method of storing the keys outside the nix -# store and mixing them back into a string is devised -# (without too much shell hackery), a skeleton of the -# configuration building lies commented out. - { config, lib, pkgs, ... }: with lib; @@ -16,41 +6,35 @@ let cfg = config.services.cjdns; - /* - # can't keep keys and passwords in the nix store, - # but don't want to deal with this stdin quagmire. + # would be nice to merge 'cfg' with a //, + # but the json nesting is wacky. + cjdrouteConf = builtins.toJSON ( { + admin = { + bind = cfg.admin.bind; + password = "@CJDNS_ADMIN_PASSWORD@"; + }; + authorizedPasswords = map (p: { password = p; }) cfg.authorizedPasswords; + interfaces = { + ETHInterface = if (cfg.ETHInterface.bind != "") then [ cfg.ETHInterface ] else [ ]; + UDPInterface = if (cfg.UDPInterface.bind != "") then [ cfg.UDPInterface ] else [ ]; + }; - cjdrouteConf = '' { - "admin": {"bind": "${cfg.admin.bind}", "password": "\${CJDNS_ADMIN}" }, - "privateKey": "\${CJDNS_KEY}", + privateKey = "@CJDNS_PRIVATE_KEY@"; - "interfaces": { - '' + resetAfterInactivitySeconds = 100; - + optionalString (cfg.interfaces.udp.bind.address != null) '' - "UDPInterface": [ { - "bind": "${cfg.interfaces.udp.bind.address}:"'' - ${if cfg.interfaces.upd.bind.port != null - then ${toString cfg.interfaces.udp.bind.port} - else ${RANDOM} - fi) - + '' } ]'' + router = { + interface = { type = "TUNInterface"; }; + ipTunnel = { + allowedConnections = []; + outgoingConnections = []; + }; + }; - + (if cfg.interfaces.eth.bind != null then '' - "ETHInterface": [ { - "bind": "${cfg.interfaces.eth.bind}", - "beacon": ${toString cfg.interfaces.eth.beacon} - } ] - '' fi ) - + '' - }, - "router": { "interface": { "type": "TUNInterface" }, }, - "security": [ { "setuser": "nobody" } ] - } - ''; + security = [ { exemptAngel = 1; setuser = "nobody"; } ]; + + }); - cjdrouteConfFile = pkgs.writeText "cjdroute.conf" cjdrouteConf - */ in { @@ -62,146 +46,180 @@ in type = types.bool; default = false; description = '' - Enable this option to start a instance of the - cjdns network encryption and and routing engine. - Configuration will be read from confFile. + Whether to enable the cjdns network encryption + and routing engine. A file at /etc/cjdns.keys will + be created if it does not exist to contain a random + secret key that your IPv6 address will be derived from. ''; }; - confFile = mkOption { - default = "/etc/cjdroute.conf"; - description = '' - Configuration file to pipe to cjdroute. + authorizedPasswords = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ + "snyrfgkqsc98qh1y4s5hbu0j57xw5s0" + "z9md3t4p45mfrjzdjurxn4wuj0d8swv" + "49275fut6tmzu354pq70sr5b95qq0vj" + ]; + description = '' + Any remote cjdns nodes that offer these passwords on + connection will be allowed to route through this node. ''; }; - - /* + admin = { bind = mkOption { + type = types.string; default = "127.0.0.1:11234"; description = '' Bind the administration port to this address and port. ''; }; + }; - passwordFile = mkOption { - example = "/root/cjdns.adminPassword"; + UDPInterface = { + bind = mkOption { + type = types.string; + default = ""; + example = "192.168.1.32:43211"; + description = '' + Address and port to bind UDP tunnels to. + ''; + }; + connectTo = mkOption { + type = types.attrsOf ( types.submodule ( + { options, ... }: + { options = { + # TODO make host an option, and add it to networking.extraHosts + password = mkOption { + type = types.str; + description = "Authorized password to the opposite end of the tunnel."; + }; + publicKey = mkOption { + type = types.str; + description = "Public key at the opposite end of the tunnel."; + }; + }; + } + )); + default = { }; + example = { + "192.168.1.1:27313" = { + password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; + publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; + }; + }; + description = '' + Credentials for making UDP tunnels. + ''; + }; + }; + + ETHInterface = { + bind = mkOption { + default = ""; + example = "eth0"; description = '' - File containing a password to the administration port. + Bind to this device for native ethernet operation. ''; - }; - }; - - keyFile = mkOption { - type = types.str; - example = "/root/cjdns.key"; - description = '' - Path to a file containing a cjdns private key on a single line. - ''; - }; - - passwordsFile = mkOption { - type = types.str; - default = null; - example = "/root/cjdns.authorizedPasswords"; - description = '' - A file containing a list of json dictionaries with passwords. - For example: - {"password": "s8xf5z7znl4jt05g922n3wpk75wkypk"}, - { "name": "nice guy", - "password": "xhthk1mglz8tpjrbbvdlhyc092rhpx5"}, - {"password": "3qfxyhmrht7uwzq29pmhbdm9w4bnc8w"} - ''; - }; - - interfaces = { - udp = { - bind = { - address = mkOption { - default = "0.0.0.0"; - description = '' - Address to bind UDP tunnels to; disable by setting to null; - ''; - }; - port = mkOption { - type = types.int; - default = null; - description = '' - Port to bind UDP tunnels to. - A port will be choosen at random if this is not set. - This option is required to act as the server end of - a tunnel. - ''; - }; - }; - }; - - eth = { - bind = mkOption { - default = null; - example = "eth0"; - description = '' - Bind to this device and operate with native wire format. - ''; - }; - - beacon = mkOption { - default = 2; - description = '' - Auto-connect to other cjdns nodes on the same network. - Options: - 0 -- Disabled. - - 1 -- Accept beacons, this will cause cjdns to accept incoming - beacon messages and try connecting to the sender. - - 2 -- Accept and send beacons, this will cause cjdns to broadcast - messages on the local network which contain a randomly - generated per-session password, other nodes which have this - set to 1 or 2 will hear the beacon messages and connect - automatically. - ''; - }; - - connectTo = mkOption { - type = types.listOf types.str; - default = []; - description = '' - Credentials for connecting look similar to UDP credientials - except they begin with the mac address, for example: - "01:02:03:04:05:06":{"password":"a","publicKey":"b"} - ''; - }; }; + + beacon = mkOption { + type = types.int; + default = 2; + description = '' + Auto-connect to other cjdns nodes on the same network. + Options: + 0: Disabled. + 1: Accept beacons, this will cause cjdns to accept incoming + beacon messages and try connecting to the sender. + 2: Accept and send beacons, this will cause cjdns to broadcast + messages on the local network which contain a randomly + generated per-session password, other nodes which have this + set to 1 or 2 will hear the beacon messages and connect + automatically. + ''; + }; + + connectTo = mkOption { + type = types.attrsOf ( types.submodule ( + { options, ... }: + { options = { + password = mkOption { + type = types.str; + description = "Authorized password to the opposite end of the tunnel."; + }; + publicKey = mkOption { + type = types.str; + description = "Public key at the opposite end of the tunnel."; + }; + }; + } + )); + default = { }; + example = { + "01:02:03:04:05:06" = { + password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; + publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; + }; + }; + description = '' + Credentials for connecting look similar to UDP credientials + except they begin with the mac address. + ''; + }; }; - */ + }; + }; config = mkIf config.services.cjdns.enable { boot.kernelModules = [ "tun" ]; - /* - networking.firewall.allowedUDPPorts = mkIf (cfg.udp.bind.port != null) [ - cfg.udp.bind.port - ]; - */ + # networking.firewall.allowedUDPPorts = ... systemd.services.cjdns = { description = "encrypted networking for everybody"; wantedBy = [ "multi-user.target" ]; - wants = [ "network.target" ]; - before = [ "network.target" ]; - path = [ pkgs.cjdns ]; + after = [ "network-interfaces.target" ]; + + script = '' + source /etc/cjdns.keys + echo '${cjdrouteConf}' | sed \ + -e "s/@CJDNS_ADMIN_PASSWORD@/$CJDNS_ADMIN_PASSWORD/g" \ + -e "s/@CJDNS_PRIVATE_KEY@/$CJDNS_PRIVATE_KEY/g" \ + | ${pkgs.cjdns}/sbin/cjdroute + ''; serviceConfig = { Type = "forking"; - ExecStart = '' - ${pkgs.stdenv.shell} -c "${pkgs.cjdns}/sbin/cjdroute < ${cfg.confFile}" - ''; Restart = "on-failure"; }; }; + + system.activationScripts.cjdns = '' + grep -q "CJDNS_PRIVATE_KEY=" /etc/cjdns.keys || \ + echo "CJDNS_PRIVATE_KEY=$(${pkgs.cjdns}/sbin/makekey)" \ + >> /etc/cjdns.keys + + grep -q "CJDNS_ADMIN_PASSWORD=" /etc/cjdns.keys || \ + echo "CJDNS_ADMIN_PASSWORD=$(${pkgs.coreutils}/bin/head -c 96 /dev/urandom | ${pkgs.coreutils}/bin/tr -dc A-Za-z0-9)" \ + >> /etc/cjdns.keys + + chmod 600 /etc/cjdns.keys + ''; + + assertions = [ + { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" ); + message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined."; + } + { assertion = config.networking.enableIPv6; + message = "networking.enableIPv6 must be enabled for CJDNS to work"; + } + ]; + }; -} + +} \ No newline at end of file diff --git a/pkgs/tools/networking/cjdns/default.nix b/pkgs/tools/networking/cjdns/default.nix index 48e21f4507e5..be107dfa81e7 100644 --- a/pkgs/tools/networking/cjdns/default.nix +++ b/pkgs/tools/networking/cjdns/default.nix @@ -1,21 +1,27 @@ { stdenv, fetchgit, nodejs, which, python27 }: let - date = "20140303"; - rev = "f11ce1fd4795b0173ac0ef18c8a6f752aa824adb"; + date = "20140829"; + rev = "9595d67f9edd759054c5bd3aaee0968ff55e361a"; in stdenv.mkDerivation { name = "cjdns-${date}-${stdenv.lib.strings.substring 0 7 rev}"; src = fetchgit { - url = "git://github.com/cjdelisle/cjdns.git"; + url = "https://github.com/cjdelisle/cjdns.git"; inherit rev; - sha256 = "1bxhf9f1v0slf9mz3ll6jf45mkwvwxlf3yqxx9k23kjyr1nsc8s8"; + sha256 = "519c549c42ae26c5359ae13a4548c44b51e36db403964b4d9f78c19b749dfb83"; }; buildInputs = [ which python27 nodejs]; - builder = ./builder.sh; + patches = [ ./makekey.patch ]; + + buildPhase = "bash do"; + installPhase = '' + mkdir -p $out/sbin + cp cjdroute makekey $out/sbin + ''; meta = { homepage = https://github.com/cjdelisle/cjdns; diff --git a/pkgs/tools/networking/cjdns/makekey.patch b/pkgs/tools/networking/cjdns/makekey.patch new file mode 100644 index 000000000000..fcce5e3e728e --- /dev/null +++ b/pkgs/tools/networking/cjdns/makekey.patch @@ -0,0 +1,64 @@ +diff --git a/contrib/c/makekey.c b/contrib/c/makekey.c +new file mode 100644 +index 0000000..c7184e5 +--- /dev/null ++++ b/contrib/c/makekey.c +@@ -0,0 +1,46 @@ ++/* vim: set expandtab ts=4 sw=4: */ ++/* ++ * You may redistribute this program and/or modify it under the terms of ++ * the GNU General Public License as published by the Free Software Foundation, ++ * either version 3 of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++#include "crypto/random/Random.h" ++#include "memory/MallocAllocator.h" ++#include "crypto/AddressCalc.h" ++#include "util/AddrTools.h" ++#include "util/Hex.h" ++ ++#include "crypto_scalarmult_curve25519.h" ++ ++#include ++ ++int main(int argc, char** argv) ++{ ++ struct Allocator* alloc = MallocAllocator_new(1<<22); ++ struct Random* rand = Random_new(alloc, NULL, NULL); ++ ++ uint8_t privateKey[32]; ++ uint8_t publicKey[32]; ++ uint8_t ip[16]; ++ uint8_t hexPrivateKey[65]; ++ ++ for (;;) { ++ Random_bytes(rand, privateKey, 32); ++ crypto_scalarmult_curve25519_base(publicKey, privateKey); ++ if (AddressCalc_addressForPublicKey(ip, publicKey)) { ++ Hex_encode(hexPrivateKey, 65, privateKey, 32); ++ printf(hexPrivateKey); ++ return 0; ++ } ++ } ++ return 0; ++} ++ +diff --git a/node_build/make.js b/node_build/make.js +index 5e51645..11465e3 100644 +--- a/node_build/make.js ++++ b/node_build/make.js +@@ -339,6 +339,7 @@ Builder.configure({ + builder.buildExecutable('contrib/c/privatetopublic.c'); + builder.buildExecutable('contrib/c/sybilsim.c'); + builder.buildExecutable('contrib/c/makekeys.c'); ++ builder.buildExecutable('contrib/c/makekey.c'); + + builder.buildExecutable('crypto/random/randombytes.c'); +