Self-Service VDI — Admin Invite, Request, Approve, Provision

The Federal Frontier Platform provides a fully self-service Virtual Desktop Infrastructure (VDI) flow. Admins invite users, users request workspaces, admins approve, and the platform provisions persistent or temporary desktops automatically — zero shell access, zero plaintext passwords.

Self-Service VDI — Admin Invite, Request, Approve, Provision

The Federal Frontier Platform provides a fully self-service Virtual Desktop Infrastructure (VDI) flow. The entire lifecycle — from onboarding a new user to provisioning a persistent desktop workspace — runs through the OutpostAI UI with no shell access and no plaintext passwords.

Architecture Overview

The VDI self-service flow spans four platform components:

Component Role
OutpostAI (Next.js) Admin and user-facing UI — invite form, request form, approval queue, workspace tiles
Trailboss API (FastAPI) Backend endpoints for invite, request, approve, deny + deploy script orchestration
Keycloak (FAS realm) Identity provider — user creation, password reset, OIDC tokens with role claims
OpenStack (Kolla) Infrastructure — Cinder volumes, Nova VMs, Neutron floating IPs
Guacamole Browser-based RDP gateway — connections auto-registered with per-workspace credentials
MailHog Development email capture — receives invitation and notification emails

End-to-End Demo Flow

The platform demonstrates the following self-service workflow:

1. Admin Invites a User

The admin navigates to Admin → Invite User in OutpostAI and selects an existing FAS realm user (or enters details for a new user). The platform:

  • Creates the Keycloak user with a cryptographically random temporary password
  • Marks the account for password reset on first login (requiredActions: UPDATE_PASSWORD)
  • Sends an invitation email via Keycloak’s execute-actions-email API through MailHog
  • The invitation email subject is “Update Your Account” from “Federal Frontier Platform”

Production note: MailHog is a dev-cluster email capture service. Production deployments wire SMTP to your enterprise mail relay. The flow is identical — only the transport changes.

2. User Requests a Workspace

After the invited user sets their password and signs in to OutpostAI, they navigate to the Workspaces tab and click Request a Workspace. The request dialog offers:

  • Persistent Desktop — Cinder-backed 50 GB home directory. Files survive reboots. For long-term analysis work.
  • Temporary Desktop — Session-only. No persistent storage. For quick tasks and demos.

The user provides a justification and submits. A notification email is sent to the VDI admin address, and the request appears in the admin’s VDI Requests queue.

3. Admin Approves the Request

The admin navigates to Admin → VDI Requests, which shows a filterable table of all workspace requests with state badges (pending, approved, fulfilled, denied, failed). The admin can:

  • Approve — triggers synchronous provisioning (~60 seconds). The approve button shows a spinner with “Provisioning…” text while the deploy script runs.
  • Deny — opens a comment dialog requiring a reason. The user sees the denial reason in their “My Requests” section.

4. Platform Provisions the Workspace

When the admin clicks Approve, the Trailboss API invokes the deploy script (deploy-persistent-workspace.sh) inside the Trailboss container. The script:

  1. Creates a Cinder volume (vdi-home-{username}, 50 GB) if one doesn’t exist — auto-provisioned, no manual step
  2. Boots a Nova VM from the ffp-vdi-desktop-v2.2 golden image with the volume attached at /dev/vdb
  3. Sets a cryptographically random password for the vdi-user account via cloud-init — users never see this password
  4. Allocates a floating IP from the public network
  5. Registers a Guacamole RDP connection with the random password — Guacamole injects the credential transparently
  6. Grants READ permissions to the requesting user and the operator in Guacamole
  7. Transitions the request state to fulfilled

Total provisioning time: ~60 seconds.

5. User Accesses Their Workspace

The user refreshes the Workspaces tab in OutpostAI and sees their new workspace tile (ffp-vdi-persistent-{name}). Clicking Connect opens a Guacamole RDP session in a new browser tab — the XFCE desktop loads with the user’s persistent home directory mounted and skel content (.bashrc, .profile, Desktop, Documents, Downloads) already seeded.

Files created in the workspace persist across VM reboots. The persistent volume (/dev/vdb) is formatted on first boot and mounted at /home/vdi-user by a systemd oneshot service.

Authorization Model

VDI admin capabilities are gated by a two-layer check:

Layer Mechanism
Backend _require_vdi_admin() checks user_role == "platformadmin" OR "vdi-admin" in roles
Frontend isVdiAdmin checks roles.includes("vdi-admin") \|\| roles.includes("platformadmin") \|\| roles.includes("superadmin")
  • The vdi-admin Keycloak group exists in the FAS realm with a mapped realm role
  • Platform admins (platformadmin, frontieradmin) inherit VDI admin access without explicit group membership
  • Regular users can submit workspace requests and view their own request status — they cannot see the admin queue or invite form

API Endpoints

