Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
181f63d
Lint the bootstrap scripts and build the Bicep templates in CI
paullizer Sep 25, 2026
9cace66
Retire RHEL 7 as a Linux host image
paullizer Sep 25, 2026
9079012
Default new Linux hosts to RHEL 9
paullizer Sep 25, 2026
807c4c3
Merge the RHEL and Ubuntu release agents into one script
paullizer Sep 25, 2026
37e11d2
Give Ubuntu hosts the Ubuntu desktop through an xrdp session launcher
paullizer Sep 25, 2026
7606a6a
Let each deployment choose GNOME, Xfce or MATE for its Linux hosts
paullizer Sep 25, 2026
b09ee28
Remove xpra from the Linux hosts
paullizer Sep 25, 2026
724bc5c
Offer Rocky Linux 9 and AlmaLinux 9 as Linux host images
paullizer Sep 25, 2026
ec02be3
Give Ubuntu MATE hosts a working panel
paullizer Sep 26, 2026
28cc2c2
Skip the GNOME welcome tour on RHEL-family hosts
paullizer Sep 26, 2026
6407568
Keep systemd-networkd in charge of Ubuntu desktop hosts' network
paullizer Sep 26, 2026
1901801
Unlock the login keyring with a key the broker keeps
paullizer Sep 26, 2026
1c0af38
Release host agent 1.2.0
paullizer Sep 26, 2026
6a7e7e6
Run the host migration script with bash
paullizer Sep 26, 2026
153f6cc
Turn off the Tracker file indexer in broker sessions
paullizer Sep 26, 2026
81e4ddb
Find the xrdp connection when disconnecting an idle session
paullizer Sep 26, 2026
14ade47
Give a killed Xorg a moment to exit before retrying the grace cleanup
paullizer Sep 26, 2026
87a54aa
Start the idle clock again when a user reconnects
paullizer Sep 26, 2026
78c6ae2
Document distribution and desktop support and bump versions
paullizer Sep 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/front-end-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ on:
- 'task/**'
- 'sql_queries/**'
- 'linux_host/**'
- 'custom_script_extensions/**'
- 'deploy/bicep/**'
- '.github/workflows/front-end-tests.yml'
push:
paths:
Expand All @@ -16,6 +18,8 @@ on:
- 'task/**'
- 'sql_queries/**'
- 'linux_host/**'
- 'custom_script_extensions/**'
- 'deploy/bicep/**'
- '.github/workflows/front-end-tests.yml'

jobs:
Expand Down Expand Up @@ -190,9 +194,26 @@ jobs:
steps:
- uses: actions/checkout@v4

# Also lints the bootstrap scripts in custom_script_extensions.
- name: Run the host script tests
run: bash linux_host/tests/run.sh

bicep-build:
name: Bicep templates
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@v4

# Compile only. main.json is regenerated with the template changes, and its exact
# contents depend on the Bicep version, so it is not compared here.
- name: Build the Bicep templates
run: |
az bicep install
az bicep build --file deploy/bicep/main.bicep --stdout > /dev/null

task-test:
name: Scheduled task function
runs-on: ubuntu-latest
Expand Down
50 changes: 36 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Purpose

The **Linux Broker for AVD Access** is a solution designed to manage and broker user access to Linux hosts via Azure Virtual Desktop (AVD). It provides a scalable and efficient way to connect users to Linux virtual machines (VMs) using either Remote Desktop Protocol (RDP) for full desktop experiences or xpra (X Remote Application) for virtualized applications.
The **Linux Broker for AVD Access** is a solution designed to manage and broker user access to Linux hosts via Azure Virtual Desktop (AVD). It provides a scalable and efficient way to connect users to full desktops on Linux virtual machines (VMs) over the Remote Desktop Protocol (RDP), which the hosts serve with xrdp.

This solution leverages Azure services such as managed identities, security groups, Azure App Service, Azure Functions, and Azure SQL Database to provide secure and efficient brokering, session management, and scaling of Linux hosts.

Expand All @@ -15,7 +15,7 @@ The solution consists of the following components:

- **Azure Virtual Desktop (AVD)**: Provides the interface for users to access Linux hosts. Users can connect via the AVD web client or any supported AVD client.

- **Broker Agent (`Connect-LinuxBroker.ps1`)**: A PowerShell script running on each AVD host that acts as an agent to broker connections to Linux hosts. It connects to the Broker API using managed identity to check out a Linux VM and initiate the appropriate connection (RDP or xpra).
- **Broker Agent (`Connect-LinuxBroker.ps1`)**: A PowerShell script running on each AVD host that acts as an agent to broker connections to Linux hosts. It connects to the Broker API using managed identity to check out a Linux VM and opens a Remote Desktop connection to it.

- **Linux Hosts Cluster**: A set of Linux VMs that users connect to. Each Linux host has managed identity enabled and runs a Session Release Agent.

