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