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;
+ };
+}