Expand All @@ -37,7 +37,7 @@ The solution consists of the following components:

- **Service Management Portal**: A front-end web application that allows administrators to manage VMs, scaling rules, and monitor the system. It provides functionalities such as finding and acting on hosts in bulk, importing hosts from Azure, helping users with their sessions, scheduling scaling, patching hosts in rolling maintenance runs, charting capacity and unmet demand, and viewing logs. It is a React 18 and TypeScript single-page app built with Vite and Tailwind CSS, served by a Flask backend-for-frontend that holds the Entra ID token server-side and calls the Broker API on the administrator's behalf.

- **Azure Key Vault**: Stores sensitive information such as SSH keys and database passwords, accessed securely by the Broker API using managed identity.
- **Azure Key Vault**: Stores sensitive information such as SSH keys and database passwords, accessed securely by the Broker API using managed identity. A second vault holds the key that unlocks each user's login keyring.

- **Managed Identities and Security Groups**: Used throughout the solution to securely authenticate and authorize different components. AVD hosts and Linux hosts have managed identities and are members of respective security groups.

Expand All @@ -55,7 +55,7 @@ The architecture ensures secure, efficient, and scalable management of Linux hos
- **Broker Database**: Azure SQL Database for storing VM and scaling data.
- **Azure Function for Scaling Tasks**: Manages scaling of Linux hosts.
- **Service Management Portal**: React and TypeScript front-end application for administrators, served by a Flask backend-for-frontend.
- **Azure Key Vault**: Secure storage for SSH keys and passwords.
- **Azure Key Vault**: Secure storage for SSH keys, passwords and each user's login keyring key.
- **Managed Identities**: Used for secure authentication between components.
- **Security Groups**: Controls access permissions for managed identities.

Expand All @@ -67,8 +67,9 @@ The architecture ensures secure, efficient, and scalable management of Linux hos
- The Broker Agent script (`Connect-LinuxBroker.ps1`) connects to the Broker API using the AVD host's managed identity.
- It checks out an available Linux VM for the user.
- The user's ID is added to the Linux host with a unique 25-character password.
- The user is added to appropriate user groups on the Linux host for RDP or xpra access.
4. **User Connects to Linux Host**: The user is connected to the Linux host via RDP or xpra and can work as needed.
- The user is added to appropriate user groups on the Linux host for RDP access.
- The host also receives the key that unlocks the user's login keyring, so applications that save passwords do not ask for one.
4. **User Connects to Linux Host**: The user is connected to the Linux host via RDP and can work as needed.
5. **Session Management**:
- If the user disconnects or logs off, the Session Release Agent on the Linux host reconciles the XRDP/Xorg session state immediately when possible and otherwise on the next safety-net poll.
- A reconnect timer is initiated, 20 minutes by default and configurable from the portal.
Expand Down Expand Up @@ -185,12 +186,22 @@ The custom script extension for the AVD host:

The custom script extensions support the following Linux distributions:

- **Red Hat Enterprise Linux (RHEL) 7, 8, and 9**
- **Ubuntu 24 Desktop**
- **Red Hat Enterprise Linux (RHEL) 8 and 9**
- **Rocky Linux 9 and AlmaLinux 9**: rebuilds of RHEL 9 that need no Red Hat subscription, set up by the RHEL 9 script
- **Ubuntu 24.04**: Canonical's server image, with a desktop added

Each deployment chooses the desktop its hosts run with `linuxHostDesktop`:

- **GNOME**, the default: the `Server with GUI` group on RHEL and its rebuilds, and on Ubuntu the Ubuntu desktop, which xrdp sessions run as Ubuntu on Xorg
- **Xfce**
- **MATE**

On RHEL, Rocky Linux and AlmaLinux, Xfce and MATE come from EPEL. Rocky Linux and AlmaLinux install EPEL from their own repositories.

These scripts:

