summary refs log tree commit diff
path: root/nixos/modules/services/networking/cjdns.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/networking/cjdns.nix')
-rw-r--r--nixos/modules/services/networking/cjdns.nix362
1 files changed, 208 insertions, 154 deletions
diff --git a/nixos/modules/services/networking/cjdns.nix b/nixos/modules/services/networking/cjdns.nix
index 9306ffd5a18..be0acb27324 100644
--- a/nixos/modules/services/networking/cjdns.nix
+++ b/nixos/modules/services/networking/cjdns.nix
@@ -1,56 +1,78 @@
-# 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;
 
 let
 
+  pkg = pkgs.cjdns;
+
   cfg = config.services.cjdns;
 
-  /*
-  # can't keep keys and passwords in the nix store,
-  # but don't want to deal with this stdin quagmire.
-
-  cjdrouteConf = '' {
-    "admin": {"bind": "${cfg.admin.bind}", "password": "\${CJDNS_ADMIN}" },
-    "privateKey": "\${CJDNS_KEY}",
-
-    "interfaces": {
-    ''
-
-    + 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)
-      + '' } ]''
-
-    + (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" } ]
-    }
-    '';   
-
-    cjdrouteConfFile = pkgs.writeText "cjdroute.conf" cjdrouteConf
-    */
+  connectToSubmodule =
+  { 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.";
+      };
+      hostname = mkOption {
+        default = "";
+        example = "foobar.hype";
+        type = types.str;
+        description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
+      };
+    };
+  };
+
+  peers = mapAttrsToList (n: v: v) (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo);
+
+  pubs  = toString (map (p: if p.hostname == "" then "" else p.publicKey) peers);
+  hosts = toString (map (p: if p.hostname == "" then "" else p.hostname)  peers);
+
+  cjdnsHosts =
+    if hosts != "" then
+      import (pkgs.stdenv.mkDerivation {
+        name = "cjdns-hosts";
+        builder = ./cjdns-hosts.sh;
+
+        inherit (pkgs) cjdns;
+        inherit pubs hosts;
+      })
+    else "";
+
+  # 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 [ ];
+    };
+
+    privateKey = "@CJDNS_PRIVATE_KEY@";
+
+    resetAfterInactivitySeconds = 100;
+
+    router = {
+      interface = { type = "TUNInterface"; };
+      ipTunnel = {
+        allowedConnections = [];
+        outgoingConnections = [];
+      };
+    };
+
+    security = [ { exemptAngel = 1; setuser = "nobody"; } ];
+
+  });
+
 in
 
 {
@@ -60,148 +82,180 @@ in
 
       enable = mkOption {
         type = types.bool;
-	default = false;
+        default = false;
         description = ''
-          Enable this option to start a instance of the 
-          cjdns network encryption and and routing engine.
-          Configuration will be read from <literal>confFile</literal>.
+          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";
+        type = types.str;
+        default = "";
+        example = "/etc/cjdroute.conf";
         description = ''
-          Configuration file to pipe to cjdroute.
+          Ignore all other cjdns options and load configuration from this file.
         '';
       };
 
-      /*
+      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 {
-	  default = "127.0.0.1:11234";
-	  description = ''
+          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";
-	  description = ''
-	    File containing a password to the administration port.
-	  '';
-	};
+      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 ( connectToSubmodule ) );
+          default = { };
+          example = {
+            "192.168.1.1:27313" = {
+              hostname = "homer.hype";
+              password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
+              publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
+            };
+          };
+          description = ''
+            Credentials for making UDP tunnels.
+          '';
+        };
       };
 
-      keyFile = mkOption {
-        type = types.str;
-	example = "/root/cjdns.key";
-	description = ''
-	  Path to a file containing a cjdns private key on a single line.
-	'';
+      ETHInterface = {
+        bind = mkOption {
+        default = "";
+        example = "eth0";
+        description = ''
+          Bind to this device for native ethernet operation.
+        '';
       };
-      
-      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 ( connectToSubmodule ) );
+          default = { };
+          example = {
+            "01:02:03:04:05:06" = {
+              hostname = "homer.hype";
+              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 ];
+      wantedBy = [ "network.target" ];
+      after = [ "networkSetup.service" "network-interfaces.target" ];
+
+      preStart = if cfg.confFile != "" then "" else ''
+        [ -e /etc/cjdns.keys ] && source /etc/cjdns.keys
+
+        if [ -z "$CJDNS_PRIVATE_KEY" ]; then
+            shopt -s lastpipe
+            ${pkg}/bin/makekeys | { read private ipv6 public; }
+
+            umask 0077
+            echo "CJDNS_PRIVATE_KEY=$private" >> /etc/cjdns.keys
+            echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public" > /etc/cjdns.public
+
+            chmod 600 /etc/cjdns.keys
+            chmod 444 /etc/cjdns.public
+        fi
+
+        if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then
+            echo "CJDNS_ADMIN_PASSWORD=$(${pkgs.coreutils}/bin/head -c 96 /dev/urandom | ${pkgs.coreutils}/bin/tr -dc A-Za-z0-9)" \
+                >> /etc/cjdns.keys
+        fi
+      '';
+
+      script = (
+        if cfg.confFile != "" then "${pkg}/bin/cjdroute < ${cfg.confFile}" else
+          ''
+            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" \
+                | ${pkg}/bin/cjdroute
+         ''
+      );
 
       serviceConfig = {
         Type = "forking";
-	ExecStart = ''
-          ${pkgs.stdenv.shell} -c "${pkgs.cjdns}/sbin/cjdroute < ${cfg.confFile}"
-	'';
-	Restart = "on-failure";
+        Restart = "on-failure";
       };
     };
+
+    networking.extraHosts = "${cjdnsHosts}";
+
+    assertions = [
+      { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile == "" );
+        message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined.";
+      }
+      { assertion = config.networking.enableIPv6;
+        message = "networking.enableIPv6 must be enabled for CJDNS to work";
+      }
+    ];
+
   };
+
 }