Configuration
The full value list is generated into the chart's own
README
by helm-docs, and every value is documented inline in values.yaml.
This page walks through the groups you will actually touch.
Ingress / routingβ
ingress:
enabled: true
host: waas.example.com
className: "" # empty = cluster default
tls:
enabled: true
issuerRef:
kind: ClusterIssuer
name: letsencrypt
Prefer Gateway API? Set httpRoute.enabled: true with your
parentRefs; hostnames default to ingress.host.
guacd is never routed by either β it stays ClusterIP-only, reached exclusively through the wwt proxy after JWT validation.
Authentication (OIDC SSO)β
Local accounts work out of the box (bootstrap admin +
admin-created users). For SSO (worked end-to-end example:
SSO with Authentik):
apiServer:
oidc:
issuerURL: https://idp.example.com/realms/main
clientID: waas
clientSecretRef: { name: waas-oidc, key: client-secret }
redirectURL: https://waas.example.com/api/v1/auth/oidc/callback
groupsClaim: groups
adminGroups: [platform-admins] # IdP groups granted the admin role
disableLocalLogin: false
Notes worth knowing:
- The IdP's
groupsclaim is mirrored at every SSO login and is whatWorkspacePolicysubjects match against. disableLocalLogin: truedisables username/password for everyone, bootstrap admin included β the api-server refuses to start if OIDC is not configured at the same time (a typo can never lock everyone out silently). Break-glass is redeploying without the flag.- Set
adminGroupstogether withdisableLocalLogin, otherwise no account can ever reach the admin role through SSO.
Databaseβ
The chart bundles PostgreSQL 17 for convenience. For production, bring your own:
postgres:
enabled: false
externalURLSecretRef: { name: waas-db, key: database-url }
An external URL carries its own sslmode β securing that connection is
yours to do. For the bundled instance the chart builds the URL and
pins postgres.sslMode: disable: the bundled StatefulSet serves no TLS,
so that is a description of reality rather than a downgrade. Raise it
only after wiring certificates into that instance yourself.
Workspace placementβ
Where workspace workloads (pods, services, home PVCs) land β the CRs themselves stay in the platform namespace:
workspaces:
namespace: "" # CR namespace; empty = release namespace
defaultNamespacePattern: "waas-{user}" # workload namespace pattern
waas-{user} β one namespace per user β is also the built-in default
since chart 0.3.0 (it was the shared waas-workspaces before). A shared
namespace is still supported, as an explicit choice:
defaultNamespacePattern: "waas-workspaces". Existing workspaces never
move, and retained home volumes do not follow a default change β see
the migration note in Placement.
The pattern accepts {user}, {workspace}, {templateName} and
{os} placeholders; an invalid pattern makes the operator and
api-server refuse to start rather than silently fall back. Details
and the precedence chain: Placement.
Desktop egressβ
Placed namespaces get a default-deny egress policy: DNS always, then the internet minus the blocked ranges.
operator:
desktopEgress:
enabled: true # false = historical ingress-only policy (CNI escape hatch)
allowInternet: true # false = DNS + extraAllowedCIDRs only
blockedCIDRs: # defaults: cloud IMDS + RFC1918
- 169.254.169.254/32
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
extraAllowedCIDRs: [] # wins over blockedCIDRs (your mirrors, internal Gitβ¦)
The defaults assume RFC1918 cluster networks. On GKE (Service CIDR
34.118.224.0/20 by default) append your own range or the kube-apiserver
stays reachable from desktops β check with kubectl get svc kubernetes -n default -o jsonpath='{.spec.clusterIP}'. Emptying the list carves out
nothing rather than restoring the defaults. Rationale and the full rule
set: Placement.
Remote-workspace target guardβ
apiServer:
clusterDomain: "" # empty = discovered from the pod's resolv.conf
remoteBlockedCIDRs: [] # add your cluster's pod and service CIDRs
Remote workspaces may not point back at the cluster; the built-in filter (loopback, link-local/IMDS, kube-apiserver ClusterIP, in-cluster names) cannot know your pod/service CIDRs, so list them here. Scope and deliberate exceptions: Remote workspaces.
Bootstrap policies and catalogsβ
defaultPolicy:
enabled: true # catch-all policy, priority 0 β see values.yaml
adminPolicy:
enabled: false # explicit all-rights policy for admins (off by default)
catalogs:
waasImages: { enabled: true } # official XorHub images (docker.io/xorhub)
kasm: { enabled: false } # upstream kasmweb images (KasmVNC β experimental)
Doctrine: these bootstrap CRs exist so a fresh install works without a
GitOps repo. Once gitops/-managed policies and images take over,
disable the matching flag β never run both for the same object name.
Observabilityβ
metrics:
enabled: true # /metrics on every component (cluster-internal)
serviceMonitor: { enabled: true } # api-server + wwt (prometheus-operator)
podMonitor: { enabled: true } # operator
grafana:
dashboards: { enabled: true } # bundled dashboards (configmap or operator mode)
Sizing knobsβ
Every component exposes replicas, resources,
deploymentLabels/Annotations and podLabels/Annotations
(operator.*, apiServer.*, wwt.*, frontend.*, guacd.*,
postgres.*). Session-related tunables live under apiServer.*
(accessTokenTTL, connectionTokenTTL, streamTokenTTL,
eventsPollInterval, catalogSyncInterval).
streamTokenTTL (2 min) is the lifetime of the token the portal mints
for the live-events stream. It rides the URL query string β EventSource
cannot set headers β so it lands in proxy access logs, and the short TTL
is what keeps such a leak worthless. Do not lengthen it to "avoid
reconnections": the frontend re-mints on every reconnect anyway, so a
longer TTL buys nothing and only widens the window.