Admin stack: Authelia SSO, portal at admin.carr.pub, gated grafana.carr.pub

This commit is contained in:
Adam Carr
2026-09-19 21:56:58 -07:00
parent 282fd3db8c
commit 2bc85826f1
5 changed files with 287 additions and 0 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
configs/node-password.txt configs/node-password.txt
.synology-password .synology-password
configs/admin-password.txt
configs/authelia-session-secret.txt
configs/authelia-jwt-secret.txt

View File

@@ -0,0 +1,133 @@
apiVersion: v1
kind: Namespace
metadata:
name: admin
---
apiVersion: v1
kind: ConfigMap
metadata:
name: authelia-config
namespace: admin
data:
configuration.yml: |
server:
address: tcp://:9091
theme: dark
log:
level: info
authentication_backend:
file:
path: /config/users_database.yml
password_policy:
standard:
enabled: true
min_length: 12
session:
cookies:
- domain: carr.pub
authelia_url: https://auth.carr.pub
default_redirection_url: https://admin.carr.pub
same_site: lax
storage:
local:
path: /config/data/db.sqlite3
notifier:
filesystem:
filename: /config/data/notifications.txt
totp:
issuer: carr.pub
webauthn:
disable: true
access_control:
default_policy: one_factor
rules: []
---
apiVersion: v1
kind: Secret
metadata:
name: authelia-users
namespace: admin
stringData:
users_database.yml: |
users:
adamcarr:
displayname: "Adam Carr"
password: "<argon2-hash from configs/admin-password.txt via deploy script>"
groups: ["admins"]
---
apiVersion: v1
kind: Secret
metadata:
name: authelia-secrets
namespace: admin
stringData:
session-secret: <from configs/authelia-session-secret.txt>
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: authelia-data
namespace: admin
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-path
resources:
requests: {storage: 2Gi}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: authelia
namespace: admin
spec:
replicas: 1
selector:
matchLabels: {app: authelia}
template:
metadata:
labels: {app: authelia}
spec:
containers:
- name: authelia
image: authelia/authelia:v4.39.28
args: ["--config", "/config/configuration.yml"]
env:
- name: AUTHELIA_SESSION_SECRET
valueFrom: {secretKeyRef: {name: authelia-secrets, key: session-secret}}
ports: [{containerPort: 9091}]
resources:
requests: {cpu: 50m, memory: 64Mi}
limits: {memory: 256Mi}
volumeMounts:
- {name: config, mountPath: /config/configuration.yml, subPath: configuration.yml}
- {name: users, mountPath: /config/users_database.yml, subPath: users_database.yml}
- {name: data, mountPath: /config/data}
volumes:
- name: config
configMap: {name: authelia-config}
- name: users
secret: {secretName: authelia-users}
- name: data
persistentVolumeClaim: {claimName: authelia-data}
---
apiVersion: v1
kind: Service
metadata:
name: authelia
namespace: admin
spec:
selector: {app: authelia}
ports:
- port: 9091
targetPort: 9091
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: authelia-auth
namespace: admin
spec:
forwardAuth:
address: "http://authelia.admin.svc:9091/api/verify?rd=https://auth.carr.pub"
trustForwardHeader: true
authResponseHeaders: [Remote-User, Remote-Groups, Remote-Email, Remote-Name]

View File

@@ -0,0 +1,61 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth
namespace: admin
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts: [auth.carr.pub]
secretName: auth-carr-pub-tls
rules:
- host: auth.carr.pub
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: authelia, port: {number: 9091}}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: admin-portal
namespace: admin
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.middlewares: admin-authelia-auth@kubernetescrd
spec:
tls:
- hosts: [admin.carr.pub]
secretName: admin-carr-pub-tls
rules:
- host: admin.carr.pub
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: portal, port: {number: 80}}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grafana
namespace: monitoring
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.middlewares: admin-authelia-auth@kubernetescrd
spec:
tls:
- hosts: [grafana.carr.pub]
secretName: grafana-carr-pub-tls
rules:
- host: grafana.carr.pub
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: grafana, port: {number: 3000}}

View File

@@ -0,0 +1,66 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: portal-index
namespace: admin
data:
index.html: |
<!doctype html>
<html>
<head><meta charset="utf-8"><title>planck admin</title>
<style>
body { font-family: system-ui, sans-serif; background: #111; color: #eee;
display: grid; place-items: center; min-height: 100vh; margin: 0; }
.card { text-align: center; }
h1 { font-size: 2.5rem; margin: 0 0 1.5rem; }
a { display: block; margin: 0.6rem auto; padding: 0.8rem 1.5rem;
background: #1e1e2e; border: 1px solid #333; border-radius: 10px;
color: #8be9fd; text-decoration: none; font-size: 1.1rem; width: 260px; }
a:hover { background: #2a2a3e; }
p { color: #888; }
</style>
</head>
<body><div class="card">
<h1>planck</h1>
<a href="https://grafana.carr.pub">Grafana</a>
<a href="https://auth.carr.pub">Account &amp; 2FA settings</a>
<p>20 nodes · k3s · carr.pub</p>
</div></body>
</html>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: portal
namespace: admin
spec:
replicas: 2
selector:
matchLabels: {app: portal}
template:
metadata:
labels: {app: portal}
spec:
containers:
- name: web
image: nginx:1.29-alpine
ports: [{containerPort: 80}]
resources:
requests: {cpu: 10m, memory: 16Mi}
limits: {memory: 64Mi}
volumeMounts:
- {name: html, mountPath: /usr/share/nginx/html}
volumes:
- name: html
configMap: {name: portal-index}
---
apiVersion: v1
kind: Service
metadata:
name: portal
namespace: admin
spec:
selector: {app: portal}
ports:
- port: 80
targetPort: 80

24
scripts/deploy-admin.sh Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Redeploy the admin stack (Authelia SSO + portal). Secrets are regenerated
# from the gitignored files in configs/ so nothing sensitive is committed.
# First-time setup: run scripts/gen-admin-secrets.sh before this.
set -euo pipefail
cd "$(dirname "$0")/.."
HASH=$(/tmp/opencode/authelia/authelia crypto hash generate argon2 \
--password "$(cat configs/admin-password.txt)" 2>/dev/null | grep -o '\$argon2id\$.*')
SESSION=$(cat configs/authelia-session-secret.txt)
JWT=$(cat configs/authelia-jwt-secret.txt)
ENCKEY=$(openssl rand -base64 24 | tr -d "=+/" | cut -c1-32)
export HASH SESSION JWT ENCKEY
for f in manifests/admin/*.yaml; do
envsubst '\$HASH \$SESSION \$JWT \$ENCKEY' < "$f" | kubectl apply -f -
done
kubectl -n admin set env deployment/authelia \
AUTHELIA_SESSION_SECRET="$SESSION" \
AUTHELIA_STORAGE_ENCRYPTION_KEY="$ENCKEY" \
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET="$JWT" \
>/dev/null
echo "admin stack deployed"