diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index 0954e6185af1..2d9ea1450ff0 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -130,6 +130,7 @@ newrelic = 119; starbound = 120; hydra = 122; + spiped = 123; # When adding a uid, make sure it doesn't match an existing gid. @@ -234,6 +235,7 @@ starbound = 120; grsecurity = 121; hydra = 122; + spiped = 123; # When adding a gid, make sure it doesn't match an existing uid. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index b2f530474b51..b58e5b6a79a0 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -207,6 +207,7 @@ ./services/networking/rpcbind.nix ./services/networking/sabnzbd.nix ./services/networking/searx.nix + ./services/networking/spiped.nix ./services/networking/supybot.nix ./services/networking/syncthing.nix ./services/networking/ssh/lshd.nix diff --git a/nixos/modules/services/networking/spiped.nix b/nixos/modules/services/networking/spiped.nix new file mode 100644 index 000000000000..ec5908b182fb --- /dev/null +++ b/nixos/modules/services/networking/spiped.nix @@ -0,0 +1,212 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.spiped; +in +{ + options = { + services.spiped = mkOption { + type = types.attrsOf (types.submodule ( + { + options = { + encrypt = mkOption { + type = types.bool; + default = false; + description = '' + Take unencrypted connections from the + source socket and send encrypted + connections to the target socket. + ''; + }; + + decrypt = mkOption { + type = types.bool; + default = false; + description = '' + Take encrypted connections from the + source socket and send unencrypted + connections to the target socket. + ''; + }; + + source = mkOption { + type = types.str; + description = '' + Address on which spiped should listen for incoming + connections. Must be in one of the following formats: + /absolute/path/to/unix/socket, + host.name:port, + [ip.v4.ad.dr]:port or + [ipv6::addr]:port - note that + hostnames are resolved when spiped is launched and are + not re-resolved later; thus if DNS entries change + spiped will continue to connect to the expired + address. + ''; + }; + + target = mkOption { + type = types.str; + description = "Address to which spiped should connect."; + }; + + keyfile = mkOption { + type = types.path; + description = '' + Name of a file containing the spiped key. As the + daemon runs as the spiped user, the + key file must be somewhere owned by that user. By + default, we recommend putting the keys for any spipe + services in /var/lib/spiped. + ''; + }; + + timeout = mkOption { + type = types.int; + default = 5; + description = '' + Timeout, in seconds, after which an attempt to connect to + the target or a protocol handshake will be aborted (and the + connection dropped) if not completed + ''; + }; + + maxConns = mkOption { + type = types.int; + default = 100; + description = '' + Limit on the number of simultaneous connections allowed. + ''; + }; + + waitForDNS = mkOption { + type = types.bool; + default = false; + description = '' + Wait for DNS. Normally when spiped is + launched it resolves addresses and binds to its source + socket before the parent process returns; with this option + it will daemonize first and retry failed DNS lookups until + they succeed. This allows spiped to + launch even if DNS isn't set up yet, but at the expense of + losing the guarantee that once spiped has + finished launching it will be ready to create pipes. + ''; + }; + + disableKeepalives = mkOption { + type = types.bool; + default = false; + description = "Disable transport layer keep-alives."; + }; + + weakHandshake = mkOption { + type = types.bool; + default = false; + description = '' + Use fast/weak handshaking: This reduces the CPU time spent + in the initial connection setup, at the expense of losing + perfect forward secrecy. + ''; + }; + + resolveRefresh = mkOption { + type = types.int; + default = 60; + description = '' + Resolution refresh time for the target socket, in seconds. + ''; + }; + + disableReresolution = mkOption { + type = types.bool; + default = false; + description = "Disable target address re-resolution."; + }; + }; + } + )); + + default = {}; + + example = literalExample '' + { + pipe1 = + { keyfile = "/var/lib/spiped/pipe1.key"; + encrypt = true; + source = "localhost:6000"; + target = "endpoint.example.com:7000"; + }; + pipe2 = + { keyfile = "/var/lib/spiped/pipe2.key"; + decrypt = true; + source = "0.0.0.0:7000"; + target = "localhost:3000"; + }; + } + ''; + + description = '' + Configuration for a secure pipe daemon. The daemon can be + started, stopped, or examined using + systemctl, under the name + spiped@foo. + ''; + }; + }; + + config = { + assertions = mapAttrsToList (name: c: { + assertion = (c.encrypt -> !c.decrypt) || (c.decrypt -> c.encrypt); + message = "A pipe must either encrypt or decrypt"; + }) cfg; + + users.extraGroups.spiped.gid = config.ids.gids.spiped; + users.extraUsers.spiped = { + description = "Secure Pipe Service user"; + group = "spiped"; + uid = config.ids.uids.spiped; + }; + + systemd.services."spiped@" = { + description = "Secure pipe '%i'"; + after = [ "network.target" ]; + + serviceConfig = { + Restart = "always"; + User = "spiped"; + PermissionsStartOnly = true; + }; + + preStart = '' + cd /var/lib/spiped + chmod -R 0660 * + chown -R spiped:spiped * + ''; + scriptArgs = "%i"; + script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`"; + }; + + system.activationScripts.spiped = optionalString (cfg != {}) + "mkdir -p /var/lib/spiped"; + + # Setup spiped config files + environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec" + { text = concatStringsSep " " + [ (if cfg.encrypt then "-e" else "-d") # Mode + "-s ${cfg.source}" # Source + "-t ${cfg.target}" # Target + "-k ${cfg.keyfile}" # Keyfile + "-n ${toString cfg.maxConns}" # Max number of conns + "-o ${toString cfg.timeout}" # Timeout + (optionalString cfg.waitForDNS "-D") # Wait for DNS + (optionalString cfg.weakHandshake "-f") # No PFS + (optionalString cfg.disableKeepalives "-j") # Keepalives + (if cfg.disableReresolution then "-R" + else "-r ${toString cfg.resolveRefresh}") + ]; + }) cfg; + }; +}