Update mit Guacamole-Extension
All checks were successful
release-tag / release-image (push) Successful in 2m5s
release-main / release-images (push) Successful in 5m5s

This commit is contained in:
2026-08-22 15:19:17 +02:00
parent 0f4a234f52
commit 7972ed7e38
45 changed files with 6448 additions and 402 deletions

9
deploy/.env.example Normal file
View File

@@ -0,0 +1,9 @@
POSTGRES_VERSION=17
TRAEFIK_NETWORK=aio_proxy
SESSIONGUARD_DB_NAME=sessionguard
SESSIONGUARD_DB_USER=sessionguard
SESSIONGUARD_DB_PASSWORD=generate-a-long-random-password
SESSIONGUARD_ENROLLMENT_TOKEN=generate-a-long-random-token
SESSIONGUARD_BROKER_API_KEY=generate-a-separate-long-random-token
SESSIONGUARD_OIDC_CLIENT_SECRET=pocketid-sessionguard-client-secret
SESSIONGUARD_ALERT_WEBHOOK_URL=

View File

@@ -1,14 +1,60 @@
services:
sessionguard-postgres:
image: postgres:${POSTGRES_VERSION:-17}
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
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 20
networks: [sessionguard_backend]
sessionguard-master:
build:
context: ..
dockerfile: Dockerfile
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-postgres: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_ALERT_WEBHOOK_URL: ${SESSIONGUARD_ALERT_WEBHOOK_URL:-}
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./master.json:/etc/sessionguard/master.json:ro
- sessionguard-data:/var/lib/sessionguard
tmpfs:
- /tmp:size=16m,mode=1777
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
networks:
- sessionguard_backend
- proxy
networks:
sessionguard_backend:
internal: true
proxy:
external: true
name: ${TRAEFIK_NETWORK:-aio_proxy}
volumes:
sessionguard-data:
sessionguard-postgres:

56
deploy/guacamole/CI-CD.md Normal file
View File

@@ -0,0 +1,56 @@
# Gitea CI/CD for the SessionGuard Guacamole extension
The workflow `.gitea/workflows/release.yml` publishes three artifacts on every
push to `main`:
1. `git.send.nrw/sendnrw/<repo>:<version>` - SessionGuard Master
2. `git.send.nrw/sendnrw/<repo>-guacamole:<version>` - Guacamole with the
SessionGuard broker extension preinstalled
3. `sessionguard-guacamole.jar` in the Gitea Generic Package Registry under
`<repo>-guacamole-extension/<version>`
Both container images also receive the `latest` tag.
## Required secrets
The workflow reuses the existing secrets:
- `DOCKER_USERNAME`
- `DOCKER_PASSWORD`
The account needs write access to both the OCI/container registry and the
Gitea Package Registry. If these permissions should be separated, create
`PACKAGE_USERNAME` and `PACKAGE_TOKEN` secrets and use those in the package
upload step.
## Compose
Instead of the stock Guacamole image, use the CI-built image:
```yaml
services:
guacamole:
image: git.send.nrw/sendnrw/sessionguard-guacamole:${SESSIONGUARD_VERSION:-latest}
environment:
SESSIONGUARD_MASTER_URL: http://sessionguard-master:8080
SESSIONGUARD_BROKER_API_KEY: ${SESSIONGUARD_BROKER_API_KEY}
SESSIONGUARD_BROKER_TIMEOUT_MS: "2500"
```
Keep all existing Guacamole/PostgreSQL/header-auth environment variables and
Traefik labels unchanged.
## Versioning
`fetch-depth: 0` is important. The release version is determined with:
```sh
git describe --tags --always | sed 's/^v//'
```
A commit tagged `v0.3.0` therefore publishes `0.3.0`; later commits are named
like `0.3.0-1-g0123456` until the next tag.
The extension Dockerfile no longer hardcodes `sessionguard-guacamole-0.3.0.jar`.
Maven may therefore change the project version without requiring a Dockerfile
change.

View File

