From 7592bda57af612ffc2f4660ecca38d09aab3a50d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:54:08 +0000 Subject: [PATCH] fix: apply hub-managed GRD credentials at boot --- app/tests/test_rdp_module_boot_setup.py | 23 +++++++++++++--- modules/rdp.nix | 36 +++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/app/tests/test_rdp_module_boot_setup.py b/app/tests/test_rdp_module_boot_setup.py index bbd1f9c..8cb8c8b 100644 --- a/app/tests/test_rdp_module_boot_setup.py +++ b/app/tests/test_rdp_module_boot_setup.py @@ -3,6 +3,9 @@ from pathlib import Path RDP_NIX = Path(__file__).resolve().parents[2] / "modules" / "rdp.nix" +USERNAME_READ = "USERNAME=\"$(tr -d '\\n' < \"$USERNAME_FILE\")\"" +USERNAME_LENGTH_GUARD = "if [ \"''${#USERNAME}\" -gt 32 ]; then" +SHORT_PASSWORD_GUARD = 'if [ "\'\'${#PASSWORD}" -lt 8 ]; then' def _section(source: str, start: str, end: str) -> str: @@ -45,7 +48,9 @@ class RdpModuleBootSetupTests(unittest.TestCase): self.assertIn('"gnome-remote-desktop-configuration.service"', self.setup_service) self.assertNotIn("RemainAfterExit", self.setup_service) self.assertIn('TimeoutStartSec = "2min";', self.setup_service) - self.assertIn('timeout --kill-after=5s 15s', self.setup_service) + self.assertIn('timeout --kill-after=5s 10s', self.setup_service) + self.assertIn('echo "grdctl command timed out: $*" >&2', self.setup_service) + self.assertIn('echo "grdctl command failed (exit $rc): $*" >&2', self.setup_service) def test_setup_runs_grdctl_as_gnome_remote_desktop_user(self): self.assertIn("runuser -u gnome-remote-desktop -- \\", self.setup_service) @@ -54,20 +59,30 @@ class RdpModuleBootSetupTests(unittest.TestCase): 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) - self.assertIn("USERNAME=\"$(tr -d '\\n' < \"$USERNAME_FILE\")\"", self.setup_service) + self.assertIn(USERNAME_READ, self.setup_service) + self.assertIn(USERNAME_LENGTH_GUARD, self.setup_service) + self.assertIn('case "$USERNAME" in', self.setup_service) + self.assertIn('[A-Za-z_][A-Za-z0-9_-]*)', self.setup_service) + self.assertIn("RDP username is too long (''${#USERNAME} characters, maximum 32)", self.setup_service) + self.assertIn("RDP username must start with a letter or underscore and contain only letters, numbers, underscores, and hyphens", self.setup_service) self.assertIn('if [ ! -f "$PASSWORD_FILE" ]; then', self.setup_service) - self.assertIn('PASSWORD="$(tr -d \'\\n\' < "$PASSWORD_FILE")"', self.setup_service) + self.assertIn("tr -d '\\n'", self.setup_service) + self.assertIn('"$PASSWORD_FILE"', self.setup_service) + self.assertIn(SHORT_PASSWORD_GUARD, self.setup_service) + self.assertIn("RDP password is too short (''${#PASSWORD} characters, minimum 8)", self.setup_service) self.assertIn('grdctl_system rdp set-credentials "$USERNAME" "$PASSWORD"', self.setup_service) self.assertNotIn('grdctl --system rdp set-credentials sovran "$PASSWORD"', self.setup_service) def test_secure_permissions_are_enforced_for_state_and_secret_files(self): self.assertIn('"d /var/lib/gnome-remote-desktop/tls 0700', self.source) - self.assertIn('chmod 700 \\', self.setup_service) + self.assertIn("chmod 700", self.setup_service) self.assertIn('chmod 600 "$USERNAME_FILE"', self.setup_service) self.assertIn('chmod 600 "$PASSWORD_FILE"', self.setup_service) self.assertIn('chmod 600 "$CRED_FILE"', self.setup_service) self.assertIn('chmod 600 "$TLS_DIR/rdp-tls.key"', self.setup_service) self.assertIn('chmod 644 "$TLS_DIR/rdp-tls.crt"', self.setup_service) + self.assertIn('LOCAL_IP="$(hostname -I | awk \'{print $1}\')"', self.setup_service) + self.assertIn('LOCAL_IP="127.0.0.1"', self.setup_service) if __name__ == "__main__": diff --git a/modules/rdp.nix b/modules/rdp.nix index 647d913..2499491 100644 --- a/modules/rdp.nix +++ b/modules/rdp.nix @@ -61,9 +61,22 @@ lib.mkIf config.sovran_systemsOS.features.rdp { DEFAULT_USERNAME="sovran" grdctl_system() { - timeout --kill-after=5s 15s \ + local rc=0 + + if timeout --kill-after=5s 10s \ runuser -u gnome-remote-desktop -- \ - grdctl --system "$@" + grdctl --system "$@"; then + return 0 + else + rc=$? + fi + + if [ "$rc" -eq 124 ] || [ "$rc" -eq 137 ]; then + echo "grdctl command timed out: $*" >&2 + fi + echo "grdctl command failed (exit $rc): $*" >&2 + + return "$rc" } mkdir -p "$STATE_DIR/.local/share/gnome-remote-desktop" "$TLS_DIR" @@ -104,6 +117,18 @@ lib.mkIf config.sovran_systemsOS.features.rdp { USERNAME="$DEFAULT_USERNAME" printf '%s\n' "$USERNAME" > "$USERNAME_FILE" fi + if [ "''${#USERNAME}" -gt 32 ]; then + echo "RDP username is too long (''${#USERNAME} characters, maximum 32): $USERNAME from $USERNAME_FILE" >&2 + exit 1 + fi + case "$USERNAME" in + [A-Za-z_][A-Za-z0-9_-]*) + ;; + *) + echo "RDP username must start with a letter or underscore and contain only letters, numbers, underscores, and hyphens: $USERNAME from $USERNAME_FILE" >&2 + exit 1 + ;; + esac chown gnome-remote-desktop:gnome-remote-desktop "$USERNAME_FILE" chmod 600 "$USERNAME_FILE" @@ -115,10 +140,17 @@ lib.mkIf config.sovran_systemsOS.features.rdp { echo "RDP password file is empty: $PASSWORD_FILE" >&2 exit 1 fi + if [ "''${#PASSWORD}" -lt 8 ]; then + echo "RDP password is too short (''${#PASSWORD} characters, minimum 8): $PASSWORD_FILE" >&2 + exit 1 + fi chown gnome-remote-desktop:gnome-remote-desktop "$PASSWORD_FILE" chmod 600 "$PASSWORD_FILE" LOCAL_IP="$(hostname -I | awk '{print $1}')" + if [ -z "$LOCAL_IP" ]; then + LOCAL_IP="127.0.0.1" + fi cat > "$CRED_FILE" <