All endpoints are on the Trailboss API (/api/v1/):

Method Path Auth Description
POST /users/invite vdi-admin Create Keycloak user + send reset email
POST /workspace-requests Any authenticated Submit a workspace request
GET /workspace-requests/me Any authenticated List own requests
GET /workspace-requests vdi-admin List all requests
POST /workspace-requests/{id}/approve vdi-admin Approve + provision (~60s)
POST /workspace-requests/{id}/deny vdi-admin Deny with comment

Golden Image: ffp-vdi-desktop-v2.2

The VDI desktop image is built via Packer (not manually) and includes:

  • Ubuntu 22.04 with XFCE desktop environment
  • XRDP for remote desktop access
  • vdi-persistent-home.service — systemd oneshot that formats, fsck’s, and mounts /dev/vdb at /home/vdi-user on boot
  • /usr/local/sbin/vdi-mount-persistent-home.sh — skel seeding script that populates the home directory from /etc/skel if .bashrc is missing (FF-570 fix)
  • Cloud-init support for password injection and service enablement

Image lineage: v1 → v2 → v2.1 → v2.2 (current production). Previous versions retained in Glance for rollback.

Database Schema

Workspace requests are stored in PostgreSQL (f3iai database):

CREATE TABLE workspace_requests (
    id                      SERIAL PRIMARY KEY,
    requestor_keycloak_id   VARCHAR(64) NOT NULL,
    requestor_username      VARCHAR(100) NOT NULL,
    justification           TEXT NOT NULL,
    estimated_duration      VARCHAR(50),
    workspace_type          VARCHAR(50) NOT NULL DEFAULT 'knowledge_worker',
    state                   VARCHAR(20) NOT NULL DEFAULT 'pending'
                            CHECK (state IN ('pending','approved','denied','fulfilled','failed')),
    requested_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    decision_at             TIMESTAMPTZ,
    decided_by              VARCHAR(100),
    decision_comment        TEXT,
    fulfilled_at            TIMESTAMPTZ,
    fulfilled_workspace_id  VARCHAR(100)
);

The table is auto-created on first use via the asyncpg connection pool (same pattern as the cluster templates system).

Security Properties

  • Zero plaintext passwords — VDI user passwords are cryptographically random (24 characters, secrets.choice), set via cloud-init, and stored only in the Guacamole connection parameters. No human ever sees or types them.
  • Invitation flow — Keycloak temporary credentials with UPDATE_PASSWORD required action. Users set their own password via the Keycloak reset flow.
  • OIDC token-based auth — All API calls authenticated via Bearer token from the federal-frontier OIDC client. The sub claim identifies the user; realm_access.roles gates admin operations.
  • No shell access required — The entire provisioning flow runs inside the Trailboss container via subprocess.run with asyncio.to_thread (non-blocking). No SSH to infrastructure nodes.

Email Notifications

Event Recipient Subject Transport
User invited Invited user “Update Your Account” Keycloak SMTP → MailHog
Workspace requested VDI admin “VDI Workspace Request #{id} from {username}” Python smtplib → MailHog

Production deployments replace MailHog with an enterprise mail relay or webhook integration.

Deployment

The VDI self-service system is deployed via the standard FFP GitOps pipeline:

  • Trailboss API: harbor.vitro.lan/ffp/trailboss:v5.9.4 — includes OpenStack CLI, credentials, deploy script ConfigMap mount
  • OutpostAI: harbor.vitro.lan/ffp/outpostai-dev:v1.2.1 — includes admin invite/requests UI, workspace request form, role gating
  • Deploy script: Mounted as ConfigMap vdi-deploy-script at /opt/vdi/deploy-persistent-workspace.sh
  • OpenStack credentials: Secret openstack-admin-credentials with 8 OS_* environment variables
  • Guacamole password: Key guacamole_admin_password in f3iai-secrets

All image builds via GitLab CI → Harbor → Gitea overlay update → ArgoCD auto-sync. No local Docker builds on infrastructure nodes.

Federation Roadmap

The current demo uses admin-invites-user onboarding. The architectural follow-up (FF-572) replaces this with federated identity:

“Jordan signs in once with her CAC and the platform discovers her identity from your IdP. That federation work is documented in our identity architecture ADR. What you’re seeing today is the user-facing flow for federation-shaped self-service VDI. Same flow, different identity source. That’s how it scales from 50 users to 700,000.”

Known Limitations (Current Release)

  1. No workspace destruction — workspaces can be created but not destroyed through the UI. Manual cleanup requires 4 OpenStack/Guacamole commands. (FF-584)
  2. Duplicate workspace names — re-provisioning for a username that already has a workspace fails on the Guacamole connection name collision. Cleanup required between runs.
  3. Single workspace type — “Data Scientist” template (GPU pool) is not yet implemented. Knowledge Worker only.
  4. No quota management — no limits on requests per user or total workspaces.