# NixOS module for iodine, ip over dns daemon { config, lib, pkgs, ... }: let cfg = config.services.iodine; iodinedUser = "iodined"; /* is this path made unreadable by ProtectHome = true ? */ isProtected = x: lib.hasPrefix "/root" x || lib.hasPrefix "/home" x; in { imports = [ (lib.mkRenamedOptionModule [ "services" "iodined" "enable" ] [ "services" "iodine" "server" "enable" ]) (lib.mkRenamedOptionModule [ "services" "iodined" "domain" ] [ "services" "iodine" "server" "domain" ]) (lib.mkRenamedOptionModule [ "services" "iodined" "ip" ] [ "services" "iodine" "server" "ip" ]) (lib.mkRenamedOptionModule [ "services" "iodined" "extraConfig" ] [ "services" "iodine" "server" "extraConfig" ]) (lib.mkRemovedOptionModule [ "services" "iodined" "client" ] "") ]; ### configuration options = { services.iodine = { clients = lib.mkOption { default = {}; description = '' Each attribute of this option defines a systemd service that runs iodine. Many or none may be defined. The name of each service is `iodine-«name»` where «name» is the name of the corresponding attribute name. ''; example = lib.literalExpression '' { foo = { server = "tunnel.mdomain.com"; relay = "8.8.8.8"; extraConfig = "-v"; } } ''; type = lib.types.attrsOf ( lib.types.submodule ( { options = { server = lib.mkOption { type = lib.types.str; default = ""; description = "Hostname of server running iodined"; example = "tunnel.mydomain.com"; }; relay = lib.mkOption { type = lib.types.str; default = ""; description = "DNS server to use as an intermediate relay to the iodined server"; example = "8.8.8.8"; }; extraConfig = lib.mkOption { type = lib.types.str; default = ""; description = "Additional command line parameters"; example = "-l 192.168.1.10 -p 23"; }; passwordFile = lib.mkOption { type = lib.types.str; default = ""; description = "Path to a file containing the password."; }; }; } ) ); }; server = { enable = lib.mkOption { type = lib.types.bool; default = false; description = "enable iodined server"; }; ip = lib.mkOption { type = lib.types.str; default = ""; description = "The assigned ip address or ip range"; example = "172.16.10.1/24"; }; domain = lib.mkOption { type = lib.types.str; default = ""; description = "Domain or subdomain of which nameservers point to us"; example = "tunnel.mydomain.com"; }; extraConfig = lib.mkOption { type = lib.types.str; default = ""; description = "Additional command line parameters"; example = "-l 192.168.1.10 -p 23"; }; passwordFile = lib.mkOption { type = lib.types.str; default = ""; description = "File that contains password"; }; }; }; }; ### implementation config = lib.mkIf (cfg.server.enable || cfg.clients != {}) { environment.systemPackages = [ pkgs.iodine ]; boot.kernelModules = [ "tun" ]; systemd.services = let createIodineClientService = name: cfg: { description = "iodine client - ${name}"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; script = "exec ${pkgs.iodine}/bin/iodine -f -u ${iodinedUser} ${cfg.extraConfig} ${lib.optionalString (cfg.passwordFile != "") "< \"${builtins.toString cfg.passwordFile}\""} ${cfg.relay} ${cfg.server}"; serviceConfig = { RestartSec = "30s"; Restart = "always"; # hardening : # Filesystem access ProtectSystem = "strict"; ProtectHome = if isProtected cfg.passwordFile then "read-only" else "true" ; PrivateTmp = true; ReadWritePaths = "/dev/net/tun"; PrivateDevices = false; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; # Caps NoNewPrivileges = true; # Misc. LockPersonality = true; RestrictRealtime = true; PrivateMounts = true; MemoryDenyWriteExecute = true; }; }; in lib.listToAttrs ( lib.mapAttrsToList (name: value: lib.nameValuePair "iodine-${name}" (createIodineClientService name value)) cfg.clients ) // { iodined = lib.mkIf (cfg.server.enable) { description = "iodine, ip over dns server daemon"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; script = "exec ${pkgs.iodine}/bin/iodined -f -u ${iodinedUser} ${cfg.server.extraConfig} ${lib.optionalString (cfg.server.passwordFile != "") "< \"${builtins.toString cfg.server.passwordFile}\""} ${cfg.server.ip} ${cfg.server.domain}"; serviceConfig = { # Filesystem access ProtectSystem = "strict"; ProtectHome = if isProtected cfg.server.passwordFile then "read-only" else "true" ; PrivateTmp = true; ReadWritePaths = "/dev/net/tun"; PrivateDevices = false; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; # Caps NoNewPrivileges = true; # Misc. LockPersonality = true; RestrictRealtime = true; PrivateMounts = true; MemoryDenyWriteExecute = true; }; }; }; users.users.${iodinedUser} = { uid = config.ids.uids.iodined; group = "iodined"; description = "Iodine daemon user"; }; users.groups.iodined.gid = config.ids.gids.iodined; }; }