- **Install XRDP and xpra**: Set up XRDP for full desktop access (RDP) and xpra for application virtualization, enabling users to connect via AVD.
- **Install xrdp**: Set up xrdp for full desktop access over RDP, enabling users to connect via AVD. The host firewall allows only SSH and RDP.
- **Start the desktop**: xrdp starts every session through `xrdp-startwm.sh`, which unlocks the user's login keyring and runs the desktop the deployment chose. GNOME's file indexer is turned off, because it would crawl the home directories on the NFS share.
- **Configure Authentication**: Sets up authentication mechanisms for secure user access.
- **Deploy the Linux Session Release Agent**: Installs the timer-based reconciliation service plus a `systemd-logind` watcher that can trigger early reconciliations. The timer remains the fallback path so the system still converges even if event delivery is delayed or unavailable.
- **Install the Host Settings Agent**: Installs `apply-host-settings.sh` and seeds the settings profile, so screen lock policy and session timings are applied consistently on every supported distribution rather than only on RHEL 8. `LINUXBROKER_DISABLE_SCREEN_LOCK` still chooses the screen lock posture that is seeded; from then on the values are managed from the portal.
Expand All @@ -217,7 +228,7 @@ Every run first reads each host's power state from Azure and corrects the broker
- **Session Monitoring**: The Session Release Agent reconciles XRDP/Xorg session state on a timer (60 seconds by default) and can also wake early from `systemd-logind` session signals.
- **Release State**: When a session is disconnected, the VM enters a 'released' state, allowing the user to reconnect within the configured grace period (20 minutes by default). The desktop itself is closed at disconnect unless **Keep sessions alive during the grace period** is turned on, in which case it keeps running until the grace period expires.
- **Session Termination**: If the user does not reconnect within that window, the host signs them off. The broker returns the VM once the grace period, one reconcile interval and a further 60 seconds have passed, then keeps it **Cleanup pending** until the user's account has been removed and the home unmounted. Cleanup is retried automatically about every two minutes while the host is on and reachable, and operators can retry it from the portal. Only then can the VM be checked out by someone else.
- **Idle Sessions**: When an idle timeout is configured, a user who stays connected but inactive is disconnected, which starts the same grace period. With **Keep sessions alive** turned on they can reconnect and resume; otherwise they get a fresh desktop on the same host. If they do not reconnect, the VM is reclaimed. This is disabled by default.
- **Idle Sessions**: When an idle timeout is configured, a user who stays connected but inactive is disconnected, which starts the same grace period. With **Keep sessions alive** turned on they can reconnect and resume; otherwise they get a fresh desktop on the same host. If they do not reconnect, the VM is reclaimed. Idle time never counts from before the user's latest connection, so a user who reconnects is not disconnected again straight away. This is disabled by default. The agent reads idle time with `xprintidle`, which only Ubuntu packages, so RHEL, Rocky Linux and AlmaLinux hosts do not enforce the timeout or show its warning.

### Linux Host Settings

Expand All @@ -230,18 +241,20 @@ Administrators manage host behavior from the **Host Settings** page in the Servi
| Reconcile interval | 60 s | 30–900 | How often each host re-checks session state |
| Watcher debounce | 10 s | 1–300 | Minimum gap between `logind`-triggered reconciliations |
| Watcher settle | 2 s | 0–60 | Pause after a `logind` signal before reconciling |
| Idle timeout | 0 (disabled) | 0, or 300–86400 | Inactivity before a connected user is disconnected |
| Idle timeout | 0 (disabled) | 0, or 300–86400 | Inactivity before a connected user is disconnected. Only Ubuntu hosts enforce it; see **Idle Sessions** above |
| Idle warning lead time | 120 s | 0–900 | On-screen warning before the idle timeout, must be less than the timeout |
| Remove the lock screen | true | boolean | Disables the Super+L shortcut and the Lock menu entry |
| Remove the lock screen | true | boolean | Stops the screen from locking at all. On GNOME it also removes the Super+L shortcut and the Lock menu entry |
| Screen lock enabled | false | boolean | Whether the screen locks when the screensaver activates |
| Screen blank delay | 0 (never) | 0–86400 | Inactivity before the screen blanks |
| Screen lock delay | 0 (immediate) | 0–86400 | Delay between blanking and locking |
| Lock screen settings | true | boolean | Applies dconf locks so users cannot override the screen lock values |
| Lock screen settings | true | boolean | Locks the screen lock values in dconf, and in xfconf on Xfce hosts, so users cannot override them |

The session lifecycle defaults match the values that were previously hardcoded, so adopting this feature changes no behavior until an administrator edits the profile.

The screen lock defaults preserve the posture set by `LINUXBROKER_DISABLE_SCREEN_LOCK`: the lock screen is removed, because a locked GNOME greeter inside an xrdp session frequently cannot be unlocked after a reconnect, which strands the host's lease. That environment variable still chooses the posture seeded at provisioning time; from then on the values are managed from the portal. Set **Screen lock enabled** on and **Remove the lock screen** off to satisfy a STIG or CIS idle-lock control.

