{ config, lib, pkgs, ... }: with lib; let cfg = config.services.spiped; in { options = { services.spiped = { enable = mkOption { type = types.bool; default = false; description = "Enable the spiped service module."; }; config = 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 readable by that user. To securely manage the file within your configuration consider a tool such as agenix or sops-nix. ''; }; 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 = literalExpression '' { 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 = mkIf cfg.enable { assertions = mapAttrsToList (name: c: { assertion = (c.encrypt -> !c.decrypt) || (c.decrypt -> c.encrypt); message = "A pipe must either encrypt or decrypt"; }) cfg.config; users.groups.spiped.gid = config.ids.gids.spiped; users.users.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"; }; stopIfChanged = false; scriptArgs = "%i"; script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`"; }; # 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.config; }; }