SessionGuard
SessionGuard is a Go-based RDS control plane for Windows Remote Desktop Session Hosts. It is designed to complement Apache Guacamole: Guacamole remains the HTML5/RDP gateway, while SessionGuard provides Citrix-like broker, Director, policy, profile-lifecycle and operations functions.
Current development version: 0.4.1 (RemoteApp PowerShell/CLIXML robustness)
SessionGuard is not an ICA/HDX implementation and does not replace the Windows RDS runtime. It deliberately reuses standard RDP/WTS, Guacamole and PocketID/OIDC.
0.4.1 RemoteApp robustness
Windows PowerShell auxiliary streams are now isolated from the JSON protocol used by RemoteApp discovery/reconciliation. CLIXML/progress noise no longer breaks RemoteApp status decoding.
0.4.0 RemoteApp control plane
- The Master can define RemoteApp desired state per Published Resource/Farm.
- Windows Agents discover the local
Win32_TSPublishedApplicationinventory through the documented Terminal Services WMI provider. - For resources with Agent-managed RemoteApp publication enabled, the Agent creates/updates the local RemoteApp registration and removes only aliases that the Agent itself originally created. Existing/manual RemoteApps may be adopted for desired-state checks but are never deleted merely because management is later disabled.
- Executable presence, publication state and desired-state convergence are reported back in every Agent snapshot.
- The broker fails closed per managed RemoteApp: a host is eligible only when that application is present, published and in sync. Desktop resources and unmanaged RemoteApps keep their previous placement behavior.
- Master and Agent WebUIs show RemoteApp configuration/readiness without Node.js or a frontend build chain.
- Agent protocol version is now
4; upgrade Master and Agents together.
The local Terminal Services WMI provider is used instead of direct registry manipulation. This mode should be canary-tested on the exact Windows Server/RDSH versions in your environment; it does not claim to recreate every Microsoft Connection Broker/Collection management semantic.
See docs/REMOTEAPP.md for the canary, ownership/rollback behavior and Guacamole mapping.
0.3.4 Modern Web UI
- Master and Agent consoles use a responsive enterprise-style sidebar and dashboard layout.
- Dark/light theme switching is implemented with plain CSS and Vanilla JavaScript.
- Modern status badges, cards, forms, switches and tables without Node.js, npm, Vue, React or a frontend build step.
- The existing refresh-safe editor behavior remains intact: live telemetry can update without replacing forms currently being edited.
0.3.3 Farm UI regression fix
The master UI now maintains an explicit agentCache from the dashboard response before rendering farm membership. This fixes ReferenceError: agentCache is not defined introduced by the v0.3.2 farm-count display correction.
0.3.1 UI stability fix
- Live dashboard refresh no longer recreates editable Master forms.
- Server live metrics and server-control inputs are rendered independently.
- Farm and Published Resource editors remain mounted while their live tables refresh.
- Agent policy edits are protected by a dirty-state guard and an explicit reload action.
- HTML and
app.jsare served withCache-Control: no-storeto prevent stale UI code after upgrades.
0.3.0 at a glance
Smart broker and farms
- Logical farms with explicit agent membership and/or required tags.
- Published resources for full desktops and RemoteApps.
- Existing-session reconnect affinity before new load balancing.
- Persistent user leases to reduce duplicate sessions across hosts.
- Optional global single-session-per-user behavior.
- Health-aware placement using server health, sessions, CPU and memory pressure.
online,drainandmaintenanceserver modes.restart_when_drained: stop new allocations, wait for all user sessions to leave, then queue a controlled restart.- Farm and global policy inheritance, plus per-agent override.
Guacamole 1.6 bridge
guacamole-extension/ contains a small Java extension using Guacamole's extension API. It does not authenticate users. It decorates the already authenticated Guacamole user context, asks the SessionGuard broker for connection tokens and injects:
${SESSIONGUARD_HOST}${SESSIONGUARD_AGENT_ID}${SESSIONGUARD_RESOURCE_ID}${SESSIONGUARD_REMOTE_APP}${SESSIONGUARD_REMOTE_APP_DIR}${SESSIONGUARD_REMOTE_APP_ARGS}
This keeps PocketID / header authentication and the browser gateway separate from broker logic. See docs/GUACAMOLE.md.
Director-style operations
- Consolidated server and session inventory.
- RDS logon/connect/last-input timestamps and calculated idle time.
- Per-session process inventory and remote process termination.
- CPU, memory and system-disk telemetry.
- RDP-listener and profile-store health checks.
- Composite server health score used by both the UI and broker.
- Session history for logon, reconnect, disconnect, logoff and SessionGuard
logon_readyevents. - Logon telemetry including SessionGuard restore duration and observed time until the post-restore/template stage is ready.
- Active alerts for offline agents, CPU/RAM/disk/health, profile failures, disconnected-session count and slow observed logon.
- Optional generic JSON webhook notifications with notification throttling.
- Prometheus-compatible
/metricsendpoint.
Enterprise control plane
- PostgreSQL persistence for production with a database advisory lock preventing accidental active/active masters.
- Append-only audit and session-history tables with retention and indexes.
- JSONB control-plane state for agents, farms, resources, leases, policies and alerts.
- JSON-file persistence remains available as a small single-node fallback / migration path.
- PocketID / generic OIDC authentication.
- Group-to-role RBAC.
- Policy version history and rollback for global, farm and agent policies.
- Agent-initiated HTTPS heartbeats and command delivery only; no inbound management port from master to RDS hosts.
- Expiring, deduplicated command queue with result acknowledgement and audit trail.
Windows agent and profile lifecycle
The v0.2 functionality remains:
- Windows service with native WTS session inventory and session-change wakeups.
- Send message, disconnect and logoff user sessions.
- Automatic logoff of disconnected sessions after a persistent configurable timeout.
- Backup of selected profile folders after logoff.
- Versioned transactional profile snapshots (
staging -> current). - Restore of the last successful snapshot on a newly observed logon.
- A failed backup blocks local profile deletion.
- Cleanup with grace period, exclusions, allowed profile roots, final WTS recheck and
DeleteProfileW. - Structured templates for files, folders,
.urland.lnkshortcuts. - Local PocketID/OIDC-protected fallback UI on every agent.
See docs/PROFILE-LIFECYCLE.md for the deliberately limited scope of profile synchronization.
Target architecture
PocketID
|
Traefik / trusted header-auth boundary
|
Apache Guacamole 1.6
+ sessionguard-guacamole extension
|
| broker token request
v
SessionGuard Master ---------------- PostgreSQL
|
| outbound agent heartbeats carry policy/commands/results
|
+-------------+-------------+
| | |
RDS01 RDS02 RDS03
Agent Agent Agent
Quick start: master
Copy the example configuration and environment file:
cp configs/master.example.json deploy/master.json
cp deploy/.env.example deploy/.env
Set at least:
SESSIONGUARD_DB_PASSWORDSESSIONGUARD_ENROLLMENT_TOKENSESSIONGUARD_BROKER_API_KEYSESSIONGUARD_OIDC_CLIENT_SECRET- PocketID issuer/client/redirect values in
master.json TRAEFIK_NETWORK
Then:
cd deploy
docker compose up -d --build
For production, expose the master only through your existing TLS reverse proxy. The bundled compose publishes port 8080 only on 127.0.0.1 as an additional safety measure.
Quick start: Windows agent
Build on a machine with Go module access:
.\scripts\build.ps1
Copy configs/agent.example.json, configure PocketID, the master URL and your initial policy, then install from an elevated PowerShell:
.\scripts\install-agent.ps1 -BinaryPath .\dist\sessionguard-agent.exe -ConfigPath .\configs\agent.json
For SMB profile stores, prefer a gMSA and grant it the required share/NTFS permissions. Do not store SMB passwords in the agent JSON.
Guacamole integration
Build a Guacamole image containing the extension using deploy/guacamole/Dockerfile.guacamole. Add these environment variables to Guacamole:
SESSIONGUARD_MASTER_URL: http://sessionguard-master:8080
SESSIONGUARD_BROKER_API_KEY: ${SESSIONGUARD_BROKER_API_KEY}
SESSIONGUARD_BROKER_TIMEOUT_MS: "2500"
A brokered desktop connection uses:
hostname = ${SESSIONGUARD_HOST}
A RemoteApp can additionally use the SESSIONGUARD_REMOTE_APP* tokens. Map the Guacamole connection ID or name to a SessionGuard Resource in the master UI.
Important: with Guacamole header authentication, untrusted containers or networks must not be able to reach Guacamole directly and forge the authentication header. See docs/GUACAMOLE.md and docs/SECURITY.md.
Master environment overrides
Secrets do not need to be stored in JSON:
| Variable | Purpose |
|---|---|
SESSIONGUARD_DATABASE_URL |
PostgreSQL DSN |
SESSIONGUARD_ENROLLMENT_TOKEN |
one-time agent enrollment shared secret |
SESSIONGUARD_BROKER_API_KEY |
Guacamole-extension → broker credential |
SESSIONGUARD_OIDC_CLIENT_SECRET |
master PocketID/OIDC client secret |
SESSIONGUARD_ALERT_WEBHOOK_URL |
optional alert webhook |
Agent overrides:
| Variable | Purpose |
|---|---|
SESSIONGUARD_MASTER_URL |
master URL |
SESSIONGUARD_ENROLLMENT_TOKEN |
enrollment secret |
SESSIONGUARD_OIDC_CLIENT_SECRET |
local agent UI OIDC secret |
Broker placement order
- Resolve an enabled Published Resource if one matches the requested Guacamole connection ID/name.
- Determine the requested/default farm.
- If enabled, reconnect a matching existing Active/Connected/Disconnected session in that farm. A
drainhost may be used for this;maintenancemay not. - Reuse a non-expired matching user lease if possible.
- Select only online, healthy,
online-mode members of the farm. - Score candidates using health, active/disconnected sessions, CPU and memory pressure.
- Create/refresh the user lease and return connection tokens.
A farm boundary is fail-closed: a session on another farm is not reused for a request for the selected farm.
Policy precedence
agent override
> explicit farm membership (order in agent.farm_ids)
> centrally selected farm (agent_ids / required_tags, deterministic farm-ID order)
> global policy
All policy writes are normalized and validated before activation. Every global/farm/agent policy write creates a version that can be rolled back from the UI/API.
RBAC roles
Built-in roles are:
viewerhelpdeskoperatorprofile_adminpolicy_adminauditoradmin
PocketID groups claims are mapped in master.json. If the RBAC group mapping is intentionally empty, SessionGuard preserves backwards compatibility and grants authenticated users the admin role; do not leave it empty in a multi-admin production environment.
See docs/RBAC.md.
Profile scope and safety
SessionGuard synchronizes selected profile folders, not a complete Windows profile. Do not configure NTUSER.DAT, registry hives, the whole profile root, EFS material or Windows-owned profile internals as roaming content.
Good candidates include:
AppData\Roaming\Vendor\ProductDesktopDocuments- application-owned configuration folders safe to merge after logon
If the profile backup fails, cleanup is not scheduled. If the user reconnects before snapshot activation or immediately before cleanup, the dangerous operation is cancelled.
Build and test
Requirements:
- Go 1.23+
- Windows Server 2019/2022/2025 x64 for agent integration testing
- Maven/JDK 11+ for the Guacamole extension
- PostgreSQL 17 recommended for production
Go:
go mod tidy
go test ./...
go vet ./...
Guacamole extension:
cd guacamole-extension
mvn -DskipTests package
The current source tree contains unit tests for configuration, templates, profile snapshot behavior and broker placement invariants. A real release should additionally pass the Windows/RDS/SMB/PostgreSQL integration matrix in docs/TESTING.md.
Documentation
docs/ARCHITECTURE.md– components, state and trust boundariesdocs/BROKER.md– farms, resources, leases, scoring and maintenance statesdocs/DIRECTOR.md– telemetry, history, processes, alerts and health scoringdocs/GUACAMOLE.md– extension and integration with Guacamole/PocketIDdocs/PROFILE-LIFECYCLE.md– profile backup/restore/cleanup pipelinedocs/RBAC.md– roles and PocketID group mappingdocs/API.md– HTTP APIdocs/OPERATIONS.md– deployment, upgrade, backup and recoverydocs/SECURITY.md– security model and hardeningdocs/TESTING.md– release and failure-mode test matrixCHANGELOG.md– release changes
Production-candidate status
The design intentionally fails closed around destructive profile operations and broker farm boundaries. Nevertheless, v0.4.0 should be introduced as a canary before broad production rollout. In particular, validate native WTS behavior, SMB failure/recovery, PostgreSQL backup/restore, Guacamole extension loading and your exact PocketID group claims in your environment.