diff --git a/nixos/modules/services/security/warpgate.nix b/nixos/modules/services/security/warpgate.nix index 3547d199cae0..1ddcb2cf9ff5 100644 --- a/nixos/modules/services/security/warpgate.nix +++ b/nixos/modules/services/security/warpgate.nix @@ -45,6 +45,17 @@ in default = null; }; + databaseEncryptionKeysFile = mkOption { + description = '' + Path to file containing encryption key(s) to encrypt target credentials stored in database. + Should be a env-like file: `WARPGATE_ENCRYPTION_KEY=$(openssl rand -base64 32)`. + If you are rotating key, move the old key to `WARPGATE_ENCRYPTION_KEY_OLD`. + See [Encrypting credentials at rest](https://warpgate.null.page/encryption/). + ''; + type = nullOr str; + default = null; + }; + settings = mkOption { description = "Warpgate configuration."; type = submodule { @@ -120,18 +131,6 @@ in ] ''; }; - recordings = { - enable = mkOption { - description = "Whether to enable session recording."; - default = true; - type = bool; - }; - path = mkOption { - description = "Path to store session recordings."; - default = "/var/lib/warpgate/recordings"; - type = str; - }; - }; external_host = mkOption { description = '' Configure the domain name of this Warpgate instance. @@ -160,6 +159,11 @@ in default = "[::]:2222"; type = str; }; + proxy_protocol = mkOption { + description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy."; + default = false; + type = bool; + }; external_host = mkOption { description = "The SSH listener is reachable via this domain name externally."; default = null; @@ -201,6 +205,11 @@ in default = "[::]:8888"; type = str; }; + proxy_protocol = mkOption { + description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy."; + default = false; + type = bool; + }; external_host = mkOption { description = "The HTTP listener is reachable via this domain name externally."; default = null; @@ -270,6 +279,88 @@ in type = str; }; }; + rdp = { + enable = mkOption { + description = "Whether to enable RDP listener."; + default = false; + type = bool; + }; + listen = mkOption { + description = "Listen endpoint of RDP listener."; + default = "[::]:3389"; + type = str; + }; + proxy_protocol = mkOption { + description = "Accept HAProxy PROXY protocol v1/v2 headers from the listener's peer."; + default = false; + type = bool; + }; + external_host = mkOption { + description = "The RDP listener is reachable via this domain name externally."; + default = null; + type = nullOr str; + }; + external_port = mkOption { + description = "The RDP listener is reachable via this port externally."; + default = null; + type = nullOr str; + }; + certificate = mkOption { + description = "Path to RDP listener certificate."; + default = "/var/lib/warpgate/tls.certificate.pem"; + type = str; + }; + key = mkOption { + description = "Path to RDP listener private key."; + default = "/var/lib/warpgate/tls.key.pem"; + type = str; + }; + }; + vnc = { + enable = mkOption { + description = "Whether to enable VNC listener."; + default = false; + type = bool; + }; + listen = mkOption { + description = "Listen endpoint of VNC listener."; + default = "[::]:5900"; + type = str; + }; + proxy_protocol = mkOption { + description = "Accept HAProxy PROXY protocol v1/v2 headers from the listener's peer."; + default = false; + type = bool; + }; + external_host = mkOption { + description = "The VNC listener is reachable via this domain name externally."; + default = null; + type = nullOr str; + }; + external_port = mkOption { + description = "The VNC listener is reachable via this port externally."; + default = null; + type = nullOr str; + }; + certificate = mkOption { + description = "Path to VNC listener certificate."; + default = "/var/lib/warpgate/tls.certificate.pem"; + type = str; + }; + key = mkOption { + description = "Path to VNC listener private key."; + default = "/var/lib/warpgate/tls.key.pem"; + type = str; + }; + enable_ard_auth = mkOption { + description = '' + Enable Apple-DH (Apple Remote Desktop / type 30) auth, which is to ensure compatibility with Apple clients. + However [connections from macOS built-in VNC client with ARD auth is not supported](https://github.com/warp-tech/warpgate/blob/47e676969a0b1e0b8456f9a5f1474d6c58648c4f/warpgate-protocol-vnc/src/server/rfb.rs#L8-L10). + ''; + default = false; + type = bool; + }; + }; mysql = { enable = mkOption { description = "Whether to enable MySQL listener."; @@ -281,6 +372,11 @@ in default = "[::]:33306"; type = str; }; + proxy_protocol = mkOption { + description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy."; + default = false; + type = bool; + }; external_host = mkOption { description = "The MySQL listener is reachable via this domain name externally."; default = null; @@ -301,6 +397,14 @@ in default = "/var/lib/warpgate/tls.key.pem"; type = str; }; + advertised_version = mkOption { + description = '' + The server version advertised to clients during the handshake. + Warpgate can't auto-match the target's version since the target is only known after the handshake, but Warpgate's clients use it to pick a protocol dialect. + ''; + default = "8.0.3-Warpgate"; + type = str; + }; }; postgres = { enable = mkOption { @@ -313,6 +417,11 @@ in default = "[::]:55432"; type = str; }; + proxy_protocol = mkOption { + description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy."; + default = false; + type = bool; + }; external_host = mkOption { description = "The PostgreSQL listener is reachable via this domain name externally."; default = null; @@ -345,6 +454,11 @@ in default = "[::]:8443"; type = str; }; + proxy_protocol = mkOption { + description = "Accept HAProxy PROXY protocol v1/v2 headers from upstream reverse proxy."; + default = false; + type = bool; + }; external_host = mkOption { description = "The Kubernetes listener is reachable via this domain name externally."; default = null; @@ -420,36 +534,45 @@ in any map head + optional reverseList ; - inherit (lib.strings) splitString toIntBase10; + inherit (lib.strings) + optionalString + splitString + toIntBase10 + ; - preStartScript = pkgs.writers.writeBash "warpgate-init" '' - CFGFILE=/var/lib/warpgate/config.yaml + renderedYamlConfig = yaml.generate "warpgate-config" cfg.settings; + + startupScript = pkgs.writeShellScript "warpgate-run" '' + CFGFILE=$STATE_DIRECTORY/config.yaml if [ ! -O $CFGFILE ] || [ ! -s $CFGFILE ]; then INITPWD=$(tr -dc 'A-Za-z0-9!?%=' /dev/null | head -c 16) ${lib.getExe cfg.package} \ --config $CFGFILE unattended-setup \ - --data-path /var/lib/warpgate \ + --data-path $STATE_DIRECTORY \ --http-port 8888 \ --admin-password $INITPWD fi - ${ - if cfg.databaseUrlFile != null then - '' - sed -e '/^database_url: null/d' ${yaml.generate "warpgate-config" cfg.settings} > $CFGFILE - cat /run/credentials/warpgate.service/databaseUrl >> $CFGFILE - '' - else - "cp --no-preserve=ownership ${yaml.generate "warpgate-config" cfg.settings} $CFGFILE" - } + cp --no-preserve=ownership ${renderedYamlConfig} $CFGFILE + ${optionalString (cfg.databaseUrlFile != null) '' + sed -e '/^database_url: null/d' ${renderedYamlConfig} > $CFGFILE + cat $CREDENTIALS_DIRECTORY/databaseUrl >> $CFGFILE + ''} + ${optionalString (cfg.databaseEncryptionKeysFile != null) '' + set -a + source $CREDENTIALS_DIRECTORY/dbEncryptionKeys + set +a + ''} + ${lib.getExe cfg.package} --config $CFGFILE run ''; bindOnPrivilegedPorts = any (x: toIntBase10 x < 1025) ( map (x: head (reverseList (splitString ":" x))) ( [ cfg.settings.http.listen ] - ++ lib.optional cfg.settings.ssh.enable cfg.settings.ssh.listen - ++ lib.optional cfg.settings.mysql.enable cfg.settings.mysql.listen - ++ lib.optional cfg.settings.postgres.enable cfg.settings.postgres.listen + ++ optional cfg.settings.ssh.enable cfg.settings.ssh.listen + ++ optional cfg.settings.mysql.enable cfg.settings.mysql.listen + ++ optional cfg.settings.postgres.enable cfg.settings.postgres.listen ) ); in @@ -467,6 +590,10 @@ in assertion = !(lib.hasAttr "config_provider" cfg.settings); message = "`services.warpgate.settings.config_provider` is a legacy option that has been removed since 0.14.0. Please do not set this option."; } + { + assertion = !(lib.hasAttr "recordings" cfg.settings); + message = "`services.warpgate.settings.recordings` has been deprecated by S3 recording storage support in 0.27.0. Please remove this section from your config and set it from admin UI."; + } ]; environment.systemPackages = [ cfg.package ]; @@ -474,14 +601,16 @@ in systemd.services.warpgate = { description = "Warpgate smart bastion"; wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; startLimitBurst = 5; serviceConfig = { - LoadCredential = "${ - if cfg.databaseUrlFile != null then "databaseUrl:${cfg.databaseUrlFile}" else "" - }"; - ExecStartPre = preStartScript; - ExecStart = "${lib.getExe cfg.package} --config /var/lib/warpgate/config.yaml run"; + LoadCredential = + optional (cfg.databaseUrlFile != null) "databaseUrl:${cfg.databaseUrlFile}" + ++ optional ( + cfg.databaseEncryptionKeysFile != null + ) "dbEncryptionKeys:${cfg.databaseEncryptionKeysFile}"; + ExecStart = startupScript; DynamicUser = true; RestartSec = 3; Restart = "on-failure"; diff --git a/nixos/tests/warpgate.nix b/nixos/tests/warpgate.nix index 64ec9bd6dc22..518fcbcd7733 100644 --- a/nixos/tests/warpgate.nix +++ b/nixos/tests/warpgate.nix @@ -1,3 +1,4 @@ +{ pkgs, ... }: { name = "warpgate"; @@ -9,14 +10,29 @@ }; machine2 = { - environment.etc."warpgate-db-url".text = "database: sqlite:/var/lib/warpgate/db/"; + environment.etc."warpgate-db-url".text = + "database_url: postgresql://warpgate:warpgate@localhost:5432/warpgate"; + environment.etc."warpgate-db-enc".text = + "WARPGATE_ENCRYPTION_KEY=QVJBTkRPTTMyQ0hBUkFDVEVSU0VOQ1JZUFRJT05LRVk="; services.warpgate = { enable = true; databaseUrlFile = "/etc/warpgate-db-url"; + databaseEncryptionKeysFile = "/etc/warpgate-db-enc"; settings = { database_url = null; }; }; + services.postgresql = { + enable = true; + initialScript = pkgs.writeText "psql-init" '' + CREATE ROLE warpgate WITH LOGIN PASSWORD 'warpgate'; + CREATE DATABASE warpgate WITH OWNER warpgate; + ''; + }; + systemd.services.warpgate = { + after = [ "postgresql.target" ]; + requires = [ "postgresql.target" ]; + }; }; machine3 = { @@ -24,6 +40,12 @@ enable = true; settings = { http.listen = "[::]:443"; + ssh.enable = true; + rdp.enable = true; + vnc.enable = true; + mysql.enable = true; + postgres.enable = true; + kubernetes.enable = true; }; }; }; @@ -43,6 +65,12 @@ machine3.wait_for_unit("warpgate.service") machine3.wait_for_open_port(443) + machine3.wait_for_open_port(2222) + machine3.wait_for_open_port(3389) + machine3.wait_for_open_port(5900) + machine3.wait_for_open_port(33306) + machine3.wait_for_open_port(55432) + machine3.wait_for_open_port(8443) machine3.succeed("curl -k --fail https://localhost/@warpgate") machine3.shutdown() ''; diff --git a/pkgs/by-name/wa/warpgate/hardcode-version.patch b/pkgs/by-name/wa/warpgate/hardcode-version.patch index 359c7bd04c10..5481d48a2d1a 100644 --- a/pkgs/by-name/wa/warpgate/hardcode-version.patch +++ b/pkgs/by-name/wa/warpgate/hardcode-version.patch @@ -1,20 +1,12 @@ diff --git a/warpgate-common/src/version.rs b/warpgate-common/src/version.rs -index 0e7985a..62c2b67 100644 +index 31104706..ec201419 100644 --- a/warpgate-common/src/version.rs +++ b/warpgate-common/src/version.rs -@@ -1,14 +1,3 @@ --use git_version::git_version; -- - pub const fn warpgate_version() -> &'static str { -- git_version!( -- args = [ -- "--tags", -- "--always", -- "--dirty=-modified", -- "--match", -- "v[0-9]*" -- ], +@@ -9,6 +9,6 @@ pub const fn warpgate_version() -> &'static str { + "--match", + "v[0-9]*" + ], - fallback = "unknown" -- ) -+ "v@version@" ++ fallback = "v@version@" + ) } diff --git a/pkgs/by-name/wa/warpgate/package.nix b/pkgs/by-name/wa/warpgate/package.nix index e9136ea3cdfc..b61d13c757d3 100644 --- a/pkgs/by-name/wa/warpgate/package.nix +++ b/pkgs/by-name/wa/warpgate/package.nix @@ -7,20 +7,24 @@ openapi-generator-cli, nixosTests, nix-update-script, + perl, + withRDPLegacyTLSBackend ? false, }: rustPlatform.buildRustPackage ( finalAttrs: let - warpgate-web = buildNpmPackage { - pname = "${finalAttrs.pname}-web"; + webUi = buildNpmPackage { + pname = "warpgate-web"; version = finalAttrs.version; src = finalAttrs.src; sourceRoot = "${finalAttrs.src.name}/warpgate-web"; - patches = [ ./web-ui-package-json.patch ]; + patches = [ + ./web-ui-package-json.patch + ]; - npmDepsHash = "sha256-McQI5EmTfrbdcWnYRsoRHjhZphrZVaV/fN9i9MX8XF0="; + npmDepsHash = "sha256-BfmYRfsxdJZuS/c7bGccXXYktsjQ76mjwTFKLvNsGAg="; nativeBuildInputs = [ openapi-generator-cli ]; @@ -35,45 +39,51 @@ rustPlatform.buildRustPackage ( in { pname = "warpgate"; - version = "0.26.1"; + version = "0.28.4"; src = fetchFromGitHub { owner = "warp-tech"; repo = "warpgate"; tag = "v${finalAttrs.version}"; - hash = "sha256-1Dg7bzhBQNe+u90Tw+kcmVaxV5IK0/t505HZr18qP5I="; + hash = "sha256-BWfkStxPi4LucoADK1YwRZaQwObPtq08eEVK9XG7vfU="; }; - cargoHash = "sha256-A5rRLrqlAZV/3ID8F+wUO8OP3Ocivg7vYrNDiMqRKik="; + cargoHash = "sha256-TVNOCMmL8ICtQImA39jhlfCnghvV90NlRBdSol2MGtY="; patches = [ (replaceVars ./hardcode-version.patch { inherit (finalAttrs) version; }) - ./remove-nightly-rustflags.patch ]; - env.RUSTFLAGS = "--cfg tokio_unstable"; + env = { + # uses nightly feature: gethostname, once_cell_try + RUSTC_BOOTSTRAP = true; + RUSTFLAGS = "--cfg tokio_unstable"; + }; + + nativeBuildInputs = lib.optional withRDPLegacyTLSBackend perl; buildFeatures = [ "postgres" "mysql" "sqlite" - ]; + ] + ++ lib.optional withRDPLegacyTLSBackend "rdp-openssl-tls"; preBuild = '' - rm -r .cargo/ - ln -rs "${warpgate-web}" warpgate-web/dist + rm -rf .cargo/ + ln -rs "${webUi}" warpgate-web/dist ''; # skip check, project included tests require python stuff and docker doCheck = false; passthru = { - inherit warpgate-web; + inherit webUi; tests = { inherit (nixosTests) warpgate; }; updateScript = nix-update-script { - extraArgs = [ "--subpackage=warpgate-web" ]; + extraArgs = [ "--subpackage=webUi" ]; }; }; diff --git a/pkgs/by-name/wa/warpgate/remove-nightly-rustflags.patch b/pkgs/by-name/wa/warpgate/remove-nightly-rustflags.patch deleted file mode 100644 index caa2ceba247d..000000000000 --- a/pkgs/by-name/wa/warpgate/remove-nightly-rustflags.patch +++ /dev/null @@ -1,31 +0,0 @@ -diff --git a/Cargo.toml b/Cargo.toml -index 0e92acb..d187ebc 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -1,5 +1,3 @@ --cargo-features = ["profile-rustflags"] -- - [workspace] - members = [ - "warpgate", -@@ -160,20 +158,2 @@ - [profile.coverage] - inherits = "dev" -- --[profile.dev.package.aws-sdk-ec2] --hint-mostly-unused = true -- --[profile.release.package.aws-sdk-ec2] --hint-mostly-unused = true -- --[profile.dev.package.aws-sdk-rds] --hint-mostly-unused = true -- --[profile.release.package.aws-sdk-rds] --hint-mostly-unused = true -- --[profile.dev.package.aws-sdk-eks] --hint-mostly-unused = true -- --[profile.release.package.aws-sdk-eks] --hint-mostly-unused = true diff --git a/pkgs/by-name/wa/warpgate/web-ui-package-json.patch b/pkgs/by-name/wa/warpgate/web-ui-package-json.patch index 973c19a97f8b..62e56c10bf6e 100644 --- a/pkgs/by-name/wa/warpgate/web-ui-package-json.patch +++ b/pkgs/by-name/wa/warpgate/web-ui-package-json.patch @@ -1,15 +1,15 @@ diff --git a/package.json b/package.json -index 0f1d768..c070a59 100644 +index d8734a74..513d5606 100644 --- a/package.json +++ b/package.json -@@ -12,8 +12,8 @@ - "postinstall": "npm run openapi:client:gateway && npm run openapi:client:admin", - "openapi:schema:gateway": "cargo run -p warpgate-protocol-http > src/gateway/lib/openapi-schema.json", - "openapi:schema:admin": "cargo run -p warpgate-admin > src/admin/lib/openapi-schema.json", -- "openapi:client:gateway": "openapi-generator-cli generate -g typescript-fetch -i src/gateway/lib/openapi-schema.json -o src/gateway/lib/api-client -p npmName=warpgate-gateway-api-client -p useSingleRequestParameter=true && cd src/gateway/lib/api-client && npm i typescript@5 && npm i && npx tsc --target esnext --module esnext && rm -rf src tsconfig.json", -- "openapi:client:admin": "openapi-generator-cli generate -g typescript-fetch -i src/admin/lib/openapi-schema.json -o src/admin/lib/api-client -p npmName=warpgate-admin-api-client -p useSingleRequestParameter=true && cd src/admin/lib/api-client && npm i typescript@5 && npm i && npx tsc --target esnext --module esnext && rm -rf src tsconfig.json", -+ "openapi:client:gateway": "openapi-generator-cli generate -g typescript-fetch -i src/gateway/lib/openapi-schema.json -o src/gateway/lib/api-client -p npmName=warpgate-gateway-api-client -p useSingleRequestParameter=true && ln -sr node_modules src/gateway/lib/api-client/node_modules && cd src/gateway/lib/api-client && npx tsc --target esnext --moduleResolution node && npx tsc -p tsconfig.esm.json --target esnext --moduleResolution node && rm -rf src tsconfig.json tsconfig.esm.json", -+ "openapi:client:admin": "openapi-generator-cli generate -g typescript-fetch -i src/admin/lib/openapi-schema.json -o src/admin/lib/api-client -p npmName=warpgate-admin-api-client -p useSingleRequestParameter=true && ln -sr node_modules src/admin/lib/api-client/node_modules && cd src/admin/lib/api-client && npx tsc --target esnext --moduleResolution node && npx tsc -p tsconfig.esm.json --target esnext --moduleResolution node && rm -rf src tsconfig.json tsconfig.esm.json", - "openapi:tests-sdk": "openapi-generator-cli generate -g python -i src/admin/lib/openapi-schema.json -o ../tests/api_sdk", - "openapi": "npm run openapi:schema:admin && npm run openapi:schema:gateway && npm run openapi:client:admin && npm run openapi:client:gateway" - }, +@@ -16,8 +16,8 @@ + "postinstall": "npm run openapi:client:gateway && npm run openapi:client:admin", + "openapi:schema:gateway": "cargo run -p warpgate-protocol-http > src/gateway/lib/openapi-schema.json", + "openapi:schema:admin": "cargo run -p warpgate-admin > src/admin/lib/openapi-schema.json", +- "openapi:client:gateway": "openapi-generator-cli generate -g typescript-fetch -i src/gateway/lib/openapi-schema.json -o src/gateway/lib/api-client -p npmName=warpgate-gateway-api-client -p useSingleRequestParameter=true && cd src/gateway/lib/api-client && npm i typescript@5 && npm i && npx tsc --target esnext --module esnext && rm -rf src tsconfig.json", +- "openapi:client:admin": "openapi-generator-cli generate -g typescript-fetch -i src/admin/lib/openapi-schema.json -o src/admin/lib/api-client -p npmName=warpgate-admin-api-client -p useSingleRequestParameter=true && cd src/admin/lib/api-client && npm i typescript@5 && npm i && npx tsc --target esnext --module esnext && rm -rf src tsconfig.json", ++ "openapi:client:gateway": "openapi-generator-cli generate -g typescript-fetch -i src/gateway/lib/openapi-schema.json -o src/gateway/lib/api-client -p npmName=warpgate-gateway-api-client -p useSingleRequestParameter=true && ln -sr node_modules src/gateway/lib/api-client/node_modules && cd src/gateway/lib/api-client && npx tsc --target esnext --moduleResolution node && npx tsc -p tsconfig.esm.json --target esnext --moduleResolution node && rm -rf src tsconfig.json tsconfig.esm.json", ++ "openapi:client:admin": "openapi-generator-cli generate -g typescript-fetch -i src/admin/lib/openapi-schema.json -o src/admin/lib/api-client -p npmName=warpgate-admin-api-client -p useSingleRequestParameter=true && ln -sr node_modules src/admin/lib/api-client/node_modules && cd src/admin/lib/api-client && npx tsc --target esnext --moduleResolution node && npx tsc -p tsconfig.esm.json --target esnext --moduleResolution node && rm -rf src tsconfig.json tsconfig.esm.json", + "openapi:tests-sdk": "openapi-generator-cli generate -g python -i src/admin/lib/openapi-schema.json -o ../tests/api_sdk", + "openapi": "npm run openapi:schema:admin && npm run openapi:schema:gateway && npm run openapi:client:admin && npm run openapi:client:gateway" + },