BeyondTrust/POV

← Back to dashboard

Virtual Desktops

Audience: operator · Profile: both · Read this when: you need a pool of private desktop VMs that reps reach through the PRA Gateway instead of over the open internet.

Part of Cloud.

Preview. All three clouds provision and broker seats, but the feature has not been run end to end on AWS or GCP against a live PRA appliance. Off by default; turn it on with the Virtual Desktops preview toggle in Settings (vdesktops_enabled). The pools page is /desktops.

A desktop pool is N private VMs built from one desktop image, tagged so the pool is recoverable from the cloud itself rather than only from this database. Seats are created, scaled and deleted as a unit. Each seat is registered as a PRA Jump Item, so a rep reaches a desktop from the representative console and no seat needs a public IP.

What each cloud can do

Azure AWS GCP
Provisions VMs yes yes yes
Windows seats yes no no
Linux seats yes yes yes
Jump item Remote RDP (Windows) / Shell Jump (Linux) Shell Jump Shell Jump
Credential injection Windows only none none
Pool tag key dashboard:desktop_pool dashboard:desktop_pool dashboard_desktop_pool

Why AWS and GCP are Linux-only

Windows credentials work differently on each cloud, and only one of the three is wired. Azure generates a password and vaults it before the VM exists, so the credential survives a failure anywhere after that point. EC2 instead returns password data encrypted to the launch key pair, decrypted client-side. GCE delivers a password through windows-keys instance metadata and an RSA exchange. Those are two more mechanisms, each with its own storage question, so an AWS or GCP Windows pool is refused at create time with that reason rather than provisioned into seats nobody can sign into.

Why Linux seats inject no credential

A Linux seat authenticates with an SSH key. The dashboard never holds the private half, and the PRA provider wrapper here has no SSH-key vault resource — the only vault resource wired is a username/password account, which a key-based login cannot use. So a Linux seat's Shell Jump registers without credential injection and the rep supplies the key in PRA. This is a real difference from the Windows path, not an oversight, and the pool form and the session dialog both say so.

Creating a pool

Desktops → New pool. Pick a cloud, name the pool and choose a seat count, then fill in that cloud's block. Create stays disabled until everything that cloud's backend requires is present — a pool that would 400 cannot be submitted.

Changing the AWS region or the GCP zone clears the selections scoped to it and refetches. That is deliberate: an AMI id from another region fails with InvalidAMIID.NotFound, which names no region at all, and a sandbox uses the same subnetwork name in every GCP region, so a stale self-link looks correct and fails at launch on a scope mismatch.

The SSH key

The form does not ask for one on AWS or GCP. The key is resolved server-side from that cloud's configured secret — Secrets Manager on AWS, Secret Manager on GCP — exactly as the single-VM deploy pages do. (/api/gcp/secrets/ssh-key returns only the first 80 characters of the key, so a form field there could not show a real one anyway.) Azure's picker can show its key, so the Azure block still posts one. If no key is configured, the pool form says so and blocks Create rather than producing seats nobody can reach.

Settings

Settings → Virtual Desktops holds the per-cloud defaults. Everything here is optional except Azure's subnet; a blank field falls back down a chain.

Setting Blank falls back to
azure_desktops_subnet_id this region's desktops_subnet_id. No further fallback
azure_desktops_vm_size the pool form's own default
azure_desktops_vault_account_group_id PRA's Default account group
aws_desktops_subnet_id this region's desktops_subnet_id, then aws_default_subnet_id
aws_desktops_instance_type the pool form's own default
gcp_desktops_subnetwork this region's desktops_subnetwork, then gcp_subnetwork
gcp_desktops_machine_type the pool form's own default

Azure is the one cloud with no final fallback to its ordinary VM subnet, and that is on purpose: an Azure sandbox's aci-subnet is delegated to Container Instances and cannot host a VM NIC, so inheriting "the VM subnet" there could silently select a subnet that cannot deploy a desktop. Every AWS subnet and every GCP subnetwork can host an instance, so inheriting is a default an operator can live with rather than a guess.

Per-region overrides for all three clouds live in Settings → Multi-region.

Brokering and the Gateway

Before any seat registers, the pool's provision job warms the shared PRA Gateway host for that seat's cloud and region — register first and the jump items exist but read Unavailable, with no Gateway to broker them. Warming is idempotent and best-effort: it usually reuses an existing host and takes about a minute the first time.

Two consequences worth knowing before turning this on:

Registration is best-effort by design. A running seat with no jump item is debuggable; a seat marked failed because PRA was briefly unreachable is a VM nobody cleans up. When a seat shows no jump item, the reason is on the pool's job.

Scaling and deleting

Scale grows or shrinks a pool to a seat count. Growing reuses the spec stored on the pool's create-time job, so a pool created before that job was recorded cannot scale up and says so. Shrinking tears down the newest seats.

Delete terminates every backing VM, removes each seat's PRA jump item from its stored Terraform state, drops the rows, and then reaps the shared Gateway if nothing else still references it. Terminate runs before the row is dropped — a row dropped first is a VM nobody can find again. If a terminate fails the rows still drop, but the job ends failed with the reason, because a green job whose failure only shows on a seat row is a job nobody reads.

Seats are exempt from the auto-delete timer: a pool is inventory, not a scratch deploy.