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