at 24.11-pre 7.5 kB view raw
1{ config, lib, pkgs, ... }: 2let 3 inherit (lib) 4 attrValues 5 concatMap 6 concatStringsSep 7 escapeShellArg 8 literalExpression 9 mapAttrs' 10 mkDefault 11 mkEnableOption 12 mkPackageOption 13 mkIf 14 mkOption 15 nameValuePair 16 optional 17 types 18 ; 19 20 mainCfg = config.services.ghostunnel; 21 22 module = { config, name, ... }: 23 { 24 options = { 25 26 listen = mkOption { 27 description = '' 28 Address and port to listen on (can be HOST:PORT, unix:PATH). 29 ''; 30 type = types.str; 31 }; 32 33 target = mkOption { 34 description = '' 35 Address to forward connections to (can be HOST:PORT or unix:PATH). 36 ''; 37 type = types.str; 38 }; 39 40 keystore = mkOption { 41 description = '' 42 Path to keystore (combined PEM with cert/key, or PKCS12 keystore). 43 44 NB: storepass is not supported because it would expose credentials via `/proc/*/cmdline`. 45 46 Specify this or `cert` and `key`. 47 ''; 48 type = types.nullOr types.str; 49 default = null; 50 }; 51 52 cert = mkOption { 53 description = '' 54 Path to certificate (PEM with certificate chain). 55 56 Not required if `keystore` is set. 57 ''; 58 type = types.nullOr types.str; 59 default = null; 60 }; 61 62 key = mkOption { 63 description = '' 64 Path to certificate private key (PEM with private key). 65 66 Not required if `keystore` is set. 67 ''; 68 type = types.nullOr types.str; 69 default = null; 70 }; 71 72 cacert = mkOption { 73 description = '' 74 Path to CA bundle file (PEM/X509). Uses system trust store if `null`. 75 ''; 76 type = types.nullOr types.str; 77 }; 78 79 disableAuthentication = mkOption { 80 description = '' 81 Disable client authentication, no client certificate will be required. 82 ''; 83 type = types.bool; 84 default = false; 85 }; 86 87 allowAll = mkOption { 88 description = '' 89 If true, allow all clients, do not check client cert subject. 90 ''; 91 type = types.bool; 92 default = false; 93 }; 94 95 allowCN = mkOption { 96 description = '' 97 Allow client if common name appears in the list. 98 ''; 99 type = types.listOf types.str; 100 default = []; 101 }; 102 103 allowOU = mkOption { 104 description = '' 105 Allow client if organizational unit name appears in the list. 106 ''; 107 type = types.listOf types.str; 108 default = []; 109 }; 110 111 allowDNS = mkOption { 112 description = '' 113 Allow client if DNS subject alternative name appears in the list. 114 ''; 115 type = types.listOf types.str; 116 default = []; 117 }; 118 119 allowURI = mkOption { 120 description = '' 121 Allow client if URI subject alternative name appears in the list. 122 ''; 123 type = types.listOf types.str; 124 default = []; 125 }; 126 127 extraArguments = mkOption { 128 description = "Extra arguments to pass to `ghostunnel server`"; 129 type = types.separatedString " "; 130 default = ""; 131 }; 132 133 unsafeTarget = mkOption { 134 description = '' 135 If set, does not limit target to localhost, 127.0.0.1, [::1], or UNIX sockets. 136 137 This is meant to protect against accidental unencrypted traffic on 138 untrusted networks. 139 ''; 140 type = types.bool; 141 default = false; 142 }; 143 144 # Definitions to apply at the root of the NixOS configuration. 145 atRoot = mkOption { 146 internal = true; 147 }; 148 }; 149 150 # Clients should not be authenticated with the public root certificates 151 # (afaict, it doesn't make sense), so we only provide that default when 152 # client cert auth is disabled. 153 config.cacert = mkIf config.disableAuthentication (mkDefault null); 154 155 config.atRoot = { 156 assertions = [ 157 { message = '' 158 services.ghostunnel.servers.${name}: At least one access control flag is required. 159 Set at least one of: 160 - services.ghostunnel.servers.${name}.disableAuthentication 161 - services.ghostunnel.servers.${name}.allowAll 162 - services.ghostunnel.servers.${name}.allowCN 163 - services.ghostunnel.servers.${name}.allowOU 164 - services.ghostunnel.servers.${name}.allowDNS 165 - services.ghostunnel.servers.${name}.allowURI 166 ''; 167 assertion = config.disableAuthentication 168 || config.allowAll 169 || config.allowCN != [] 170 || config.allowOU != [] 171 || config.allowDNS != [] 172 || config.allowURI != [] 173 ; 174 } 175 ]; 176 177 systemd.services."ghostunnel-server-${name}" = { 178 after = [ "network.target" ]; 179 wants = [ "network.target" ]; 180 wantedBy = [ "multi-user.target" ]; 181 serviceConfig = { 182 Restart = "always"; 183 AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; 184 DynamicUser = true; 185 LoadCredential = optional (config.keystore != null) "keystore:${config.keystore}" 186 ++ optional (config.cert != null) "cert:${config.cert}" 187 ++ optional (config.key != null) "key:${config.key}" 188 ++ optional (config.cacert != null) "cacert:${config.cacert}"; 189 }; 190 script = concatStringsSep " " ( 191 [ "${mainCfg.package}/bin/ghostunnel" ] 192 ++ optional (config.keystore != null) "--keystore=$CREDENTIALS_DIRECTORY/keystore" 193 ++ optional (config.cert != null) "--cert=$CREDENTIALS_DIRECTORY/cert" 194 ++ optional (config.key != null) "--key=$CREDENTIALS_DIRECTORY/key" 195 ++ optional (config.cacert != null) "--cacert=$CREDENTIALS_DIRECTORY/cacert" 196 ++ [ 197 "server" 198 "--listen ${config.listen}" 199 "--target ${config.target}" 200 ] ++ optional config.allowAll "--allow-all" 201 ++ map (v: "--allow-cn=${escapeShellArg v}") config.allowCN 202 ++ map (v: "--allow-ou=${escapeShellArg v}") config.allowOU 203 ++ map (v: "--allow-dns=${escapeShellArg v}") config.allowDNS 204 ++ map (v: "--allow-uri=${escapeShellArg v}") config.allowURI 205 ++ optional config.disableAuthentication "--disable-authentication" 206 ++ optional config.unsafeTarget "--unsafe-target" 207 ++ [ config.extraArguments ] 208 ); 209 }; 210 }; 211 }; 212 213in 214{ 215 216 options = { 217 services.ghostunnel.enable = mkEnableOption "ghostunnel"; 218 219 services.ghostunnel.package = mkPackageOption pkgs "ghostunnel" { }; 220 221 services.ghostunnel.servers = mkOption { 222 description = '' 223 Server mode ghostunnels (TLS listener -> plain TCP/UNIX target) 224 ''; 225 type = types.attrsOf (types.submodule module); 226 default = {}; 227 }; 228 }; 229 230 config = mkIf mainCfg.enable { 231 assertions = lib.mkMerge (map (v: v.atRoot.assertions) (attrValues mainCfg.servers)); 232 systemd = lib.mkMerge (map (v: v.atRoot.systemd) (attrValues mainCfg.servers)); 233 }; 234 235 meta.maintainers = with lib.maintainers; [ 236 roberth 237 ]; 238}