feat(auth): annuaire Authentik + portail consoles Guacamole (#76)

- k8s/apps/authentik : IdP OIDC (auth.lab.local), namespace auth —
  server + worker 2026.5.3, Redis in-cluster, PG sur storage-01, PVC media
- k8s/apps/guacamole : portail consoles SSH web s01/g01 (portail.lab.local) —
  guacd + webapp 1.6.0, SSO OIDC vers Authentik, Job initdb idempotent
- ansible : rôle console_user (user sans sudo pour les consoles, s01 + g01),
  bases PG authentik/guacamole, secrets vault (mdp PG + clé SSH console)
- doc : admin/k8s/auth-portal.md (déploiement, config initiale, pièges)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ALI YESILKAYA 2026-07-06 21:24:46 +02:00 committed by GitHub
parent 09ca0be469
commit 87b786eb57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 775 additions and 64 deletions

View file

@ -0,0 +1,24 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: authentik
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: git@github.com:Alkatrazz24/Funk-lab.git
targetRevision: main
path: k8s/apps/authentik
directory:
recurse: true
destination:
server: https://kubernetes.default.svc
namespace: auth
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,24 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guacamole
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: git@github.com:Alkatrazz24/Funk-lab.git
targetRevision: main
path: k8s/apps/guacamole
directory:
recurse: true
destination:
server: https://kubernetes.default.svc
namespace: auth
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,135 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-server
namespace: auth
spec:
replicas: 1
selector:
matchLabels:
app: authentik-server
template:
metadata:
labels:
app: authentik-server
spec:
containers:
- name: authentik
image: ghcr.io/goauthentik/server:2026.5.3
args: ["server"]
ports:
- containerPort: 9000
env:
- name: AUTHENTIK_SECRET_KEY
valueFrom:
secretKeyRef:
name: authentik-secret
key: secret-key
# PostgreSQL sur storage-01 (hors cluster) — même pattern que Ghostfolio/n8n
- name: AUTHENTIK_POSTGRESQL__HOST
value: "192.168.10.1"
- name: AUTHENTIK_POSTGRESQL__NAME
value: "authentik"
- name: AUTHENTIK_POSTGRESQL__USER
value: "authentik"
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-secret
key: pg-password
# Redis (in-cluster)
- name: AUTHENTIK_REDIS__HOST
value: "authentik-redis"
- name: AUTHENTIK_REDIS__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-secret
key: redis-password
- name: TZ
value: "Europe/Paris"
volumeMounts:
- name: media
mountPath: /media
resources:
requests:
cpu: 200m
memory: 700Mi
limits:
cpu: 1000m
memory: 1536Mi
readinessProbe:
httpGet:
path: /-/health/ready/
port: 9000
initialDelaySeconds: 30
periodSeconds: 15
livenessProbe:
httpGet:
path: /-/health/live/
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
volumes:
- name: media
persistentVolumeClaim:
claimName: authentik-media
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-worker
namespace: auth
spec:
replicas: 1
selector:
matchLabels:
app: authentik-worker
template:
metadata:
labels:
app: authentik-worker
spec:
containers:
- name: authentik
image: ghcr.io/goauthentik/server:2026.5.3
args: ["worker"]
env:
- name: AUTHENTIK_SECRET_KEY
valueFrom:
secretKeyRef:
name: authentik-secret
key: secret-key
- name: AUTHENTIK_POSTGRESQL__HOST
value: "192.168.10.1"
- name: AUTHENTIK_POSTGRESQL__NAME
value: "authentik"
- name: AUTHENTIK_POSTGRESQL__USER
value: "authentik"
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-secret
key: pg-password
- name: AUTHENTIK_REDIS__HOST
value: "authentik-redis"
- name: AUTHENTIK_REDIS__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-secret
key: redis-password
- name: TZ
value: "Europe/Paris"
volumeMounts:
- name: media
mountPath: /media
resources:
requests:
cpu: 100m
memory: 500Mi
limits:
cpu: 500m
memory: 1Gi
volumes:
- name: media
persistentVolumeClaim:
claimName: authentik-media

View file

@ -0,0 +1,14 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: authentik
namespace: auth
spec:
entryPoints:
- web
routes:
- match: Host(`auth.lab.local`)
kind: Rule
services:
- name: authentik
port: 9000

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: authentik-media
namespace: auth
spec:
# RWX : partagé entre server et worker (icônes, templates)
accessModes:
- ReadWriteMany
storageClassName: nfs
resources:
requests:
storage: 1Gi

View file

@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-redis
namespace: auth
spec:
replicas: 1
selector:
matchLabels:
app: authentik-redis
template:
metadata:
labels:
app: authentik-redis
spec:
containers:
- name: redis
image: redis:7-alpine
args:
- "--requirepass"
- "$(REDIS_PASSWORD)"
- "--maxmemory"
- "128mb"
- "--maxmemory-policy"
- "allkeys-lru"
ports:
- containerPort: 6379
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: authentik-secret
key: redis-password
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 192Mi
readinessProbe:
exec:
command: ["sh", "-c", "redis-cli -a \"$REDIS_PASSWORD\" ping | grep -q PONG"]
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: authentik-redis
namespace: auth
spec:
selector:
app: authentik-redis
ports:
- port: 6379
targetPort: 6379

View file

@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: authentik
namespace: auth
spec:
selector:
app: authentik-server
ports:
- port: 9000
targetPort: 9000

View file

@ -0,0 +1,85 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacamole
namespace: auth
spec:
replicas: 1
selector:
matchLabels:
app: guacamole
template:
metadata:
labels:
app: guacamole
spec:
containers:
- name: guacamole
image: guacamole/guacamole:1.6.0
ports:
- containerPort: 8080
env:
- name: GUACD_HOSTNAME
value: "guacd"
- name: GUACD_PORT
value: "4822"
# Base sur storage-01 (schéma posé par le Job guacamole-initdb)
- name: POSTGRESQL_HOSTNAME
value: "192.168.10.1"
- name: POSTGRESQL_DATABASE
value: "guacamole"
- name: POSTGRESQL_USERNAME
value: "guacamole"
- name: POSTGRESQL_PASSWORD
valueFrom:
secretKeyRef:
name: guacamole-secret
key: pg-password
# Les comptes venant d'Authentik sont créés automatiquement dans la base
# (sinon un utilisateur OIDC arrive sans aucune connexion assignable)
- name: POSTGRESQL_AUTO_CREATE_ACCOUNTS
value: "true"
# Servir sur / plutôt que /guacamole
- name: WEBAPP_CONTEXT
value: "ROOT"
# --- SSO OIDC → Authentik (app « guacamole » à créer dans l'UI Authentik) ---
# Endpoints navigateur : via l'IngressRoute ; JWKS : appel serveur→serveur,
# on passe par le Service interne pour ne pas dépendre du DNS lab.local des pods
- name: OPENID_AUTHORIZATION_ENDPOINT
value: "http://auth.lab.local/application/o/authorize/"
- name: OPENID_JWKS_ENDPOINT
value: "http://authentik.auth.svc.cluster.local:9000/application/o/guacamole/jwks/"
- name: OPENID_ISSUER
value: "http://auth.lab.local/application/o/guacamole/"
- name: OPENID_CLIENT_ID
value: "guacamole"
- name: OPENID_REDIRECT_URI
value: "http://portail.lab.local/"
- name: OPENID_USERNAME_CLAIM_TYPE
value: "preferred_username"
- name: OPENID_SCOPE
value: "openid profile email"
# L'extension base (autorisation/connexions) passe avant l'OIDC (authentification)
- name: EXTENSION_PRIORITY
value: "*, openid"
- name: TZ
value: "Europe/Paris"
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 30
periodSeconds: 15
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 60
periodSeconds: 30

View file

@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacd
namespace: auth
spec:
replicas: 1
selector:
matchLabels:
app: guacd
template:
metadata:
labels:
app: guacd
spec:
containers:
- name: guacd
image: guacamole/guacd:1.6.0
ports:
- containerPort: 4822
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: guacd
namespace: auth
spec:
selector:
app: guacd
ports:
- port: 4822
targetPort: 4822

View file

@ -0,0 +1,14 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: guacamole
namespace: auth
spec:
entryPoints:
- web
routes:
- match: Host(`portail.lab.local`)
kind: Rule
services:
- name: guacamole
port: 8080

View file

@ -0,0 +1,68 @@
# Pose le schéma Guacamole dans la base « guacamole » (storage-01) si absent.
# Hook ArgoCD : recréé à chaque sync, mais le garde-fou (table guacamole_user)
# le rend idempotent — il ne rejoue jamais le schéma sur une base déjà peuplée.
apiVersion: batch/v1
kind: Job
metadata:
name: guacamole-initdb
namespace: auth
annotations:
argocd.argoproj.io/hook: Sync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
initContainers:
- name: generate-schema
image: guacamole/guacamole:1.6.0
command: ["sh", "-c", "/opt/guacamole/bin/initdb.sh --postgresql > /work/initdb.sql"]
volumeMounts:
- name: work
mountPath: /work
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
containers:
- name: apply-schema
image: postgres:16-alpine
command:
- sh
- -c
- |
if psql -tAc "SELECT 1 FROM information_schema.tables WHERE table_name='guacamole_user'" | grep -q 1; then
echo "Schéma déjà présent — rien à faire"
else
psql -v ON_ERROR_STOP=1 -f /work/initdb.sql
echo "Schéma Guacamole appliqué"
fi
env:
- name: PGHOST
value: "192.168.10.1"
- name: PGDATABASE
value: "guacamole"
- name: PGUSER
value: "guacamole"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: guacamole-secret
key: pg-password
volumeMounts:
- name: work
mountPath: /work
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
volumes:
- name: work
emptyDir: {}

View file

@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: guacamole
namespace: auth
spec:
selector:
app: guacamole
ports:
- port: 8080
targetPort: 8080