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>
572 lines
27 KiB
YAML
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"
|