at 23.11-pre 5.8 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.stunnel; 8 yesNo = val: if val then "yes" else "no"; 9 10 verifyRequiredField = type: field: n: c: { 11 assertion = hasAttr field c; 12 message = "stunnel: \"${n}\" ${type} configuration - Field ${field} is required."; 13 }; 14 15 verifyChainPathAssert = n: c: { 16 assertion = (c.verifyHostname or null) == null || (c.verifyChain || c.verifyPeer); 17 message = "stunnel: \"${n}\" client configuration - hostname verification " + 18 "is not possible without either verifyChain or verifyPeer enabled"; 19 }; 20 21 removeNulls = mapAttrs (_: filterAttrs (_: v: v != null)); 22 mkValueString = v: 23 if v == true then "yes" 24 else if v == false then "no" 25 else generators.mkValueStringDefault {} v; 26 generateConfig = c: 27 generators.toINI { 28 mkSectionName = id; 29 mkKeyValue = k: v: "${k} = ${mkValueString v}"; 30 } (removeNulls c); 31 32in 33 34{ 35 36 ###### interface 37 38 options = { 39 40 services.stunnel = { 41 42 enable = mkOption { 43 type = types.bool; 44 default = false; 45 description = lib.mdDoc "Whether to enable the stunnel TLS tunneling service."; 46 }; 47 48 user = mkOption { 49 type = with types; nullOr str; 50 default = "nobody"; 51 description = lib.mdDoc "The user under which stunnel runs."; 52 }; 53 54 group = mkOption { 55 type = with types; nullOr str; 56 default = "nogroup"; 57 description = lib.mdDoc "The group under which stunnel runs."; 58 }; 59 60 logLevel = mkOption { 61 type = types.enum [ "emerg" "alert" "crit" "err" "warning" "notice" "info" "debug" ]; 62 default = "info"; 63 description = lib.mdDoc "Verbosity of stunnel output."; 64 }; 65 66 fipsMode = mkOption { 67 type = types.bool; 68 default = false; 69 description = lib.mdDoc "Enable FIPS 140-2 mode required for compliance."; 70 }; 71 72 enableInsecureSSLv3 = mkOption { 73 type = types.bool; 74 default = false; 75 description = lib.mdDoc "Enable support for the insecure SSLv3 protocol."; 76 }; 77 78 79 servers = mkOption { 80 description = lib.mdDoc '' 81 Define the server configurations. 82 83 See "SERVICE-LEVEL OPTIONS" in {manpage}`stunnel(8)`. 84 ''; 85 type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str]))); 86 example = { 87 fancyWebserver = { 88 accept = 443; 89 connect = 8080; 90 cert = "/path/to/pem/file"; 91 }; 92 }; 93 default = { }; 94 }; 95 96 clients = mkOption { 97 description = lib.mdDoc '' 98 Define the client configurations. 99 100 By default, verifyChain and OCSPaia are enabled and a CAFile is provided from pkgs.cacert. 101 102 See "SERVICE-LEVEL OPTIONS" in {manpage}`stunnel(8)`. 103 ''; 104 type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str]))); 105 106 apply = let 107 applyDefaults = c: 108 { 109 CAFile = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; 110 OCSPaia = true; 111 verifyChain = true; 112 } // c; 113 setCheckHostFromVerifyHostname = c: 114 # To preserve backward-compatibility with the old NixOS stunnel module 115 # definition, allow "verifyHostname" as an alias for "checkHost". 116 c // { 117 checkHost = c.checkHost or c.verifyHostname or null; 118 verifyHostname = null; # Not a real stunnel configuration setting 119 }; 120 forceClient = c: c // { client = true; }; 121 in mapAttrs (_: c: forceClient (setCheckHostFromVerifyHostname (applyDefaults c))); 122 123 example = { 124 foobar = { 125 accept = "0.0.0.0:8080"; 126 connect = "nixos.org:443"; 127 verifyChain = false; 128 }; 129 }; 130 default = { }; 131 }; 132 }; 133 }; 134 135 136 ###### implementation 137 138 config = mkIf cfg.enable { 139 140 assertions = concatLists [ 141 (singleton { 142 assertion = (length (attrValues cfg.servers) != 0) || ((length (attrValues cfg.clients)) != 0); 143 message = "stunnel: At least one server- or client-configuration has to be present."; 144 }) 145 146 (mapAttrsToList verifyChainPathAssert cfg.clients) 147 (mapAttrsToList (verifyRequiredField "client" "accept") cfg.clients) 148 (mapAttrsToList (verifyRequiredField "client" "connect") cfg.clients) 149 (mapAttrsToList (verifyRequiredField "server" "accept") cfg.servers) 150 (mapAttrsToList (verifyRequiredField "server" "cert") cfg.servers) 151 (mapAttrsToList (verifyRequiredField "server" "connect") cfg.servers) 152 ]; 153 154 environment.systemPackages = [ pkgs.stunnel ]; 155 156 environment.etc."stunnel.cfg".text = '' 157 ${ optionalString (cfg.user != null) "setuid = ${cfg.user}" } 158 ${ optionalString (cfg.group != null) "setgid = ${cfg.group}" } 159 160 debug = ${cfg.logLevel} 161 162 ${ optionalString cfg.fipsMode "fips = yes" } 163 ${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" } 164 165 ; ----- SERVER CONFIGURATIONS ----- 166 ${ generateConfig cfg.servers } 167 168 ; ----- CLIENT CONFIGURATIONS ----- 169 ${ generateConfig cfg.clients } 170 ''; 171 172 systemd.services.stunnel = { 173 description = "stunnel TLS tunneling service"; 174 after = [ "network.target" ]; 175 wants = [ "network.target" ]; 176 wantedBy = [ "multi-user.target" ]; 177 restartTriggers = [ config.environment.etc."stunnel.cfg".source ]; 178 serviceConfig = { 179 ExecStart = "${pkgs.stunnel}/bin/stunnel ${config.environment.etc."stunnel.cfg".source}"; 180 Type = "forking"; 181 }; 182 }; 183 184 meta.maintainers = with maintainers; [ 185 # Server side 186 lschuermann 187 # Client side 188 das_j 189 ]; 190 }; 191 192}