summary refs log tree commit diff
diff options
context:
space:
mode:
authorScott Worley <scottworley@scottworley.com>2022-02-22 16:42:29 -0800
committerScott Worley <scottworley@scottworley.com>2022-03-11 14:36:26 -0800
commit131399effb405114449d7777da650d0977931178 (patch)
tree5c2efafd948508bde3fdc1c01af824604b0050b7
parent61585d1cd7f699dd9187ade7c0b21735c96b53ee (diff)
downloadnixpkgs-131399effb405114449d7777da650d0977931178.tar
nixpkgs-131399effb405114449d7777da650d0977931178.tar.gz
nixpkgs-131399effb405114449d7777da650d0977931178.tar.bz2
nixpkgs-131399effb405114449d7777da650d0977931178.tar.lz
nixpkgs-131399effb405114449d7777da650d0977931178.tar.xz
nixpkgs-131399effb405114449d7777da650d0977931178.tar.zst
nixpkgs-131399effb405114449d7777da650d0977931178.zip
nixos/stunnel: Make free-form
This unlocks stunnel's other ~100 configuration directives, allowing
full stunnel use in NixOS.
-rw-r--r--nixos/modules/services/networking/stunnel.nix158
1 files changed, 57 insertions, 101 deletions
diff --git a/nixos/modules/services/networking/stunnel.nix b/nixos/modules/services/networking/stunnel.nix
index df4908a0fff..55fac27f92c 100644
--- a/nixos/modules/services/networking/stunnel.nix
+++ b/nixos/modules/services/networking/stunnel.nix
@@ -7,80 +7,27 @@ let
   cfg = config.services.stunnel;
   yesNo = val: if val then "yes" else "no";
 
+  verifyRequiredField = type: field: n: c: {
+    assertion = hasAttr field c;
+    message =  "stunnel: \"${n}\" ${type} configuration - Field ${field} is required.";
+  };
+
   verifyChainPathAssert = n: c: {
-    assertion = c.verifyHostname == null || (c.verifyChain || c.verifyPeer);
+    assertion = (c.verifyHostname or null) == null || (c.verifyChain || c.verifyPeer);
     message =  "stunnel: \"${n}\" client configuration - hostname verification " +
       "is not possible without either verifyChain or verifyPeer enabled";
   };
 
-  serverConfig = {
-    options = {
-      accept = mkOption {
-        type = types.either types.str types.int;
-        description = ''
-          On which [host:]port stunnel should listen for incoming TLS connections.
-          Note that unlike other softwares stunnel ipv6 address need no brackets,
-          so to listen on all IPv6 addresses on port 1234 one would use ':::1234'.
-        '';
-      };
-
-      connect = mkOption {
-        type = types.either types.str types.int;
-        description = "Port or IP:Port to which the decrypted connection should be forwarded.";
-      };
-
-      cert = mkOption {
-        type = types.path;
-        description = "File containing both the private and public keys.";
-      };
-    };
-  };
-
-  clientConfig = {
-    options = {
-      accept = mkOption {
-        type = types.str;
-        description = "IP:Port on which connections should be accepted.";
-      };
-
-      connect = mkOption {
-        type = types.str;
-        description = "IP:Port destination to connect to.";
-      };
-
-      verifyChain = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Check if the provided certificate has a valid certificate chain (against CAPath).";
-      };
-
-      verifyPeer = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Check if the provided certificate is contained in CAPath.";
-      };
-
-      CAPath = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = "Path to a directory containing certificates to validate against.";
-      };
-
-      CAFile = mkOption {
-        type = types.nullOr types.path;
-        default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
-        defaultText = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
-        description = "Path to a file containing certificates to validate against.";
-      };
-
-      verifyHostname = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = "If set, stunnel checks if the provided certificate is valid for the given hostname.";
-      };
-    };
-  };
-
+  removeNulls = mapAttrs (_: filterAttrs (_: v: v != null));
+  mkValueString = v:
+    if v == true then "yes"
+    else if v == false then "no"
+    else generators.mkValueStringDefault {} v;
+  generateConfig = c:
+    generators.toINI {
+      mkSectionName = id;
+      mkKeyValue = k: v: "${k} = ${mkValueString v}";
+    } (removeNulls c);
 
 in
 