The same values apply to every desktop. Xfce and MATE count the screen blank and lock delays in whole minutes, up to 8 hours, so the blank delay is rounded up and the lock delay to the nearest minute, and Xfce sessions pick up a change when they start. **Host Settings** notes this when the fleet has Xfce or MATE hosts. See [Linux Host Screen Lock](deploy/DEPLOYMENT.md#linux-host-screen-lock) for the files each desktop reads.

**Keep sessions alive during the grace period** and **Screen lock enabled** are mutually exclusive: a resumed session behind a lock screen cannot be unlocked, because users never know the password the broker sets at each checkout. Hosts that have not been updated with `deploy/Migrate-LinuxHostReleaseAgent.ps1` keep closing desktops at disconnect and show as pending in the drift table once the setting is on.

#### How settings reach the hosts
Expand All @@ -259,6 +272,7 @@ Because the profile is versioned, the portal shows which hosts have applied the

- **Managed Identities**: Used for secure authentication between Azure resources without storing credentials.
- **Azure Key Vault**: Stores SSH keys and database passwords securely, accessed via managed identities.
- **Login keyring**: The Linux password changes at every checkout, so it cannot protect a user's GNOME login keyring. The broker keeps a separate random key for each user in a vault of its own, where the API can write secrets but cannot change the deployment's, and `xrdp-startwm.sh` unlocks the keyring with it before the desktop starts. The key reaches the host over the checkout's SSH session and stays in memory-backed storage under `/run`, readable only by the user, until the host is returned.
- **API Permissions**: Specific API permissions are granted to components to restrict access based on roles.
- **Logging and Monitoring**: All activities are logged to Azure Application Insights and Log Analytics Workspace.

Expand Down Expand Up @@ -339,9 +353,17 @@ The admin console foundations (audit log, host actions and drain, fleet health)

The rest of the admin console (sessions, broadcast messages, scaling schedules, trends, rolling maintenance and the new host list) needs no new Azure resources or roles either, but the Linux hosts need agent 1.1.0 for sign-out, messages, profile resets and patching. See [Upgrading To The Complete Admin Console](deploy/DEPLOYMENT.md#upgrading-to-the-complete-admin-console).

The distribution and desktop support release needs `azd provision` and agent 1.2.0. See [Upgrading To Distribution And Desktop Support](deploy/DEPLOYMENT.md#upgrading-to-distribution-and-desktop-support).

- **RHEL 7 is no longer offered.** An azd environment that still stores `linuxHostOsVersion=7-LVM` fails validation, so change it before you provision.
- **Run `azd provision`.** It creates the keyring vault, gives the API access to it and sets `KEYRING_VAULT_URL`. The existing hosts keep their image and extension, unless you change `linuxHostDesktop`.
- **Update the Linux hosts to agent 1.2.0.** Fleet health flags every host as outdated until `deploy/Migrate-LinuxHostReleaseAgent.ps1` from this release has updated it. The migration also removes xpra and closes TCP 443.
- **Review the idle timeout.** It had never disconnected anyone before this release, and migrated Ubuntu hosts now enforce any timeout already set.
- **Replace or bootstrap again any Ubuntu hosts.** Earlier releases deployed them with no desktop and without the packages NFS homes need.

## Roadmap

Planned work beyond this release, including Ubuntu desktop and RHEL 10 support, starting a host on demand, golden images and multi-session hosts, is described in [docs/ROADMAP.md](docs/ROADMAP.md).
Planned work beyond this release, including RHEL 10 and Ubuntu 26.04 support, starting a host on demand, golden images and multi-session hosts, is described in [docs/ROADMAP.md](docs/ROADMAP.md).

## Contributing

Expand Down
1 change: 1 addition & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ The API reads environment variables directly; it does not load `.env` files by i
| `DOMAIN_NAME` | required for SSH actions | DNS suffix used to build `<admin>@<hostname>.<domain>`. The `azd` deployment sets it to its private DNS zone (`linuxbroker.internal`) unless you supply `domainName`. |
| `VAULT_URL` | required | Key Vault URL for SQL password and SSH key retrieval. |
| `KEY_NAME` | required for SSH actions | Key Vault secret name containing the PEM SSH private key. |
| `KEYRING_VAULT_URL` | optional | Key Vault that holds each user's login keyring key, as a secret named `keyring-<uid>`. A checkout reads the key, or creates it the first time, and sends it to `create-user.sh` so the xrdp session launcher can unlock the user's GNOME login keyring; a profile reset replaces it. The API needs Key Vault Secrets Officer on this vault. Without the setting, no key is sent and keyrings stay locked as before. A Key Vault error never fails a checkout: that worker sends no key for the next five minutes. The `azd` deployment creates the vault (`kr…`) and sets it. |
| `NFS_SHARE` | required for checkout provisioning | NFS share argument passed to `create-user.sh`; used by code but not currently listed in `env.example`. The `azd` deployment sets it to the Azure Files NFS share it provisions unless you supply `nfsShare` or set `deployNfsShare` to `false`. |
| `ALLOW_LEGACY_SCOPE_ACCESS` | optional | `true` treats the portal's `access_as_user` scope as `FullAccess` while roles are assigned during an upgrade. Defaults to `false`; set through the `allowLegacyScopeAccess` deployment value. |
| `GUNICORN_CMD_ARGS` | optional | Overrides the image default of `--workers 2 --threads 8 --timeout 120 --graceful-timeout 30 --keep-alive 5`. |
Expand Down
Loading
Loading