RC-1
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
/bin/
|
||||
*.exe
|
||||
*.log
|
||||
state.json
|
||||
master.json
|
||||
|
||||
105
production/NETBIRD-NETWORKS.md
Normal file
105
production/NETBIRD-NETWORKS.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# NetBird Networks / Routing-Peer setup
|
||||
|
||||
Use **Networks** (the newer NetBird model), not legacy Network Routes.
|
||||
|
||||
The NetBird client runs with `network_mode: host` on each Docker host. Each Docker
|
||||
host is a routing peer for only the private Docker resource(s) that must be
|
||||
reachable remotely. None of the application containers publishes a host port.
|
||||
|
||||
## Peer groups
|
||||
|
||||
Create these peer groups:
|
||||
|
||||
- `public-proxy`: PUBLIC01
|
||||
- `guac-workers`: GUAC01, GUAC02, GUAC03
|
||||
- `sessionguard-host`: SESSIONGUARD01
|
||||
|
||||
## Network: guac01-docker
|
||||
|
||||
Routing peer: `GUAC01`
|
||||
|
||||
Resource:
|
||||
|
||||
- Name: `guac01-web`
|
||||
- Address: `10.201.1.10`
|
||||
- Type: IP
|
||||
- Resource group: `guacamole-web`
|
||||
|
||||
Policy:
|
||||
|
||||
- Source: `public-proxy`
|
||||
- Destination/resource: `guac01-web`
|
||||
- Protocol: TCP
|
||||
- Port: 8080
|
||||
|
||||
Repeat with:
|
||||
|
||||
- GUAC02 -> `10.201.2.10:8080`
|
||||
- GUAC03 -> `10.201.3.10:8080`
|
||||
|
||||
Do **not** advertise the guacd addresses (`.11`). guacd has no authentication and
|
||||
must remain reachable only inside its local Docker bridge.
|
||||
|
||||
## Network: sessionguard-docker
|
||||
|
||||
Routing peer: `SESSIONGUARD01`
|
||||
|
||||
Resources:
|
||||
|
||||
1. `sessionguard-master`
|
||||
- Address: `10.202.0.10`
|
||||
- Type: IP
|
||||
|
||||
2. `guacamole-db`
|
||||
- Address: `10.202.0.20`
|
||||
- Type: IP
|
||||
|
||||
Policies:
|
||||
|
||||
- `public-proxy` -> `sessionguard-master`, TCP 8080
|
||||
- `guac-workers` -> `sessionguard-master`, TCP 8080
|
||||
- `guac-workers` -> `guacamole-db`, TCP 5432
|
||||
|
||||
Do **not** create a resource for `10.202.0.11` (SessionGuard PostgreSQL).
|
||||
That database remains private to the SessionGuard Docker bridge.
|
||||
|
||||
## IP forwarding
|
||||
|
||||
Linux routing peers normally enable forwarding automatically through NetBird.
|
||||
If forwarding is blocked by host policy, persist it explicitly:
|
||||
|
||||
```bash
|
||||
sudo sysctl -w net.ipv4.ip_forward=1
|
||||
printf 'net.ipv4.ip_forward=1\n' | sudo tee /etc/sysctl.d/99-netbird-routing.conf
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
On each routing peer:
|
||||
|
||||
```bash
|
||||
netbird status -d
|
||||
ip route
|
||||
```
|
||||
|
||||
From PUBLIC01, after the Networks are distributed:
|
||||
|
||||
```bash
|
||||
curl -I http://10.201.1.10:8080/
|
||||
curl -I http://10.201.2.10:8080/
|
||||
curl -I http://10.201.3.10:8080/
|
||||
curl -I http://10.202.0.10:8080/healthz
|
||||
```
|
||||
|
||||
From a Guacamole worker container:
|
||||
|
||||
```bash
|
||||
# SessionGuard broker/control plane
|
||||
wget -S -O- http://10.202.0.10:8080/healthz
|
||||
|
||||
# Shared Guacamole PostgreSQL
|
||||
# use pg_isready/psql from a temporary postgres client container if needed
|
||||
```
|
||||
|
||||
For production, check that PUBLIC01 <-> routing peers are **P2P/direct**, not
|
||||
permanently relayed, using `netbird status -d`.
|
||||
137
production/README.md
Normal file
137
production/README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# 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.
|
||||
23
production/guac-worker/.env.guac01.example
Normal file
23
production/guac-worker/.env.guac01.example
Normal file
@@ -0,0 +1,23 @@
|
||||
WORKER_NAME=guac01
|
||||
NETBIRD_VERSION=latest
|
||||
NETBIRD_MANAGEMENT_URL=https://netbird.example.org
|
||||
NETBIRD_SETUP_KEY=REPLACE_ME_GUAC01
|
||||
NETBIRD_LOG_LEVEL=info
|
||||
|
||||
GUACAMOLE_VERSION=1.6.0
|
||||
GUACD_LOG_LEVEL=info
|
||||
SESSIONGUARD_GUAC_IMAGE=git.send.nrw/sendnrw/sessiongurad-guacamole:latest
|
||||
|
||||
# Unique Docker subnet for this host. It MUST NOT overlap any LAN/VPN/Docker subnet.
|
||||
GUAC_DOCKER_SUBNET=10.201.1.0/24
|
||||
GUAC_APP_IP=10.201.1.10
|
||||
GUACD_IP=10.201.1.11
|
||||
|
||||
# Routed through the SessionGuard routing peer.
|
||||
SESSIONGUARD_IP=10.202.0.10
|
||||
GUAC_DB_IP=10.202.0.20
|
||||
|
||||
SESSIONGUARD_BROKER_API_KEY=REPLACE_ME
|
||||
GUAC_DB_NAME=guacamole_db
|
||||
GUAC_DB_USER=guacamole_user
|
||||
GUAC_DB_PASSWORD=REPLACE_ME
|
||||
21
production/guac-worker/.env.guac02.example
Normal file
21
production/guac-worker/.env.guac02.example
Normal file
@@ -0,0 +1,21 @@
|
||||
WORKER_NAME=guac02
|
||||
NETBIRD_VERSION=latest
|
||||
NETBIRD_MANAGEMENT_URL=https://netbird.example.org
|
||||
NETBIRD_SETUP_KEY=REPLACE_ME_GUAC02
|
||||
NETBIRD_LOG_LEVEL=info
|
||||
|
||||
GUACAMOLE_VERSION=1.6.0
|
||||
GUACD_LOG_LEVEL=info
|
||||
SESSIONGUARD_GUAC_IMAGE=git.send.nrw/sendnrw/sessiongurad-guacamole:latest
|
||||
|
||||
GUAC_DOCKER_SUBNET=10.201.2.0/24
|
||||
GUAC_APP_IP=10.201.2.10
|
||||
GUACD_IP=10.201.2.11
|
||||
|
||||
SESSIONGUARD_IP=10.202.0.10
|
||||
GUAC_DB_IP=10.202.0.20
|
||||
|
||||
SESSIONGUARD_BROKER_API_KEY=REPLACE_ME
|
||||
GUAC_DB_NAME=guacamole_db
|
||||
GUAC_DB_USER=guacamole_user
|
||||
GUAC_DB_PASSWORD=REPLACE_ME
|
||||
21
production/guac-worker/.env.guac03.example
Normal file
21
production/guac-worker/.env.guac03.example
Normal file
@@ -0,0 +1,21 @@
|
||||
WORKER_NAME=guac03
|
||||
NETBIRD_VERSION=latest
|
||||
NETBIRD_MANAGEMENT_URL=https://netbird.example.org
|
||||
NETBIRD_SETUP_KEY=REPLACE_ME_GUAC03
|
||||
NETBIRD_LOG_LEVEL=info
|
||||
|
||||
GUACAMOLE_VERSION=1.6.0
|
||||
GUACD_LOG_LEVEL=info
|
||||
SESSIONGUARD_GUAC_IMAGE=git.send.nrw/sendnrw/sessiongurad-guacamole:latest
|
||||
|
||||
GUAC_DOCKER_SUBNET=10.201.3.0/24
|
||||
GUAC_APP_IP=10.201.3.10
|
||||
GUACD_IP=10.201.3.11
|
||||
|
||||
SESSIONGUARD_IP=10.202.0.10
|
||||
GUAC_DB_IP=10.202.0.20
|
||||
|
||||
SESSIONGUARD_BROKER_API_KEY=REPLACE_ME
|
||||
GUAC_DB_NAME=guacamole_db
|
||||
GUAC_DB_USER=guacamole_user
|
||||
GUAC_DB_PASSWORD=REPLACE_ME
|
||||
82
production/guac-worker/docker-compose.yml
Normal file
82
production/guac-worker/docker-compose.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
services:
|
||||
# Official NetBird Docker client pattern. Because this peer runs on the
|
||||
# Docker host network, it can act as a routing peer for GUAC_DOCKER_SUBNET.
|
||||
netbird:
|
||||
image: netbirdio/netbird:${NETBIRD_VERSION:-latest}
|
||||
container_name: netbird-${WORKER_NAME}
|
||||
hostname: ${WORKER_NAME}
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
- SYS_RESOURCE
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
environment:
|
||||
NB_SETUP_KEY: ${NETBIRD_SETUP_KEY:?NETBIRD_SETUP_KEY is required}
|
||||
NB_MANAGEMENT_URL: ${NETBIRD_MANAGEMENT_URL:?NETBIRD_MANAGEMENT_URL is required}
|
||||
NB_LOG_LEVEL: ${NETBIRD_LOG_LEVEL:-info}
|
||||
volumes:
|
||||
- netbird-client:/var/lib/netbird
|
||||
|
||||
guacd:
|
||||
image: guacamole/guacd:${GUACAMOLE_VERSION:-1.6.0}
|
||||
container_name: ${WORKER_NAME}-guacd
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LOG_LEVEL: ${GUACD_LOG_LEVEL:-info}
|
||||
networks:
|
||||
guac_internal:
|
||||
ipv4_address: ${GUACD_IP}
|
||||
volumes:
|
||||
- drive:/drive
|
||||
|
||||
guacamole:
|
||||
# Must contain the SessionGuard Broker + Access-Auth JS extension.
|
||||
image: ${SESSIONGUARD_GUAC_IMAGE:?SESSIONGUARD_GUAC_IMAGE is required}
|
||||
container_name: ${WORKER_NAME}-guacamole
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
guacd:
|
||||
condition: service_started
|
||||
environment:
|
||||
WEBAPP_CONTEXT: ROOT
|
||||
GUACD_HOSTNAME: ${GUACD_IP}
|
||||
GUACD_PORT: "4822"
|
||||
|
||||
# SessionGuard Broker over the routed private SessionGuard Docker subnet.
|
||||
SESSIONGUARD_MASTER_URL: http://${SESSIONGUARD_IP}:8080
|
||||
SESSIONGUARD_BROKER_API_KEY: ${SESSIONGUARD_BROKER_API_KEY:?SESSIONGUARD_BROKER_API_KEY is required}
|
||||
SESSIONGUARD_BROKER_TIMEOUT_MS: "2500"
|
||||
|
||||
# ALL Guacamole workers use the SAME PostgreSQL database.
|
||||
POSTGRESQL_ENABLED: "true"
|
||||
POSTGRESQL_HOSTNAME: ${GUAC_DB_IP}
|
||||
POSTGRESQL_PORT: "5432"
|
||||
POSTGRESQL_DATABASE: ${GUAC_DB_NAME:-guacamole_db}
|
||||
POSTGRESQL_USERNAME: ${GUAC_DB_USER:-guacamole_user}
|
||||
POSTGRESQL_PASSWORD: ${GUAC_DB_PASSWORD:?GUAC_DB_PASSWORD is required}
|
||||
POSTGRESQL_AUTO_CREATE_ACCOUNTS: "true"
|
||||
|
||||
# PocketID is NOT configured in Guacamole. The public Caddy calls
|
||||
# SessionGuard ForwardAuth and injects this trusted identity header.
|
||||
HTTP_AUTH_ENABLED: "true"
|
||||
HTTP_AUTH_HEADER: X-Guacamole-User
|
||||
REMOTE_IP_VALVE_ENABLED: "true"
|
||||
networks:
|
||||
guac_internal:
|
||||
ipv4_address: ${GUAC_APP_IP}
|
||||
volumes:
|
||||
- drive:/drive
|
||||
|
||||
networks:
|
||||
guac_internal:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: ${GUAC_DOCKER_SUBNET}
|
||||
|
||||
volumes:
|
||||
netbird-client:
|
||||
drive:
|
||||
47
production/install-agent.ps1
Normal file
47
production/install-agent.ps1
Normal file
@@ -0,0 +1,47 @@
|
||||
param(
|
||||
[string]$Binary = ".\bin\sessionguard-agent.exe",
|
||||
[string]$Config = ".\configs\agent.json",
|
||||
[string]$GmsaAccount = ""
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$service = 'SessionGuardAgent'
|
||||
$dest = 'C:\Program Files\SessionGuard'
|
||||
$data = 'C:\ProgramData\SessionGuard'
|
||||
New-Item -ItemType Directory -Force -Path $dest,$data | Out-Null
|
||||
|
||||
$existing = Get-Service -Name $service -ErrorAction SilentlyContinue
|
||||
if ($existing -and $existing.Status -ne 'Stopped') {
|
||||
Stop-Service -Name $service -Force
|
||||
$existing.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(20))
|
||||
}
|
||||
|
||||
Copy-Item $Binary "$dest\sessionguard-agent.exe" -Force
|
||||
if (Test-Path $Config) {
|
||||
Copy-Item $Config "$data\agent.json" -Force
|
||||
} elseif (-not (Test-Path "$data\agent.json")) {
|
||||
throw "Config '$Config' does not exist and no existing $data\agent.json was found."
|
||||
}
|
||||
|
||||
# Protect credentials, enrollment material and the persisted per-agent token/state.
|
||||
icacls $data /inheritance:r /grant:r 'SYSTEM:(OI)(CI)(F)' 'Administrators:(OI)(CI)(F)' | Out-Null
|
||||
if ($GmsaAccount) {
|
||||
icacls $data /grant "$GmsaAccount`:(OI)(CI)(F)" | Out-Null
|
||||
}
|
||||
|
||||
if (-not $existing) {
|
||||
& "$dest\sessionguard-agent.exe" -config "$data\agent.json" -service install
|
||||
}
|
||||
|
||||
# Enterprise service defaults: delayed start and automatic recovery after crashes.
|
||||
& sc.exe config $service start= delayed-auto | Out-Null
|
||||
& sc.exe failure $service reset= 86400 actions= restart/5000/restart/15000/restart/60000 | Out-Null
|
||||
& sc.exe failureflag $service 1 | Out-Null
|
||||
|
||||
if ($GmsaAccount) {
|
||||
# gMSA accounts are specified as DOMAIN\name$ and use an empty service password.
|
||||
& sc.exe config $service obj= $GmsaAccount password= "" | Out-Null
|
||||
}
|
||||
|
||||
Start-Service -Name $service
|
||||
Write-Host "SessionGuard Agent installed/updated and started."
|
||||
if ($GmsaAccount) { Write-Host "Service identity: $GmsaAccount" } else { Write-Host "Service identity: LocalSystem" }
|
||||
21
production/public-vps/.env.example
Normal file
21
production/public-vps/.env.example
Normal file
@@ -0,0 +1,21 @@
|
||||
# Public VPS / edge
|
||||
NETBIRD_VERSION=latest
|
||||
CADDY_VERSION=2
|
||||
NETBIRD_PEER_NAME=public-proxy
|
||||
NETBIRD_MANAGEMENT_URL=https://netbird.example.org
|
||||
NETBIRD_SETUP_KEY=REPLACE_ME
|
||||
NETBIRD_LOG_LEVEL=info
|
||||
|
||||
# Public DNS names. Both A/AAAA records point to this VPS.
|
||||
GUAC_HOST=ts.hilden.info
|
||||
SESSIONGUARD_HOST=sessionguard.hilden.info
|
||||
ACME_EMAIL=admin@example.org
|
||||
|
||||
# Fixed private Docker addresses advertised through NetBird Networks.
|
||||
GUAC01_IP=10.201.1.10
|
||||
GUAC02_IP=10.201.2.10
|
||||
GUAC03_IP=10.201.3.10
|
||||
SESSIONGUARD_IP=10.202.0.10
|
||||
|
||||
# Generate e.g. with: openssl rand -hex 32
|
||||
GUAC_LB_SECRET=REPLACE_WITH_RANDOM_SECRET
|
||||
67
production/public-vps/Caddyfile
Normal file
67
production/public-vps/Caddyfile
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
email {$ACME_EMAIL}
|
||||
}
|
||||
|
||||
# Guacamole public endpoint + SessionGuard Access Auth
|
||||
{$GUAC_HOST} {
|
||||
encode zstd gzip
|
||||
|
||||
# These endpoints belong to SessionGuard, but intentionally live on the
|
||||
# Guacamole hostname so the Access-Auth cookie remains host-bound.
|
||||
# handle_path removes /_sessionguard before proxying.
|
||||
handle_path /_sessionguard/* {
|
||||
reverse_proxy {$SESSIONGUARD_IP}:8080
|
||||
}
|
||||
|
||||
handle {
|
||||
route {
|
||||
# Never trust identity headers supplied by an Internet client.
|
||||
request_header -X-Guacamole-User
|
||||
request_header -X-SessionGuard-User
|
||||
request_header -X-SessionGuard-Email
|
||||
request_header -X-SessionGuard-Groups
|
||||
request_header -X-Forwarded-User
|
||||
request_header -X-Authenticated-User
|
||||
|
||||
# SessionGuard is the single OIDC/ForwardAuth authority.
|
||||
forward_auth {$SESSIONGUARD_IP}:8080 {
|
||||
uri /auth/verify
|
||||
copy_headers {
|
||||
X-Guacamole-User
|
||||
X-SessionGuard-User
|
||||
X-SessionGuard-Email
|
||||
X-SessionGuard-Groups
|
||||
}
|
||||
}
|
||||
|
||||
# Sticky sessions are important because Guacamole keeps runtime
|
||||
# authentication/session state in the selected webapp process.
|
||||
reverse_proxy {$GUAC01_IP}:8080 {$GUAC02_IP}:8080 {$GUAC03_IP}:8080 {
|
||||
lb_policy cookie guac_node {$GUAC_LB_SECRET}
|
||||
lb_try_duration 5s
|
||||
lb_try_interval 250ms
|
||||
|
||||
# Guacamole with WEBAPP_CONTEXT=ROOT serves / as its frontend.
|
||||
health_uri /
|
||||
health_interval 10s
|
||||
health_timeout 3s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log {
|
||||
output stdout
|
||||
format console
|
||||
}
|
||||
}
|
||||
|
||||
# SessionGuard administration UI, Agent endpoint and APIs.
|
||||
{$SESSIONGUARD_HOST} {
|
||||
encode zstd gzip
|
||||
reverse_proxy {$SESSIONGUARD_IP}:8080
|
||||
|
||||
log {
|
||||
output stdout
|
||||
format console
|
||||
}
|
||||
}
|
||||
45
production/public-vps/docker-compose.yml
Normal file
45
production/public-vps/docker-compose.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
services:
|
||||
netbird:
|
||||
image: netbirdio/netbird:${NETBIRD_VERSION:-latest}
|
||||
container_name: netbird-public-proxy
|
||||
hostname: ${NETBIRD_PEER_NAME:-public-proxy}
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
- SYS_RESOURCE
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
environment:
|
||||
NB_SETUP_KEY: ${NETBIRD_SETUP_KEY:?NETBIRD_SETUP_KEY is required}
|
||||
NB_MANAGEMENT_URL: ${NETBIRD_MANAGEMENT_URL:?NETBIRD_MANAGEMENT_URL is required}
|
||||
NB_LOG_LEVEL: ${NETBIRD_LOG_LEVEL:-info}
|
||||
volumes:
|
||||
- netbird-client:/var/lib/netbird
|
||||
|
||||
caddy:
|
||||
image: caddy:${CADDY_VERSION:-2}
|
||||
container_name: caddy-public
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
depends_on:
|
||||
- netbird
|
||||
environment:
|
||||
ACME_EMAIL: ${ACME_EMAIL:?ACME_EMAIL is required}
|
||||
GUAC_HOST: ${GUAC_HOST:-ts.hilden.info}
|
||||
SESSIONGUARD_HOST: ${SESSIONGUARD_HOST:-sessionguard.hilden.info}
|
||||
GUAC01_IP: ${GUAC01_IP:?GUAC01_IP is required}
|
||||
GUAC02_IP: ${GUAC02_IP:?GUAC02_IP is required}
|
||||
GUAC03_IP: ${GUAC03_IP:?GUAC03_IP is required}
|
||||
SESSIONGUARD_IP: ${SESSIONGUARD_IP:?SESSIONGUARD_IP is required}
|
||||
GUAC_LB_SECRET: ${GUAC_LB_SECRET:?GUAC_LB_SECRET is required}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
|
||||
volumes:
|
||||
netbird-client:
|
||||
caddy-data:
|
||||
caddy-config:
|
||||
BIN
production/sessionguard-agent.exe
Normal file
BIN
production/sessionguard-agent.exe
Normal file
Binary file not shown.
31
production/sessionguard/.env.example
Normal file
31
production/sessionguard/.env.example
Normal file
@@ -0,0 +1,31 @@
|
||||
NETBIRD_VERSION=latest
|
||||
NETBIRD_PEER_NAME=sessionguard01
|
||||
NETBIRD_MANAGEMENT_URL=https://netbird.example.org
|
||||
NETBIRD_SETUP_KEY=REPLACE_ME_SESSIONGUARD01
|
||||
NETBIRD_LOG_LEVEL=info
|
||||
|
||||
POSTGRES_VERSION=17
|
||||
GUACAMOLE_VERSION=1.6.0
|
||||
|
||||
# Preserve your actual registry spelling. Change sessiongurad -> sessionguard if your repo uses the corrected name.
|
||||
SESSIONGUARD_IMAGE=git.send.nrw/sendnrw/sessiongurad:latest
|
||||
|
||||
# Unique Docker subnet advertised through this NetBird routing peer.
|
||||
SESSIONGUARD_DOCKER_SUBNET=10.202.0.0/24
|
||||
SESSIONGUARD_APP_IP=10.202.0.10
|
||||
SESSIONGUARD_DB_IP=10.202.0.11
|
||||
GUAC_DB_IP=10.202.0.20
|
||||
|
||||
SESSIONGUARD_DB_NAME=sessionguard
|
||||
SESSIONGUARD_DB_USER=sessionguard
|
||||
SESSIONGUARD_DB_PASSWORD=REPLACE_ME
|
||||
|
||||
GUAC_DB_NAME=guacamole_db
|
||||
GUAC_DB_USER=guacamole_user
|
||||
GUAC_DB_PASSWORD=REPLACE_ME
|
||||
|
||||
SESSIONGUARD_ENROLLMENT_TOKEN=REPLACE_ME
|
||||
SESSIONGUARD_BROKER_API_KEY=REPLACE_ME
|
||||
SESSIONGUARD_OIDC_CLIENT_SECRET=REPLACE_ME_ADMIN_OIDC_SECRET
|
||||
SESSIONGUARD_ACCESS_OIDC_CLIENT_SECRET=REPLACE_ME_GUAC_ACCESS_OIDC_SECRET
|
||||
SESSIONGUARD_ALERT_WEBHOOK_URL=
|
||||
123
production/sessionguard/docker-compose.yml
Normal file
123
production/sessionguard/docker-compose.yml
Normal file
@@ -0,0 +1,123 @@
|
||||
services:
|
||||
netbird:
|
||||
image: netbirdio/netbird:${NETBIRD_VERSION:-latest}
|
||||
container_name: netbird-sessionguard01
|
||||
hostname: ${NETBIRD_PEER_NAME:-sessionguard01}
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
- SYS_RESOURCE
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
environment:
|
||||
NB_SETUP_KEY: ${NETBIRD_SETUP_KEY:?NETBIRD_SETUP_KEY is required}
|
||||
NB_MANAGEMENT_URL: ${NETBIRD_MANAGEMENT_URL:?NETBIRD_MANAGEMENT_URL is required}
|
||||
NB_LOG_LEVEL: ${NETBIRD_LOG_LEVEL:-info}
|
||||
volumes:
|
||||
- netbird-client:/var/lib/netbird
|
||||
|
||||
sessionguard-postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-17}
|
||||
container_name: sessionguard-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${SESSIONGUARD_DB_NAME:-sessionguard}
|
||||
POSTGRES_USER: ${SESSIONGUARD_DB_USER:-sessionguard}
|
||||
POSTGRES_PASSWORD: ${SESSIONGUARD_DB_PASSWORD:?SESSIONGUARD_DB_PASSWORD is required}
|
||||
volumes:
|
||||
- sessionguard-postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
sessionguard_internal:
|
||||
ipv4_address: ${SESSIONGUARD_DB_IP}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
sessionguard-master:
|
||||
image: ${SESSIONGUARD_IMAGE:?SESSIONGUARD_IMAGE is required}
|
||||
container_name: sessionguard
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
sessionguard-postgres:
|
||||
condition: service_healthy
|
||||
read_only: true
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
environment:
|
||||
SESSIONGUARD_DATABASE_URL: postgres://${SESSIONGUARD_DB_USER:-sessionguard}:${SESSIONGUARD_DB_PASSWORD}@${SESSIONGUARD_DB_IP}:5432/${SESSIONGUARD_DB_NAME:-sessionguard}?sslmode=disable
|
||||
SESSIONGUARD_ENROLLMENT_TOKEN: ${SESSIONGUARD_ENROLLMENT_TOKEN:?SESSIONGUARD_ENROLLMENT_TOKEN is required}
|
||||
SESSIONGUARD_BROKER_API_KEY: ${SESSIONGUARD_BROKER_API_KEY:?SESSIONGUARD_BROKER_API_KEY is required}
|
||||
SESSIONGUARD_OIDC_CLIENT_SECRET: ${SESSIONGUARD_OIDC_CLIENT_SECRET:?SESSIONGUARD_OIDC_CLIENT_SECRET is required}
|
||||
SESSIONGUARD_ACCESS_OIDC_CLIENT_SECRET: ${SESSIONGUARD_ACCESS_OIDC_CLIENT_SECRET:?SESSIONGUARD_ACCESS_OIDC_CLIENT_SECRET is required}
|
||||
SESSIONGUARD_ALERT_WEBHOOK_URL: ${SESSIONGUARD_ALERT_WEBHOOK_URL:-}
|
||||
volumes:
|
||||
- ./master.json:/etc/sessionguard/master.json:ro
|
||||
tmpfs:
|
||||
- /tmp:size=16m,mode=1777
|
||||
networks:
|
||||
sessionguard_internal:
|
||||
ipv4_address: ${SESSIONGUARD_APP_IP}
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/healthz"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
# Generates the official Guacamole PostgreSQL schema once into a named volume.
|
||||
guac-init:
|
||||
image: guacamole/guacamole:${GUACAMOLE_VERSION:-1.6.0}
|
||||
container_name: guac-init
|
||||
restart: "no"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
/opt/guacamole/bin/initdb.sh --postgresql > /initdb/001-guacamole.sql
|
||||
test -s /initdb/001-guacamole.sql
|
||||
volumes:
|
||||
- guac-init:/initdb
|
||||
|
||||
# One shared Guacamole DB for ALL guac01/02/03 workers.
|
||||
guacamole-postgres:
|
||||
image: postgres:${POSTGRES_VERSION:-17}
|
||||
container_name: guacamole-postgres
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
guac-init:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
POSTGRES_DB: ${GUAC_DB_NAME:-guacamole_db}
|
||||
POSTGRES_USER: ${GUAC_DB_USER:-guacamole_user}
|
||||
POSTGRES_PASSWORD: ${GUAC_DB_PASSWORD:?GUAC_DB_PASSWORD is required}
|
||||
volumes:
|
||||
- guacamole-postgres:/var/lib/postgresql/data
|
||||
- guac-init:/docker-entrypoint-initdb.d:ro
|
||||
networks:
|
||||
sessionguard_internal:
|
||||
ipv4_address: ${GUAC_DB_IP}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
|
||||
networks:
|
||||
sessionguard_internal:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: ${SESSIONGUARD_DOCKER_SUBNET}
|
||||
|
||||
volumes:
|
||||
netbird-client:
|
||||
sessionguard-postgres:
|
||||
guacamole-postgres:
|
||||
guac-init:
|
||||
72
production/sessionguard/master.json.example
Normal file
72
production/sessionguard/master.json.example
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"listen": ":8080",
|
||||
"public_url": "https://sessionguard.hilden.info",
|
||||
"data_file": "/var/lib/sessionguard/master.json",
|
||||
"database_url": "",
|
||||
"enrollment_token": "SET-BY-SESSIONGUARD_ENROLLMENT_TOKEN",
|
||||
"offline_after_seconds": 30,
|
||||
"history_limit": 50000,
|
||||
|
||||
"oidc": {
|
||||
"issuer": "https://auth.hilden.info",
|
||||
"client_id": "POCKETID-SESSIONGUARD-ADMIN-CLIENT-ID",
|
||||
"client_secret": "SET-BY-SESSIONGUARD_OIDC_CLIENT_SECRET",
|
||||
"redirect_url": "https://sessionguard.hilden.info/oidc/callback",
|
||||
"admin_groups": [],
|
||||
"secure_cookie": true
|
||||
},
|
||||
|
||||
"access_auth": {
|
||||
"enabled": true,
|
||||
"issuer": "https://auth.hilden.info",
|
||||
"client_id": "POCKETID-GUACAMOLE-ACCESS-CLIENT-ID",
|
||||
"client_secret": "SET-BY-SESSIONGUARD_ACCESS_OIDC_CLIENT_SECRET",
|
||||
"redirect_url": "https://ts.hilden.info/_sessionguard/auth/oidc/callback",
|
||||
"logout_redirect_url": "https://ts.hilden.info/",
|
||||
"cookie_name": "sg_access_session",
|
||||
"cookie_domain": "",
|
||||
"secure_cookie": true,
|
||||
"session_hours": 8,
|
||||
"username_claim": "preferred_username",
|
||||
"allowed_groups": [],
|
||||
"allowed_hosts": [
|
||||
"ts.hilden.info"
|
||||
]
|
||||
},
|
||||
|
||||
"rbac": {
|
||||
"default_role": "viewer",
|
||||
"groups": {
|
||||
"sessionguard-admins": ["admin"],
|
||||
"sessionguard-helpdesk": ["helpdesk"],
|
||||
"sessionguard-operators": ["operator"],
|
||||
"sessionguard-policy": ["policy_admin"],
|
||||
"sessionguard-profile": ["profile_admin"],
|
||||
"sessionguard-auditors": ["auditor"]
|
||||
}
|
||||
},
|
||||
|
||||
"broker": {
|
||||
"enabled": true,
|
||||
"api_key": "SET-BY-SESSIONGUARD_BROKER_API_KEY",
|
||||
"lease_seconds": 900,
|
||||
"min_health_score": 60,
|
||||
"reconnect_existing": true,
|
||||
"single_session_per_user": true,
|
||||
"default_farm_id": ""
|
||||
},
|
||||
|
||||
"alerts": {
|
||||
"enabled": true,
|
||||
"webhook_url": "",
|
||||
"cpu_percent": 90,
|
||||
"memory_percent": 90,
|
||||
"disk_free_gb": 10,
|
||||
"health_score": 50,
|
||||
"offline_seconds": 120,
|
||||
"profile_failures": 3,
|
||||
"disconnected_sessions": 20,
|
||||
"logon_duration_seconds": 30,
|
||||
"notification_min_interval_seconds": 900
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user