299 lines
12 KiB
Markdown
299 lines
12 KiB
Markdown
# 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.3.4 (Broker & Director production candidate)**
|
||
|
||
> 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.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.js` are served with `Cache-Control: no-store` to 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`, `drain` and `maintenance` server 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_ready` events.
|
||
- 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 `/metrics` endpoint.
|
||
|
||
### 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, `.url` and `.lnk` shortcuts.
|
||
- Local PocketID/OIDC-protected fallback UI on every agent.
|
||
|
||
See `docs/PROFILE-LIFECYCLE.md` for the deliberately limited scope of profile synchronization.
|
||
|
||
## Target architecture
|
||
|
||
```text
|
||
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:
|
||
|
||
```bash
|
||
cp configs/master.example.json deploy/master.json
|
||
cp deploy/.env.example deploy/.env
|
||
```
|
||
|
||
Set at least:
|
||
|
||
- `SESSIONGUARD_DB_PASSWORD`
|
||
- `SESSIONGUARD_ENROLLMENT_TOKEN`
|
||
- `SESSIONGUARD_BROKER_API_KEY`
|
||
- `SESSIONGUARD_OIDC_CLIENT_SECRET`
|
||
- PocketID issuer/client/redirect values in `master.json`
|
||
- `TRAEFIK_NETWORK`
|
||
|
||
Then:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```powershell
|
||
.\scripts\build.ps1
|
||
```
|
||
|
||
Copy `configs/agent.example.json`, configure PocketID, the master URL and your initial policy, then install from an elevated PowerShell:
|
||
|
||
```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:
|
||
|
||
```yaml
|
||
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:
|
||
|
||
```text
|
||
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
|
||
|
||
1. Resolve an enabled Published Resource if one matches the requested Guacamole connection ID/name.
|
||
2. Determine the requested/default farm.
|
||
3. If enabled, reconnect a matching existing Active/Connected/Disconnected session in that farm. A `drain` host may be used for this; `maintenance` may not.
|
||
4. Reuse a non-expired matching user lease if possible.
|
||
5. Select only online, healthy, `online`-mode members of the farm.
|
||
6. Score candidates using health, active/disconnected sessions, CPU and memory pressure.
|
||
7. 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
|
||
|
||
```text
|
||
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:
|
||
|
||
- `viewer`
|
||
- `helpdesk`
|
||
- `operator`
|
||
- `profile_admin`
|
||
- `policy_admin`
|
||
- `auditor`
|
||
- `admin`
|
||
|
||
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\Product`
|
||
- `Desktop`
|
||
- `Documents`
|
||
- 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:
|
||
|
||
```bash
|
||
go mod tidy
|
||
go test ./...
|
||
go vet ./...
|
||
```
|
||
|
||
Guacamole extension:
|
||
|
||
```bash
|
||
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 boundaries
|
||
- `docs/BROKER.md` – farms, resources, leases, scoring and maintenance states
|
||
- `docs/DIRECTOR.md` – telemetry, history, processes, alerts and health scoring
|
||
- `docs/GUACAMOLE.md` – extension and integration with Guacamole/PocketID
|
||
- `docs/PROFILE-LIFECYCLE.md` – profile backup/restore/cleanup pipeline
|
||
- `docs/RBAC.md` – roles and PocketID group mapping
|
||
- `docs/API.md` – HTTP API
|
||
- `docs/OPERATIONS.md` – deployment, upgrade, backup and recovery
|
||
- `docs/SECURITY.md` – security model and hardening
|
||
- `docs/TESTING.md` – release and failure-mode test matrix
|
||
- `CHANGELOG.md` – release changes
|
||
|
||
## Production-candidate status
|
||
|
||
The design intentionally fails closed around destructive profile operations and broker farm boundaries. Nevertheless, v0.3.4 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.
|