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