337 lines
16 KiB
Markdown
337 lines
16 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.5.2 (Public EdgeGuard + modal-first responsive Web UI + integrated Guacamole Access Auth)**
|
||
|
||
> 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.5.2 Public EdgeGuard
|
||
|
||
The optional public-VPS deployment now includes `sessionguard-edgeguard`, a small Go service called by Caddy before requests reach SessionGuard or Guacamole. It provides static CIDR blacklisting, global/per-IP/endpoint token-bucket limits, scanner-path detection, persistent temporary bans, method/host/URI guards and local metrics. Caddy is additionally hardened with strict SNI/Host matching, a request-header timeout and smaller header ceiling. This protects application backends against common Internet abuse; provider-side DDoS filtering is still required for attacks that saturate the network link. See `docs/EDGE-SECURITY.md` and `deploy/public-vps-netbird/`.
|
||
|
||
## 0.5.1 Web UI refresh
|
||
|
||
The Master and local Agent consoles now use a modal-first, responsive Vanilla-JavaScript interface. Server drill-down, Farm creation, Resource editing and local Agent policy editing no longer occupy permanent large inline forms. The Master server workspace uses tabs, Farms/Resources use compact cards, and the Agent groups operational data into a tabbed workspace. No Node.js/npm/frontend build step is required.
|
||
|
||
## 0.5.0 Integrated Guacamole Access Auth
|
||
|
||
- `sessionguard-master` can now act directly as Traefik ForwardAuth for Guacamole; no separate forward-auth container is required.
|
||
- PocketID OIDC login for Guacamole uses a separate `access_auth` configuration/cookie scope from the SessionGuard admin UI.
|
||
- Browser sessions are opaque, server-side and persisted in the existing Master store; expired/revoked sessions are denied even if Guacamole still has an old auth token.
|
||
- `X-Guacamole-User` is emitted only after a valid SessionGuard access-session check. There is no fallback identity.
|
||
- OIDC RP-initiated logout and PocketID back-channel logout are supported; back-channel tokens are signature/audience/event/time/JTI checked with replay protection.
|
||
- The Guacamole extension now includes plain JavaScript which detects Guacamole logout and performs the full SessionGuard/PocketID logout, while also polling access-session status to close stale browser sessions.
|
||
- Recommended deployment exposes `/_sessionguard/auth/*` on the Guacamole hostname and routes that prefix to the same Master container.
|
||
|
||
See `docs/ACCESS-AUTH.md` for the PocketID and Traefik migration.
|
||
|
||
## 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_TSPublishedApplication` inventory 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.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 with Authorization Code + PKCE (S256).
|
||
- 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.5.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.
|