@@ -130,8 +77,13 @@ in
 
 
       servers = mkOption {
-        description = "Define the server configuations.";
-        type = with types; attrsOf (submodule serverConfig);
+        description = ''
+          Define the server configuations.
+
+          See "SERVICE-LEVEL OPTIONS" in <citerefentry><refentrytitle>stunnel</refentrytitle>
+          <manvolnum>8</manvolnum></citerefentry>.
+        '';
+        type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str])));
         example = {
           fancyWebserver = {
             accept = 443;
@@ -143,8 +95,33 @@ in
       };
 
       clients = mkOption {
-        description = "Define the client configurations.";
-        type = with types; attrsOf (submodule clientConfig);
+        description = ''
+          Define the client configurations.
+
+          By default, verifyChain and OCSPaia are enabled and a CAFile is provided from pkgs.cacert.
+
+          See "SERVICE-LEVEL OPTIONS" in <citerefentry><refentrytitle>stunnel</refentrytitle>
+          <manvolnum>8</manvolnum></citerefentry>.
+        '';
+        type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str])));
+
+        apply = let
+          applyDefaults = c:
+            {
+              CAFile = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+              OCSPaia = true;
+              verifyChain = true;
+            } // c;
+          setCheckHostFromVerifyHostname = c:
+            # To preserve backward-compatibility with the old NixOS stunnel module
+            # definition, allow "verifyHostname" as an alias for "checkHost".
+            c // {
+              checkHost = c.checkHost or c.verifyHostname or null;
+              verifyHostname = null; # Not a real stunnel configuration setting
+            };
+          forceClient = c: c // { client = true; };
+        in mapAttrs (_: c: forceClient (setCheckHostFromVerifyHostname (applyDefaults c)));
+
         example = {
           foobar = {
             accept = "0.0.0.0:8080";
@@ -169,6 +146,11 @@ in
       })
 
       (mapAttrsToList verifyChainPathAssert cfg.clients)
+      (mapAttrsToList (verifyRequiredField "client" "accept") cfg.clients)
+      (mapAttrsToList (verifyRequiredField "client" "connect") cfg.clients)
+      (mapAttrsToList (verifyRequiredField "server" "accept") cfg.servers)
+      (mapAttrsToList (verifyRequiredField "server" "cert") cfg.servers)
+      (mapAttrsToList (verifyRequiredField "server" "connect") cfg.servers)
     ];
 
     environment.systemPackages = [ pkgs.stunnel ];
@@ -183,36 +165,10 @@ in
       ${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" }
 
       ; ----- SERVER CONFIGURATIONS -----
-      ${ lib.concatStringsSep "\n"
-           (lib.mapAttrsToList
-             (n: v: ''
-               [${n}]
-               accept = ${toString v.accept}
-               connect = ${toString v.connect}
-               cert = ${v.cert}
-
-             '')
-           cfg.servers)
-      }
+      ${ generateConfig cfg.servers }
 
       ; ----- CLIENT CONFIGURATIONS -----
-      ${ lib.concatStringsSep "\n"
-           (lib.mapAttrsToList
-             (n: v: ''
-               [${n}]
-               client = yes
-               accept = ${v.accept}
-               connect = ${v.connect}
-               verifyChain = ${yesNo v.verifyChain}
-               verifyPeer = ${yesNo v.verifyPeer}
-               ${optionalString (v.CAPath != null) "CApath = ${v.CAPath}"}
-               ${optionalString (v.CAFile != null) "CAFile = ${v.CAFile}"}
-               ${optionalString (v.verifyHostname != null) "checkHost = ${v.verifyHostname}"}
-               OCSPaia = yes
-
-             '')
-           cfg.clients)
-      }
+      ${ generateConfig cfg.clients }
     '';
 
     systemd.services.stunnel = {