fix: tighten GRD boot credential setup

This commit is contained in:
copilot-swe-agent[bot]
2026-07-13 23:48:12 +00:00
committed by GitHub
parent 4a2c3a8eb4
commit 47f496efcf
2 changed files with 149 additions and 55 deletions
+74
View File
@@ -0,0 +1,74 @@
import unittest
from pathlib import Path
RDP_NIX = Path(__file__).resolve().parents[2] / "modules" / "rdp.nix"
def _section(source: str, start: str, end: str) -> str:
start_idx = source.find(start)
if start_idx == -1:
raise AssertionError(f"Expected section start not found: {start!r}")
end_idx = source.find(end, start_idx)
if end_idx == -1:
raise AssertionError(f"Expected section end not found: {end!r}")
return source[start_idx:end_idx]
class RdpModuleBootSetupTests(unittest.TestCase):
def setUp(self):
self.source = RDP_NIX.read_text()
self.gnome_service = _section(
self.source,
"systemd.services.gnome-remote-desktop = {",
"systemd.tmpfiles.rules = [",
)
self.setup_service = _section(
self.source,
"systemd.services.gnome-remote-desktop-setup = {",
"};\n}",
)
def test_does_not_redeclare_gnome_remote_desktop_user(self):
self.assertNotIn("users.users.gnome-remote-desktop", self.source)
self.assertNotIn("createHome = true;", self.source)
def test_main_service_requires_setup_before_starting(self):
self.assertIn('wantedBy = [ "graphical.target" ];', self.gnome_service)
self.assertIn('after = [ "gnome-remote-desktop-setup.service" ];', self.gnome_service)
self.assertIn('requires = [ "gnome-remote-desktop-setup.service" ];', self.gnome_service)
def test_setup_waits_for_configuration_service_and_bounded_timeout(self):
self.assertIn('wantedBy = [ "graphical.target" ];', self.setup_service)
self.assertIn('before = [ "gnome-remote-desktop.service" ];', self.setup_service)
self.assertIn('"dbus.service"', self.setup_service)
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)
def test_setup_runs_grdctl_as_gnome_remote_desktop_user(self):
self.assertIn("runuser -u gnome-remote-desktop -- \\", self.setup_service)
self.assertIn('grdctl --system "$@"', 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)
self.assertIn("USERNAME=\"$(tr -d '\\n' < \"$USERNAME_FILE\")\"", 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('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 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)
if __name__ == "__main__":
unittest.main()
Executable → Regular
+75 -55
View File
@@ -2,70 +2,87 @@
lib.mkIf config.sovran_systemsOS.features.rdp {
users.users.gnome-remote-desktop = {
isSystemUser = true;
group = "gnome-remote-desktop";
home = "/var/lib/gnome-remote-desktop";
createHome = true;
};
users.groups.gnome-remote-desktop = {};
# Enable the GNOME Remote Desktop service at the system level
services.gnome.gnome-remote-desktop.enable = true;
# Open RDP port in the firewall
networking.firewall.allowedTCPPorts = [ 3389 ];
# Ensure the service actually starts and waits for setup to complete
# Ensure the service only starts after setup succeeds
systemd.services.gnome-remote-desktop = {
wantedBy = [ "graphical.target" ];
after = [ "gnome-remote-desktop-setup.service" ];
wants = [ "gnome-remote-desktop-setup.service" ];
requires = [ "gnome-remote-desktop-setup.service" ];
};
systemd.tmpfiles.rules = [
"d /var/lib/gnome-remote-desktop 0750 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local 0750 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local/share 0750 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop 0750 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local 0700 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local/share 0700 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop 0700 gnome-remote-desktop gnome-remote-desktop -"
"d /var/lib/gnome-remote-desktop/tls 0700 gnome-remote-desktop gnome-remote-desktop -"
];
systemd.services.gnome-remote-desktop-setup = {
description = "Configure GNOME Remote Desktop RDP";
wantedBy = [ "multi-user.target" ];
wantedBy = [ "graphical.target" ];
before = [ "gnome-remote-desktop.service" ];
after = [ "systemd-tmpfiles-setup.service" "network-online.target" ];
wants = [ "network-online.target" ];
after = [
"dbus.service"
"systemd-tmpfiles-setup.service"
"network-online.target"
"gnome-remote-desktop-configuration.service"
];
wants = [
"network-online.target"
"gnome-remote-desktop-configuration.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
TimeoutStartSec = "2min";
};
path = [
pkgs.gnome-remote-desktop
pkgs.polkit
pkgs.openssl
pkgs.hostname
pkgs.coreutils
pkgs.gawk
pkgs.gnome-remote-desktop
pkgs.hostname
pkgs.openssl
pkgs.polkit
pkgs.systemd
pkgs.util-linux
];
script = ''
# Ensure directory structure exists
mkdir -p /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop
chown -R gnome-remote-desktop:gnome-remote-desktop /var/lib/gnome-remote-desktop
set -euo pipefail
TLS_DIR="/var/lib/gnome-remote-desktop/tls"
CRED_FILE="/var/lib/gnome-remote-desktop/rdp-credentials"
STATE_DIR="/var/lib/gnome-remote-desktop"
TLS_DIR="$STATE_DIR/tls"
USERNAME_FILE="$STATE_DIR/rdp-username"
PASSWORD_FILE="$STATE_DIR/rdp-password"
CRED_FILE="$STATE_DIR/rdp-credentials"
DEFAULT_USERNAME="sovran"
grdctl_system() {
timeout --kill-after=5s 15s \
runuser -u gnome-remote-desktop -- \
grdctl --system "$@"
}
mkdir -p "$STATE_DIR/.local/share/gnome-remote-desktop" "$TLS_DIR"
chown -R gnome-remote-desktop:gnome-remote-desktop "$STATE_DIR"
chmod 700 \
"$STATE_DIR" \
"$STATE_DIR/.local" \
"$STATE_DIR/.local/share" \
"$STATE_DIR/.local/share/gnome-remote-desktop" \
"$TLS_DIR"
# Regenerate TLS certificate if missing OR if ownership is wrong
# (disable/re-enable cycle can break ownership or grdctl state)
NEED_REGEN=0
if [ ! -f "$TLS_DIR/rdp-tls.crt" ] || [ ! -f "$TLS_DIR/rdp-tls.key" ]; then
NEED_REGEN=1
elif [ "$(stat -c '%U' "$TLS_DIR/rdp-tls.key" 2>/dev/null)" != "gnome-remote-desktop" ]; then
elif [ "$(stat -c '%U:%G' "$TLS_DIR/rdp-tls.key" 2>/dev/null)" != "gnome-remote-desktop:gnome-remote-desktop" ]; then
NEED_REGEN=1
fi
if [ "$NEED_REGEN" = "1" ]; then
mkdir -p "$TLS_DIR"
rm -f "$TLS_DIR/rdp-tls.key" "$TLS_DIR/rdp-tls.crt"
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-sha256 -nodes -days 3650 \
@@ -75,39 +92,40 @@ lib.mkIf config.sovran_systemsOS.features.rdp {
echo "Generated new RDP TLS certificate"
fi
# Always fix ownership and permissions (handles re-enable after disable)
chown -R gnome-remote-desktop:gnome-remote-desktop "$TLS_DIR"
chown gnome-remote-desktop:gnome-remote-desktop "$TLS_DIR/rdp-tls.key" "$TLS_DIR/rdp-tls.crt"
chmod 600 "$TLS_DIR/rdp-tls.key"
chmod 644 "$TLS_DIR/rdp-tls.crt"
# Configure TLS certificate
grdctl --system rdp set-tls-cert "$TLS_DIR/rdp-tls.crt"
grdctl --system rdp set-tls-key "$TLS_DIR/rdp-tls.key"
# Generate password on first boot only
PASSWORD=""
if [ ! -f /var/lib/gnome-remote-desktop/rdp-password ]; then
PASSWORD=$(openssl rand -base64 16)
echo "$PASSWORD" > /var/lib/gnome-remote-desktop/rdp-password
chmod 600 /var/lib/gnome-remote-desktop/rdp-password
else
PASSWORD=$(cat /var/lib/gnome-remote-desktop/rdp-password)
if [ ! -f "$USERNAME_FILE" ]; then
printf '%s\n' "$DEFAULT_USERNAME" > "$USERNAME_FILE"
fi
USERNAME="$(tr -d '\n' < "$USERNAME_FILE")"
if [ -z "$USERNAME" ]; then
USERNAME="$DEFAULT_USERNAME"
printf '%s\n' "$USERNAME" > "$USERNAME_FILE"
fi
chown gnome-remote-desktop:gnome-remote-desktop "$USERNAME_FILE"
chmod 600 "$USERNAME_FILE"
# Write username to a separate file for the hub
echo "sovran" > /var/lib/gnome-remote-desktop/rdp-username
chmod 600 /var/lib/gnome-remote-desktop/rdp-username
if [ ! -f "$PASSWORD_FILE" ]; then
openssl rand -base64 16 > "$PASSWORD_FILE"
fi
PASSWORD="$(tr -d '\n' < "$PASSWORD_FILE")"
if [ -z "$PASSWORD" ]; then
echo "RDP password file is empty: $PASSWORD_FILE" >&2
exit 1
fi
chown gnome-remote-desktop:gnome-remote-desktop "$PASSWORD_FILE"
chmod 600 "$PASSWORD_FILE"
# Get current IP address
LOCAL_IP=$(hostname -I | awk '{print $1}')
LOCAL_IP="$(hostname -I | awk '{print $1}')"
# Always rewrite the credentials file with the current IP
cat > "$CRED_FILE" <<EOF
========================================
GNOME Remote Desktop (RDP) Credentials
========================================
Username: sovran
Username: $USERNAME
Password: $PASSWORD
Connect from any RDP client to:
@@ -116,11 +134,13 @@ lib.mkIf config.sovran_systemsOS.features.rdp {
========================================
EOF
chown gnome-remote-desktop:gnome-remote-desktop "$CRED_FILE"
chmod 600 "$CRED_FILE"
# Enable RDP backend and set credentials
grdctl --system rdp enable
grdctl --system rdp set-credentials sovran "$PASSWORD"
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"
grdctl_system rdp set-credentials "$USERNAME" "$PASSWORD"
echo "GNOME Remote Desktop RDP configured successfully"
'';