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
The unset DBUS_SESSION_BUS_ADDRESS and unset XDG_RUNTIME_DIR lines are the critical part. When xrdp-sesman forks a session, it can pass its own stale values for these variables into the user's desktop. This silently breaks PolicyKit prompts, file manager operations, and audio -- even when pam_systemd.so is correctly configured in step 3. The exec startxfce4 bypasses the default session-detection logic and launches Xfce directly.
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.
If the VM runs SELinux in enforcing mode, install the xrdp-selinux policy module. Without it, session spawning and home directory creation are blocked by AVC denials.
dnf install -y xrdp-selinux
rpm -q xrdp-selinux # verify it installed
The xrdp-selinux package is a weak dependency of xrdp and is silently skipped on minimal images or when using --setopt=install_weak_deps=False. Always verify it is present.
Troubleshooting SELinux denials
Check for denials:
ausearch -m avc -ts recent | grep -i -E "xrdp|sesman|xorg|oddjob"
| Symptom | Cause | Fix |
|---|---|---|
| Sessions fail to spawn a desktop | xrdp-selinux module missing | dnf install -y xrdp-selinux |
| Some VMs from the same image work, others don't | Stale file labels from image cloning | restorecon -R -v /home /etc/xrdp /var/log/xrdp* |
| Home directory creation fails on first login | Missing xrdp-selinux (it allows oddjob_mkhomedir transitions) | dnf install -y xrdp-selinux |
If you build a golden VM image that is cloned for deployments, run a full relabel after finalizing the image:
touch /.autorelabel && reboot
If you change XRDP from the default port 3389, register the new port:
semanage port -a -t unreserved_port_t -p tcp <new-port>
As a last resort, generate a targeted policy from observed denials:
ausearch -m avc -ts recent | grep xrdp | audit2allow -M xrdplocal
semodule -i xrdplocal.pp
Review the generated .te file before loading -- audit2allow grants exactly what was denied, which may be overly permissive.
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.