Linux Images: XRDP Configuration
Enable XRDP if users need a graphical desktop session via RDP. XRDP provides the RDP server, and users connect through tiCrypt's TLS tunnel infrastructure.
GNOME on RHEL 8+ defaults to Wayland, which is incompatible with XRDP's Xorg backend. Use Xfce or MATE as the desktop environment for reliable RDP sessions.
1. Install Packagesโ
- RHEL / Rocky / AlmaLinux
- Ubuntu / Debian
XRDP is in the EPEL repository:
dnf install -y epel-release
dnf install -y xrdp xorgxrdp
dnf groupinstall -y "Xfce" --with-optional
apt-get install -y xrdp xorgxrdp xfce4
The xorgxrdp package provides a native Xorg backend for XRDP, giving the best performance and clipboard/drive redirection support. The alternative is Xvnc (via tigervnc-server), which is more forgiving of Xorg version mismatches but has higher latency and limited channel support. Use xorgxrdp unless you encounter Xorg compatibility issues after a kernel or mesa update.
2. Configure the Session Managerโ
Replace the contents of /etc/xrdp/startwm.sh with the following. This preserves user profile sourcing (/etc/profile, ~/.profile) so environment variables, PATH modifications, and module loads are available in the desktop session:
#!/bin/sh
if [ -r /etc/profile ]; then
. /etc/profile
fi
if [ -r ~/.bash_profile ]; then
. ~/.bash_profile
elif [ -r ~/.bash_login ]; then
. ~/.bash_login
elif [ -r ~/.profile ]; then
. ~/.profile
fi
unset DBUS_SESSION_BUS_ADDRESS
unset XDG_RUNTIME_DIR
exec startxfce4
Do not use a bare exec startxfce4 without the profile sourcing above. Without it, users will not have their PATH, environment variables, or shell profile customizations in the desktop session.
3. Configure PAM for Session Registrationโ
The default /etc/pam.d/xrdp-sesman on RHEL does not include pam_systemd.so. Without it, systemd-logind does not register the RDP session, /run/user/<uid> is not created, and desktop features that depend on XDG_RUNTIME_DIR (file manager operations, PolicyKit prompts, audio) will fail.
Add the following line to /etc/pam.d/xrdp-sesman after the pam_loginuid.so line:
sed -i '/pam_loginuid.so/a session optional pam_systemd.so' /etc/pam.d/xrdp-sesman
4. Configure Session Managementโ
Edit /etc/xrdp/sesman.ini and adjust the [Sessions] section for a multi-user VM. The default Policy=Default already supports session reconnect (users reconnect to their existing desktop rather than getting a new one). Adjust the reaping settings so disconnected sessions do not accumulate indefinitely:
[Sessions]
MaxSessions=100
KillDisconnected=true
DisconnectedTimeLimit=28800
| Setting | Value | Effect |
|---|---|---|
MaxSessions | 100 | Maximum concurrent sessions (active + disconnected). Set above your expected user count with headroom. |
KillDisconnected | true | Terminate sessions that have been disconnected longer than DisconnectedTimeLimit |
DisconnectedTimeLimit | 28800 | Seconds (8 hours) before a disconnected session is reaped. Users who reconnect within this window rejoin their existing desktop. |
Do not add I (IP) to the Policy setting. tiCrypt traffic arrives through the tunnel infrastructure, so the source IP seen by XRDP is the tunnel endpoint, not the user's real IP. Adding I would prevent session reconnect.
5. Restrict Clipboard Redirectionโ
By default XRDP shares the clipboard between the user's local machine and the remote desktop, which is a data-exfiltration path for secure-research VMs. To disable the clipboard entirely, set cliprdr=false in the [Channels] section of /etc/xrdp/xrdp.ini:
[Channels]
cliprdr=false
Then restart the XRDP services:
systemctl restart xrdp xrdp-sesman
Directional clipboard control (newer XRDP versions)
Newer XRDP can restrict the clipboard by direction, configured in the [Security] section of /etc/xrdp/sesman.ini (not xrdp.ini):
[Security]
RestrictOutboundClipboard=all
RestrictInboundClipboard=all
RestrictOutboundClipboard controls copying from the session out to the client (the data-exfiltration direction); RestrictInboundClipboard controls pasting into the session from the client. Both accept:
| Value | Effect |
|---|---|
none | No restriction (default) |
all | Block all clipboard transfer in that direction |
text, file, image | Block only that content type; combine as a comma-separated list, for example text,file |
To let researchers paste into the VM but never copy anything out, block only the outbound direction and leave inbound unrestricted:
[Security]
RestrictOutboundClipboard=all
RestrictInboundClipboard=none
Directional control is not available on older XRDP, and a version that does not recognize a key silently ignores it and leaves the clipboard open. Confirm the keys exist for your installed version with man sesman.ini (older releases exposed a similar setting in xrdp.ini), apply the change, restart with systemctl restart xrdp xrdp-sesman, then verify from a real session that copy and paste are actually blocked in the direction you intended.
6. Fix Startup Orderingโ
The stock xrdp-sesman.service unit only declares After=network.target, which means it can start before D-Bus, NFS mounts, or the VM Controller's user provisioning are ready. This race condition causes intermittent RDP failures where some VMs from the same image work and others do not, or a VM works until its next restart.
Create systemd drop-in overrides to enforce correct ordering:
mkdir -p /etc/systemd/system/xrdp-sesman.service.d
cat > /etc/systemd/system/xrdp-sesman.service.d/override.conf <<'EOF'
[Unit]
After=dbus.service network-online.target
Wants=dbus.service network-online.target
[Service]
Restart=on-failure
RestartSec=2
StartLimitIntervalSec=60
StartLimitBurst=10
EOF
mkdir -p /etc/systemd/system/xrdp.service.d
cat > /etc/systemd/system/xrdp.service.d/override.conf <<'EOF'
[Unit]
After=xrdp-sesman.service
Requires=xrdp-sesman.service
[Service]
Restart=on-failure
RestartSec=2
EOF
systemctl daemon-reload
The Restart=on-failure directives are critical. Without them, if an RDP connection attempt arrives before the VM Controller has finished provisioning users, xrdp-sesman can crash or enter a bad state and systemd will not restart it. This produces a permanently broken RDP service on that VM while identical VMs launched from the same image work fine.
7. Disable NSS Caching Daemonsโ
systemctl disable --now sssd nscd 2>/dev/null
sssd and nscd cache name-service lookups, including negative results. If an RDP login attempt triggers a user lookup before the VM Controller has created that account, the "user not found" response is cached. Subsequent attempts fail even after the account exists, until the cache entry expires. On an isolated VLAN with no AD or LDAP, these services provide no benefit. Disable them so NSS lookups resolve directly from /etc/passwd.
8. Enable Servicesโ
systemctl enable xrdp xrdp-sesman
9. Configure controller.tomlโ
The controller must allow port 3389 for RDP tunneling. Add the following to the [tunnel] section:
[tunnel]
allowedPorts = 3389
[tunnel.services]
rdp = 3389
10. Verifyโ
systemctl status xrdp xrdp-sesman
ss -tlnp | grep 3389
XRDP should be listening on port 3389. A full end-to-end test requires a deployed VM with a provisioned user.