From 69e996ff987913119e53a624bc005734e7effc2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:54:40 +0000 Subject: [PATCH] fix(rdp): prepend /run/wrappers/bin to PATH and add pkexec preflight check GRD 50.x invokes pkexec internally for every grdctl --system call, even when the caller is root. An isolated systemd script PATH does not include /run/wrappers/bin automatically, causing exit 70 at boot. Changes: - Prepend /run/wrappers/bin to PATH at script start so every subsequent grdctl --system resolves the NixOS setuid pkexec wrapper. - Add an explicit preflight check (test -x /run/wrappers/bin/pkexec) with a clear error message before the first grdctl_system call. - pkgs.polkit remains absent from the service path. - Update test_setup_runs_grdctl_directly_as_root to reflect that pkexec now appears in the preflight check (not as a direct invocation). - Add test_run_wrappers_bin_prepended_to_path and test_pkexec_preflight_check. --- app/tests/test_rdp_module_boot_setup.py | 31 ++++++++++++++++++++++--- modules/rdp.nix | 16 +++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/app/tests/test_rdp_module_boot_setup.py b/app/tests/test_rdp_module_boot_setup.py index c070034..4948b47 100644 --- a/app/tests/test_rdp_module_boot_setup.py +++ b/app/tests/test_rdp_module_boot_setup.py @@ -53,17 +53,42 @@ class RdpModuleBootSetupTests(unittest.TestCase): self.assertIn('echo "grdctl command failed (exit $rc): $*" >&2', self.setup_service) def test_setup_runs_grdctl_directly_as_root(self): - # The oneshot service already runs as root; system-mode grdctl must be - # invoked directly so it does not attempt pkexec authorization. + # The oneshot service runs as root; grdctl --system is called directly. + # GRD 50.x invokes pkexec internally, but the call itself is plain + # grdctl --system, not a manual pkexec invocation. self.assertIn('grdctl --system "$@"', self.setup_service) self.assertNotIn("runuser", self.setup_service) - self.assertNotIn("pkexec", self.setup_service) self.assertNotIn("sudo", self.setup_service) + # No direct Nix-store pkexec invocation (pkgs.polkit}/bin/pkexec). + self.assertNotIn("pkgs.polkit}/bin/pkexec", self.setup_service) def test_privilege_escalation_packages_absent_from_setup_path(self): self.assertNotIn("pkgs.polkit", self.setup_service) self.assertNotIn("pkgs.util-linux", self.setup_service) + def test_run_wrappers_bin_prepended_to_path(self): + # /run/wrappers/bin must be prepended to PATH before any grdctl_system + # invocation so that grdctl --system resolves the NixOS setuid pkexec. + path_export = 'export PATH="/run/wrappers/bin:$PATH"' + grdctl_marker = "grdctl_system" + script = self.setup_service + path_idx = script.find(path_export) + grdctl_idx = script.find(grdctl_marker) + self.assertGreater(path_idx, -1, f"{path_export!r} not found in setup script") + self.assertGreater( + grdctl_idx, path_idx, + "PATH export must appear before the first grdctl_system usage", + ) + + def test_pkexec_preflight_check(self): + # A preflight must confirm /run/wrappers/bin/pkexec is executable + # with a clear error message before any GRD configuration changes. + self.assertIn("test -x /run/wrappers/bin/pkexec", self.setup_service) + self.assertIn( + "/run/wrappers/bin/pkexec is absent or not executable", + self.setup_service, + ) + def test_hub_files_are_the_source_of_truth_for_username_and_password(self): self.assertIn('DEFAULT_USERNAME="sovran"', self.setup_service) self.assertIn('if [ ! -f "$USERNAME_FILE" ]; then', self.setup_service) diff --git a/modules/rdp.nix b/modules/rdp.nix index c658631..e162b94 100644 --- a/modules/rdp.nix +++ b/modules/rdp.nix @@ -51,6 +51,13 @@ lib.mkIf config.sovran_systemsOS.features.rdp { script = '' set -euo pipefail + # GRD 50.x invokes pkexec internally for every grdctl --system call, even + # when the caller is root. NixOS exposes the required setuid wrapper at + # /run/wrappers/bin/pkexec; the Nix-store polkit binary is not setuid and + # must not shadow it. Prepend the wrapper directory so every subsequent + # grdctl --system resolves the correct binary. + export PATH="/run/wrappers/bin:$PATH" + STATE_DIR="/var/lib/gnome-remote-desktop" TLS_DIR="$STATE_DIR/tls" USERNAME_FILE="$STATE_DIR/rdp-username" @@ -166,6 +173,15 @@ lib.mkIf config.sovran_systemsOS.features.rdp { chown gnome-remote-desktop:gnome-remote-desktop "$CRED_FILE" chmod 600 "$CRED_FILE" + # Preflight: the NixOS setuid pkexec wrapper must be present and executable + # before any grdctl --system call. Absence means the system was booted + # without security.wrappers or the wrapper directory is not mounted yet. + if ! test -x /run/wrappers/bin/pkexec; then + echo "Preflight check failed: /run/wrappers/bin/pkexec is absent or not executable." >&2 + echo "GNOME Remote Desktop system configuration requires the NixOS setuid pkexec wrapper at /run/wrappers/bin/pkexec." >&2 + exit 1 + fi + grdctl_system rdp enable grdctl_system rdp set-tls-cert "$TLS_DIR/rdp-tls.crt" grdctl_system rdp set-tls-key "$TLS_DIR/rdp-tls.key"