Files
bluejay-infra/apps/guacamole/guacamole.yaml
Andrew Stoltz 313bdcb21a guacamole: NFS subPath — Synology exports /volume1/kubernetes root only
First pass used nfs.path=/volume1/kubernetes/guacamole/recordings,
which triggered "mount.nfs: access denied by server" on rke2-agent1.
Synology NFS export is scoped to /volume1/kubernetes; match the
working fc-desktop pattern: mount the export root and select the
subdirectory via volumeMount.subPath.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:23:49 -05:00

572 lines
27 KiB
YAML

# Apache Guacamole - Remote Desktop Gateway
# MySQL 8 + guacd + guacamole web
# ArgoCD managed - BlueJay Lab
# ALL credentials sourced from 1Password via OnePasswordItem CRD (guacamole-credentials)
# Fields: username, password, DB-User, DB-Password, DB-Root-Password, DB-Name, URL
---
apiVersion: v1
kind: Namespace
metadata:
name: guacamole
labels:
app.kubernetes.io/part-of: bluejay-infra
---
# MySQL 8 StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: guac-mysql
namespace: guacamole
labels:
app: guac-mysql
spec:
serviceName: guac-mysql
replicas: 1
selector:
matchLabels:
app: guac-mysql
template:
metadata:
labels:
app: guac-mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-Root-Password
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-Name
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-User
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-Password
volumeMounts:
- name: guac-mysql-data
mountPath: /var/lib/mysql
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 1Gi
cpu: 500m
livenessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 30
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: guac-mysql-data
spec:
accessModes: [ReadWriteOnce]
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: guac-mysql
namespace: guacamole
spec:
selector:
app: guac-mysql
ports:
- port: 3306
targetPort: 3306
name: mysql
clusterIP: None
---
# DB schema init Job
# Generates the MySQL schema and pipes it into the database
apiVersion: batch/v1
kind: Job
metadata:
name: guacamole-initdb
namespace: guacamole
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: OnFailure
initContainers:
- name: wait-for-mysql
image: mysql:8.0
command:
- sh
- -c
- |
until mysqladmin ping -h guac-mysql --silent; do
echo "Waiting for MySQL..."
sleep 5
done
containers:
- name: initdb
image: guacamole/guacamole:latest
command:
- sh
- -c
- |
# Generate schema SQL
/opt/guacamole/bin/initdb.sh --mysql > /tmp/initdb.sql
# Apply schema (ignore errors if tables already exist)
mysql -h guac-mysql -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < /tmp/initdb.sql || true
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-Root-Password
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-Name
---
# guacd (Guacamole daemon)
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacd
namespace: guacamole
labels:
app: guacd
spec:
replicas: 1
selector:
matchLabels:
app: guacd
template:
metadata:
labels:
app: guacd
spec:
serviceAccountName: guacd-exec
containers:
- name: guacd
image: guacamole/guacd:latest
ports:
- containerPort: 4822
name: guacd
env:
- name: LOG_LEVEL
value: debug
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 512Mi
cpu: 500m
livenessProbe:
tcpSocket:
port: 4822
initialDelaySeconds: 15
periodSeconds: 10
volumeMounts:
- name: recordings
mountPath: /var/lib/guacamole/recordings
subPath: guacamole/recordings
- name: kubectl-proxy
image: bitnami/kubectl:latest
args:
- proxy
- "--port=8001"
- "--address=127.0.0.1"
- "--accept-hosts=.*"
- "--accept-paths=.*"
- "--disable-filter=true"
- "--v=2"
resources:
requests:
memory: 32Mi
cpu: 10m
limits:
memory: 64Mi
cpu: 50m
volumes:
- name: recordings
nfs:
server: 10.0.58.3
path: /volume1/kubernetes
---
apiVersion: v1
kind: Service
metadata:
name: guacd
namespace: guacamole
spec:
selector:
app: guacd
ports:
- port: 4822
targetPort: 4822
name: guacd
---
# Guacamole Web Application
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacamole
namespace: guacamole
labels:
app: guacamole
spec:
replicas: 1
selector:
matchLabels:
app: guacamole
template:
metadata:
labels:
app: guacamole
spec:
containers:
- name: guacamole
image: guacamole/guacamole:latest
ports:
- containerPort: 8080
name: http
env:
- name: GUACD_HOSTNAME
value: guacd
- name: GUACD_PORT
value: "4822"
- name: MYSQL_HOSTNAME
value: guac-mysql
- name: MYSQL_PORT
value: "3306"
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-Name
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-User
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: DB-Password
# RECORDING_* presence triggers history-recording-storage extension load
# per Guacamole Docker convention (/opt/guacamole/environment/RECORDING_/).
# Recordings are written by guacd and read by guacamole web (history UI).
- name: RECORDING_SEARCH_PATH
value: /var/lib/guacamole/recordings
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 1Gi
cpu: 500m
livenessProbe:
httpGet:
path: /guacamole/
port: 8080
initialDelaySeconds: 120
periodSeconds: 10
readinessProbe:
httpGet:
path: /guacamole/
port: 8080
initialDelaySeconds: 60
periodSeconds: 5
volumeMounts:
- name: guac-properties
mountPath: /etc/guacamole/guacamole.properties
subPath: guacamole.properties
- name: bluejay-branding
mountPath: /etc/guacamole/extensions/bluejay-branding-1.0.0.jar
subPath: bluejay-branding-1.0.0.jar
- name: recordings
mountPath: /var/lib/guacamole/recordings
subPath: guacamole/recordings
volumes:
- name: guac-properties
configMap:
name: guacamole-properties
- name: bluejay-branding
configMap:
name: guacamole-branding
- name: recordings
nfs:
server: 10.0.58.3
path: /volume1/kubernetes
---
apiVersion: v1
kind: Service
metadata:
name: guacamole
namespace: guacamole
spec:
selector:
app: guacamole
ports:
- port: 8080
targetPort: 8080
name: http
---
# Traefik addPrefix middleware
# External URL guac.iamworkin.lan/ gets prefix /guacamole added
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: guac-add-prefix
namespace: guacamole
spec:
addPrefix:
prefix: /guacamole
---
# TLS Certificate via cert-manager
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: guacamole-tls
namespace: guacamole
spec:
secretName: guacamole-tls
issuerRef:
name: step-ca-acme
kind: ClusterIssuer
dnsNames:
- guac.iamworkin.lan
---
# Traefik IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: guacamole
namespace: guacamole
spec:
entryPoints:
- websecure
routes:
- match: Host(`guac.iamworkin.lan`)
kind: Rule
middlewares:
- name: guac-add-prefix
services:
- name: guacamole
port: 8080
tls:
secretName: guacamole-tls
---
# 1Password secret sync — creates guacamole-credentials K8s Secret
# Fields: username, password, DB-User, DB-Password, DB-Root-Password, DB-Name, URL
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: guacamole-credentials
namespace: guacamole
spec:
itemPath: vaults/IAmWorkin/items/Guacamole
---
# Blue Jay Branding Extension (CSS + translations)
apiVersion: v1
kind: ConfigMap
metadata:
name: guacamole-branding
namespace: guacamole
binaryData:
bluejay-branding-1.0.0.jar: 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
---
# Guacamole custom properties
apiVersion: v1
kind: ConfigMap
metadata:
name: guacamole-properties
namespace: guacamole
data:
guacamole.properties: |
# Blue Jay Remote Access — Guacamole Configuration
# MySQL/guacd settings provided via env vars — do NOT duplicate here
# Extension Priority
extension-priority: mysql, ban, bluejay, *
# Ban (brute force)
ban-max-invalid-attempts: 5
ban-address-duration: 300000
ban-max-addresses: 1000
# TOTP
totp-issuer: Blue Jay Remote Access
totp-digits: 6
totp-period: 30
totp-mode: sha256
# Session
api-session-timeout: 60
---
# guacd ServiceAccount for K8s exec
apiVersion: v1
kind: ServiceAccount
metadata:
name: guacd-exec
namespace: guacamole
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: guacd-pod-exec
labels:
app.kubernetes.io/component: proxy
app.kubernetes.io/name: guacd
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods/exec", "pods/attach"]
verbs: ["create", "get"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["list", "get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: guacd-pod-exec
labels:
app.kubernetes.io/component: proxy
app.kubernetes.io/name: guacd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: guacd-pod-exec
subjects:
- kind: ServiceAccount
name: guacd-exec
namespace: guacamole
---
# K8s pod sync CronJob — auto-updates Guacamole connections when pods restart
apiVersion: batch/v1
kind: CronJob
metadata:
name: guac-k8s-sync
namespace: guacamole
labels:
app.kubernetes.io/name: guac-k8s-sync
app.kubernetes.io/component: sync
spec:
schedule: "*/2 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
serviceAccountName: guacd-exec
restartPolicy: OnFailure
containers:
- name: sync
image: bitnami/kubectl:latest
command:
- /bin/bash
- /scripts/sync-k8s-connections.sh
env:
- name: GUAC_URL
value: "http://guacamole.guacamole.svc.cluster.local:8080"
- name: GUAC_ADMIN_USER
value: "guacadmin"
- name: GUAC_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: guacamole-credentials
key: password
- name: TARGET_NAMESPACES
value: "argocd,gitea,telephony,traefik-system,zabbix,matrix,irc,mail,selenium"
volumeMounts:
- name: scripts
mountPath: /scripts
volumes:
- name: scripts
configMap:
name: guac-k8s-sync-scripts
defaultMode: 0755
---
apiVersion: v1
kind: ConfigMap
metadata:
name: guac-k8s-sync-scripts
namespace: guacamole
labels:
app.kubernetes.io/name: guac-k8s-sync
app.kubernetes.io/component: sync
data:
sync-k8s-connections.sh: |
#!/bin/bash
set -euo pipefail
GUAC_API="${GUAC_URL}/guacamole/api"
DS="mysql"
echo "[k8s-sync] Starting pod connection sync"
# Auth using grep (no python3/jq in bitnami/kubectl)
AUTH_RESP=$(curl -sf -d "username=${GUAC_ADMIN_USER}&password=${GUAC_ADMIN_PASSWORD}" "${GUAC_API}/tokens")
TOKEN=$(echo "$AUTH_RESP" | grep -o '"authToken":"[^"]*"' | cut -d'"' -f4)
if [ -z "$TOKEN" ]; then echo "[k8s-sync] ERROR: Auth failed"; exit 1; fi
echo "[k8s-sync] Authenticated"
CONNS=$(curl -sf "${GUAC_API}/session/data/${DS}/connections?token=${TOKEN}")
IFS=',' read -ra NAMESPACES <<< "$TARGET_NAMESPACES"
UPDATED=0
for NS in "${NAMESPACES[@]}"; do
PODS=$(kubectl get pods -n "$NS" --field-selector=status.phase=Running \
-o jsonpath='{range .items[*]}{.metadata.name} {.spec.containers[0].name}{"\n"}{end}' 2>/dev/null || echo "")
while read -r POD CONTAINER; do
[ -z "$POD" ] && continue
CONN_IDS=$(echo "$CONNS" | grep -o "\"[0-9]*\":{\"name\":\"k8s: ${CONTAINER}\"" | grep -o '"[0-9]*"' | tr -d '"' || echo "")
for CID in $CONN_IDS; do
PARAMS=$(curl -sf "${GUAC_API}/session/data/${DS}/connections/${CID}/parameters?token=${TOKEN}")
CURR_NS=$(echo "$PARAMS" | grep -o '"namespace":"[^"]*"' | cut -d'"' -f4)
CURR_POD=$(echo "$PARAMS" | grep -o '"pod":"[^"]*"' | cut -d'"' -f4)
if [ "$CURR_NS" = "$NS" ] && [ "$CURR_POD" != "$POD" ]; then
echo "[k8s-sync] Updating $NS/$CONTAINER: $CURR_POD -> $POD"
curl -sf -X PUT -H "Content-Type: application/json" \
-d "{\"name\":\"k8s: $CONTAINER\",\"parentIdentifier\":\"ROOT\",\"protocol\":\"kubernetes\",\"parameters\":{\"hostname\":\"localhost\",\"port\":\"8001\",\"namespace\":\"$NS\",\"pod\":\"$POD\",\"container\":\"$CONTAINER\",\"use-ssl\":\"false\",\"exec-command\":\"/bin/sh\",\"font-size\":\"14\",\"color-scheme\":\"gray-black\",\"scrollback-size\":\"5000\"},\"attributes\":{\"max-connections\":\"2\",\"max-connections-per-user\":\"1\"}}" \
"${GUAC_API}/session/data/${DS}/connections/${CID}?token=${TOKEN}" > /dev/null
UPDATED=$((UPDATED + 1))
fi
done
done <<< "$PODS"
done
echo "[k8s-sync] Done: ${UPDATED} updated"