at 18.03-beta 6.0 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 verifyChainPathAssert = n: c: { 11 assertion = c.verifyHostname == null || (c.verifyChain || c.verifyPeer); 12 message = "stunnel: \"${n}\" client configuration - hostname verification " + 13 "is not possible without either verifyChain or verifyPeer enabled"; 14 }; 15 16 serverConfig = { 17 options = { 18 accept = mkOption { 19 type = types.int; 20 description = "On which port stunnel should listen for incoming TLS connections."; 21 }; 22 23 connect = mkOption { 24 type = types.int; 25 description = "To which port the decrypted connection should be forwarded."; 26 }; 27 28 cert = mkOption { 29 type = types.path; 30 description = "File containing both the private and public keys."; 31 }; 32 }; 33 }; 34 35 clientConfig = { 36 options = { 37 accept = mkOption { 38 type = types.string; 39 description = "IP:Port on which connections should be accepted."; 40 }; 41 42 connect = mkOption { 43 type = types.string; 44 description = "IP:Port destination to connect to."; 45 }; 46 47 verifyChain = mkOption { 48 type = types.bool; 49 default = true; 50 description = "Check if the provided certificate has a valid certificate chain (against CAPath)."; 51 }; 52 53 verifyPeer = mkOption { 54 type = types.bool; 55 default = false; 56 description = "Check if the provided certificate is contained in CAPath."; 57 }; 58 59 CAPath = mkOption { 60 type = types.path; 61 default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; 62 description = "Path to a file containing certificates to validate against."; 63 }; 64 65 verifyHostname = mkOption { 66 type = with types; nullOr string; 67 default = null; 68 description = "If set, stunnel checks if the provided certificate is valid for the given hostname."; 69 }; 70 }; 71 }; 72 73 74in 75 76{ 77 78 ###### interface 79 80 options = { 81 82 services.stunnel = { 83 84 enable = mkOption { 85 type = types.bool; 86 default = false; 87 description = "Whether to enable the stunnel TLS tunneling service."; 88 }; 89 90 user = mkOption { 91 type = with types; nullOr string; 92 default = "nobody"; 93 description = "The user under which stunnel runs."; 94 }; 95 96 group = mkOption { 97 type = with types; nullOr string; 98 default = "nogroup"; 99 description = "The group under which stunnel runs."; 100 }; 101 102 logLevel = mkOption { 103 type = types.enum [ "emerg" "alert" "crit" "err" "warning" "notice" "info" "debug" ]; 104 default = "info"; 105 description = "Verbosity of stunnel output."; 106 }; 107 108 fipsMode = mkOption { 109 type = types.bool; 110 default = false; 111 description = "Enable FIPS 140-2 mode required for compliance."; 112 }; 113 114 enableInsecureSSLv3 = mkOption { 115 type = types.bool; 116 default = false; 117 description = "Enable support for the insecure SSLv3 protocol."; 118 }; 119 120 121 servers = mkOption { 122 description = "Define the server configuations."; 123 type = with types; attrsOf (submodule serverConfig); 124 example = { 125 fancyWebserver = { 126 enable = true; 127 accept = 443; 128 connect = 8080; 129 cert = "/path/to/pem/file"; 130 }; 131 }; 132 default = { }; 133 }; 134 135 clients = mkOption { 136 description = "Define the client configurations."; 137 type = with types; attrsOf (submodule clientConfig); 138 example = { 139 foobar = { 140 accept = "0.0.0.0:8080"; 141 connect = "nixos.org:443"; 142 verifyChain = false; 143 }; 144 }; 145 default = { }; 146 }; 147 }; 148 }; 149 150 151 ###### implementation 152 153 config = mkIf cfg.enable { 154 155 assertions = concatLists [ 156 (singleton { 157 assertion = (length (attrValues cfg.servers) != 0) || ((length (attrValues cfg.clients)) != 0); 158 message = "stunnel: At least one server- or client-configuration has to be present."; 159 }) 160 161 (mapAttrsToList verifyChainPathAssert cfg.clients) 162 ]; 163 164 environment.systemPackages = [ pkgs.stunnel ]; 165 166 environment.etc."stunnel.cfg".text = '' 167 ${ if cfg.user != null then "setuid = ${cfg.user}" else "" } 168 ${ if cfg.group != null then "setgid = ${cfg.group}" else "" } 169 170 debug = ${cfg.logLevel} 171 172 ${ optionalString cfg.fipsMode "fips = yes" } 173 ${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" } 174 175 ; ----- SERVER CONFIGURATIONS ----- 176 ${ lib.concatStringsSep "\n" 177 (lib.mapAttrsToList 178 (n: v: '' 179 [${n}] 180 accept = ${toString v.accept} 181 connect = ${toString v.connect} 182 cert = ${v.cert} 183 184 '') 185 cfg.servers) 186 } 187 188 ; ----- CLIENT CONFIGURATIONS ----- 189 ${ lib.concatStringsSep "\n" 190 (lib.mapAttrsToList 191 (n: v: '' 192 [${n}] 193 client = yes 194 accept = ${v.accept} 195 connect = ${v.connect} 196 verifyChain = ${yesNo v.verifyChain} 197 verifyPeer = ${yesNo v.verifyPeer} 198 ${optionalString (v.CAPath != null) "CApath = ${v.CAPath}"} 199 ${optionalString (v.verifyHostname != null) "checkHost = ${v.verifyHostname}"} 200 OCSPaia = yes 201 202 '') 203 cfg.clients) 204 } 205 ''; 206 207 systemd.services.stunnel = { 208 description = "stunnel TLS tunneling service"; 209 after = [ "network.target" ]; 210 wants = [ "network.target" ]; 211 wantedBy = [ "multi-user.target" ]; 212 restartTriggers = [ config.environment.etc."stunnel.cfg".source ]; 213 serviceConfig = { 214 ExecStart = "${pkgs.stunnel}/bin/stunnel ${config.environment.etc."stunnel.cfg".source}"; 215 Type = "forking"; 216 }; 217 }; 218 219 }; 220 221}