# NixOS module for iodine, ip over dns daemon { config, lib, pkgs, ... }: with lib; let cfg = config.services.iodine; iodinedUser = "iodined"; /* is this path made unreadable by ProtectHome = true ? */ isProtected = x: hasPrefix "/root" x || hasPrefix "/home" x; in { imports = [ (mkRenamedOptionModule [ "services" "iodined" "enable" ] [ "services" "iodine" "server" "enable" ]) (mkRenamedOptionModule [ "services" "iodined" "domain" ] [ "services" "iodine" "server" "domain" ]) (mkRenamedOptionModule [ "services" "iodined" "ip" ] [ "services" "iodine" "server" "ip" ]) (mkRenamedOptionModule [ "services" "iodined" "extraConfig" ] [ "services" "iodine" "server" "extraConfig" ]) (mkRemovedOptionModule [ "services" "iodined" "client" ] "") ]; ### configuration options = { services.iodine = { clients = 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 = literalExpression '' { foo = { server = "tunnel.mdomain.com"; relay = "8.8.8.8"; extraConfig = "-v"; } } ''; type = types.attrsOf ( types.submodule ( { options = { server = mkOption { type = types.str; default = ""; description = "Hostname of server running iodined"; example = "tunnel.mydomain.com"; }; relay = mkOption { type = types.str; default = ""; description = "DNS server to use as an intermediate relay to the iodined server"; example = "8.8.8.8"; }; extraConfig = mkOption { type = types.str; default = ""; description = "Additional command line parameters"; example = "-l 192.168.1.10 -p 23"; }; passwordFile = mkOption { type = types.str; default = ""; description = "Path to a file containing the password."; }; }; } ) ); }; server = { enable = mkOption { type = types.bool; default = false; description = "enable iodined server"; }; ip = mkOption { type = types.str; default = ""; description = "The assigned ip address or ip range"; example = "172.16.10.1/24"; }; domain = mkOption { type = types.str; default = ""; description = "Domain or subdomain of which nameservers point to us"; example = "tunnel.mydomain.com"; }; extraConfig = mkOption { type = types.str; default = ""; description = "Additional command line parameters"; example = "-l 192.168.1.10 -p 23"; }; passwordFile = mkOption { type = types.str; default = ""; description = "File that contains password"; }; }; }; }; ### implementation config = 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} ${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 listToAttrs ( mapAttrsToList (name: value: nameValuePair "iodine-${name}" (createIodineClientService name value)) cfg.clients ) // { iodined = 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} ${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; }; }