# SessionGuard + 3x Guacamole + NetBird + public Caddy

Target design for about 200-250 concurrent Guacamole/RDS sessions:

```text
Internet
   |
   v
PUBLIC01 (Caddy + NetBird)
   |  HTTPS termination / SessionGuard ForwardAuth / sticky LB
   |  NetBird/WireGuard
   +--> GUAC01 Docker subnet --> Guacamole01 --> guacd01 --> RDS farm
   +--> GUAC02 Docker subnet --> Guacamole02 --> guacd02 --> RDS farm
   +--> GUAC03 Docker subnet --> Guacamole03 --> guacd03 --> RDS farm
   +--> SESSIONGUARD01 Docker subnet --> SessionGuard Master
                                      --> shared Guacamole PostgreSQL
```

## Important: Guacamole is configured once, not three times

All three workers connect to the **same Guacamole PostgreSQL database** at
`10.202.0.20:5432`.

Therefore these are shared automatically:

- Guacamole users
- connection definitions
- connection groups
- permissions
- user preferences stored in PostgreSQL
- the Guacamole connection IDs used by SessionGuard resources

Every worker must use the same:

- SessionGuard-enabled Guacamole image/version
- PostgreSQL credentials/database
- `SESSIONGUARD_BROKER_API_KEY`
- Header-Auth configuration

There is **no PocketID/OIDC configuration on the individual Guacamole workers**.

## PocketID flow

PocketID is configured once through SessionGuard:

```text
Browser -> https://ts.hilden.info
        -> public Caddy
        -> SessionGuard /auth/verify
        -> PocketID if no valid access session
        -> SessionGuard OIDC callback
        -> Caddy injects X-Guacamole-User
        -> selected Guacamole worker
```

Use one PocketID confidential client for Guacamole access:

- Redirect URI: `https://ts.hilden.info/_sessionguard/auth/oidc/callback`
- Back-channel logout callback: `https://ts.hilden.info/_sessionguard/auth/backchannel-logout`
- Post logout redirect: `https://ts.hilden.info/`
- Scopes: `openid profile email groups`

Use a second PocketID client for the SessionGuard administration UI:

- Redirect URI: `https://sessionguard.hilden.info/oidc/callback`

## DNS

Only the public VPS needs public DNS:

```text
ts.hilden.info            -> PUBLIC01 public IP
sessionguard.hilden.info  -> PUBLIC01 public IP
```

`auth.hilden.info` remains wherever PocketID currently runs. It only needs to be
moved behind PUBLIC01 if you intentionally want that architecture too.

The Guacamole workers and SessionGuard host need no public A records.

## Docker subnet plan

The example intentionally uses unique, fixed subnets:

```text
GUAC01           10.201.1.0/24   Guacamole 10.201.1.10
GUAC02           10.201.2.0/24   Guacamole 10.201.2.10
GUAC03           10.201.3.0/24   Guacamole 10.201.3.10
SESSIONGUARD01   10.202.0.0/24   Master    10.202.0.10
                                    SG DB     10.202.0.11 (NOT routed)
                                    Guac DB   10.202.0.20
```

Change them if they overlap any existing LAN, Docker, VPN or NetBird routes.

## Deployment order

1. Deploy SessionGuard stack.
2. Deploy GUAC01/02/03 using the same `guac-worker/docker-compose.yml` but the
   matching `.env` values.
3. Register all four on-prem hosts as NetBird peers.
4. Configure the NetBird Networks described in `NETBIRD-NETWORKS.md`.
5. Verify PUBLIC01 can reach the three Guacamole IPs and SessionGuard health.
6. Deploy PUBLIC01 Caddy stack.
7. Point public DNS for `ts.hilden.info` and `sessionguard.hilden.info` to PUBLIC01.
8. Configure the PocketID callback URLs.
9. Test `/ _sessionguard/auth/status` (without the space) and then Guacamole.

## Guacamole N+1 behavior

Caddy uses a signed `guac_node` load-balancer cookie. A browser stays on the same
Guacamole webapp while that worker is healthy. If a worker fails, new requests can
move to another worker, but an already active remote desktop tunnel on the failed
worker must reconnect. The RDS session itself can survive and SessionGuard can
broker the reconnect back to its existing RDS host.

For a 250-session peak, three workers imply about 83 sessions/worker normally and
125 sessions/worker with one worker down. Validate that target with your actual
applications and display settings.

## RDP target DNS

Your Guacamole connection currently uses:

```text
${SESSIONGUARD_HOST}.stadt-hilden.de
```

Ensure the Guacamole containers can resolve the internal `stadt-hilden.de` zone.
If NetBird DNS overrides resolver settings on the hosts, configure a NetBird DNS
nameserver/match-domain for your AD/internal DNS zone.

## Updating a worker

Because the three workers are stateless apart from the shared `drive` volume and
shared PostgreSQL state, drain one worker at the Caddy/NetBird level, update it,
then return it to service. Never create three independent Guacamole databases.

## Public edge security (v0.5.2)

The `public-vps/` stack now contains SessionGuard EdgeGuard in front of both public hosts. EdgeGuard provides CIDR blacklisting, NAT-friendly rate limiting, scanner detection, bounded per-IP state, persistent temporary bans and localhost-only metrics. The Caddy example disables its admin API, uses bounded/sampled access logs, pins Caddy 2.11.4, and exposes only the proxy service publicly. See `EDGE-SECURITY.md`.