@@ -0,0 +1,18 @@
ARG GUACAMOLE_VERSION=1.6.0
FROM maven:3.9-eclipse-temurin-17 AS extension-build
WORKDIR /src
COPY guacamole-extension/pom.xml ./pom.xml
COPY guacamole-extension/src ./src
RUN mvn -B -DskipTests package \
&& JAR="$(find target -maxdepth 1 -type f -name 'sessionguard-guacamole-*.jar' ! -name '*-sources.jar' ! -name '*-javadoc.jar' | head -n 1)" \
&& test -n "$JAR" \
&& cp "$JAR" /tmp/sessionguard-guacamole.jar
# This target is used by CI to export the extension as a standalone JAR.
FROM scratch AS extension-artifact
COPY --from=extension-build /tmp/sessionguard-guacamole.jar /sessionguard-guacamole.jar
# Default/final image: stock Guacamole plus the SessionGuard broker extension.
FROM guacamole/guacamole:${GUACAMOLE_VERSION} AS guacamole
COPY --from=extension-build /tmp/sessionguard-guacamole.jar /etc/guacamole/extensions/sessionguard-broker.jar

View File

@@ -0,0 +1,50 @@
# SessionGuard + Guacamole 1.6 integration
The `sessionguard-guacamole` extension does **not** authenticate users. It decorates the `UserContext` produced by Guacamole's existing providers and asks the SessionGuard broker for connection parameter tokens at connect time.
## 1. Build Guacamole with the extension
Use `Dockerfile.guacamole` instead of the stock image directly. It builds `guacamole-extension/` with Maven and copies the JAR to `/etc/guacamole/extensions/`.
The Guacamole container needs these additional environment variables:
```yaml
SESSIONGUARD_MASTER_URL: http://sessionguard-master:8080
SESSIONGUARD_BROKER_API_KEY: ${SESSIONGUARD_BROKER_API_KEY}
SESSIONGUARD_BROKER_TIMEOUT_MS: "2500"
```
Use a private Docker network between Guacamole and SessionGuard Master where possible. The broker API key is independent from the agent enrollment token and PocketID client secret.
## 2. Configure Guacamole RDP connections
For a brokered desktop connection set the RDP hostname to:
```text
${SESSIONGUARD_HOST}
```
For a brokered RemoteApp resource, these SessionGuard tokens can be used as connection parameters:
```text
hostname: ${SESSIONGUARD_HOST}
remote-app: ${SESSIONGUARD_REMOTE_APP}
remote-app-dir: ${SESSIONGUARD_REMOTE_APP_DIR}
remote-app-args: ${SESSIONGUARD_REMOTE_APP_ARGS}
```
The extension identifies a SessionGuard resource by Guacamole connection ID or connection name. Create the corresponding mapping in SessionGuard under **Published Resources**. Unmapped Guacamole connections receive no SessionGuard tokens and remain untouched; this lets brokered and static/admin connections coexist.
## 3. Broker behavior
1. Existing matching RDS session in the requested farm wins (if reconnect is enabled).
2. Otherwise a valid SessionGuard user lease is reused.
3. Otherwise only online, `online`-mode farm members above `min_health_score` are considered.
4. Candidates are scored by health, active/disconnected sessions, CPU and memory pressure.
5. `drain` blocks new allocations but allows reconnects; `maintenance` blocks both.
With `single_session_per_user=true`, the lease key is global per username. With it disabled, leases are scoped by user + farm + resource.
## 4. Header-auth boundary
If Guacamole uses `HTTP_AUTH_HEADER=X-Guacamole-User`, untrusted traffic must not be able to reach Guacamole directly and supply that header. Keep the reverse proxy/header scrubber as the only trusted ingress path. Prefer a dedicated proxy network instead of sharing Guacamole's port with unrelated containers.

View File

@@ -0,0 +1,29 @@
# Merge these changes into the existing Guacamole stack.
# Build context must point at the SessionGuard repository root.
services:
guacamole:
image: sessionguard-guacamole:${SESSIONGUARD_VERSION:-0.3.0}
build:
context: ../..
dockerfile: deploy/guacamole/Dockerfile.guacamole
args:
GUACAMOLE_VERSION: ${GUACAMOLE_VERSION:-1.6.0}
environment:
# Existing Guacamole variables stay unchanged.
SESSIONGUARD_MASTER_URL: http://sessionguard-master:8080
SESSIONGUARD_BROKER_API_KEY: ${SESSIONGUARD_BROKER_API_KEY}
SESSIONGUARD_BROKER_TIMEOUT_MS: "2500"
networks:
- aio_proxy
- sessionguard_control
sessionguard-master:
# Your SessionGuard master may be in another compose project. If so, attach
# it to this external network and use its Docker DNS name above.
networks:
- sessionguard_control
networks:
sessionguard_control:
name: sessionguard_control
internal: true