feat: initial scaffold from helm-template
Validate login GitOps repo / validate (push) Successful in 9s
Validate login GitOps repo / validate (push) Successful in 9s
- Keycloak Operator v26 deployed to keycloak-system namespace - Keycloak HA instance (2 replicas, jdbc-ping cluster discovery) - Dedicated CNPG cluster (local-postgres storage, 2 instances) - KeycloakRealmImport: home-lab realm with groups and traefik-oidc client - Traefik IngressRoute: login.olb42.com (CF Access bypass) - Admin credentials placeholder (seal before first deploy) - ArgoCD ApplicationSets: login-operator + login
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: login
|
||||
|
||||
# Dormant mode: CNPG cluster stays alive to preserve data.
|
||||
# Keycloak instance is scaled to zero by patching the Keycloak CR.
|
||||
resources:
|
||||
- ../../base/state
|
||||
|
||||
patches:
|
||||
- patch: |
|
||||
- op: replace
|
||||
path: /spec/instances
|
||||
value: 0
|
||||
target:
|
||||
kind: Keycloak
|
||||
name: keycloak
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/part-of: login
|
||||
app.kubernetes.io/environment: dormant
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: bitnami.com/v1alpha1
|
||||
kind: SealedSecret
|
||||
metadata:
|
||||
name: login-admin
|
||||
namespace: login
|
||||
annotations:
|
||||
sealedsecrets.bitnami.com/managed: "true"
|
||||
spec:
|
||||
encryptedData:
|
||||
# Seal from admin.secret.plain.yaml:
|
||||
# kubeseal --context homelab-argocd \
|
||||
# --secret-file manifest/overlays/production/admin.secret.plain.yaml \
|
||||
# --sealed-secret-file manifest/overlays/production/admin.sealed.secret.yaml
|
||||
username: REPLACE_WITH_KUBESEAL_OUTPUT
|
||||
password: REPLACE_WITH_KUBESEAL_OUTPUT
|
||||
template:
|
||||
metadata:
|
||||
name: login-admin
|
||||
namespace: login
|
||||
annotations:
|
||||
sealedsecrets.bitnami.com/managed: "true"
|
||||
labels:
|
||||
app.kubernetes.io/part-of: login
|
||||
app.kubernetes.io/environment: production
|
||||
type: Opaque
|
||||
@@ -0,0 +1,48 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: login-postgres
|
||||
namespace: login
|
||||
spec:
|
||||
# ── HA: 1 primary + 1 standby replica ────────────────────────────────────
|
||||
instances: 2
|
||||
|
||||
# ── Storage: local-postgres class (fast local NVMe, no Longhorn overhead) ─
|
||||
storage:
|
||||
storageClass: local-postgres
|
||||
size: 8Gi
|
||||
|
||||
# ── Spread replicas across nodes ──────────────────────────────────────────
|
||||
affinity:
|
||||
enablePodAntiAffinity: true
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
# ── Bootstrap: create the keycloak database and owner role ───────────────
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: keycloak
|
||||
owner: keycloak
|
||||
|
||||
# ── WAL archival via barman-cloud → MinIO ─────────────────────────────────
|
||||
plugins:
|
||||
- name: barman-cloud.cloudnative-pg.io
|
||||
isWALArchiver: true
|
||||
parameters:
|
||||
barmanObjectName: minio-store
|
||||
|
||||
# ── PostgreSQL tuning for Keycloak workload ───────────────────────────────
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "200"
|
||||
shared_buffers: "256MB"
|
||||
work_mem: "8MB"
|
||||
maintenance_work_mem: "64MB"
|
||||
|
||||
# ── Resources ─────────────────────────────────────────────────────────────
|
||||
resources:
|
||||
requests:
|
||||
memory: 256Mi
|
||||
cpu: 100m
|
||||
limits:
|
||||
memory: 512Mi
|
||||
cpu: 500m
|
||||
@@ -0,0 +1,36 @@
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: login
|
||||
namespace: login
|
||||
annotations:
|
||||
# Cloudflare tunnel: public access, bypass CF Access
|
||||
# Keycloak IS the auth provider — it must be reachable before any OIDC
|
||||
# flow can begin, so CF Access must not gate it.
|
||||
dns.public: "true"
|
||||
dns.public.access.policy: bypass
|
||||
dns.public.hostname: login.olb42.com
|
||||
|
||||
# Homepage service discovery
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "Keycloak"
|
||||
gethomepage.dev/group: "Security"
|
||||
gethomepage.dev/description: "Identity & Access Management (operator-managed)"
|
||||
gethomepage.dev/href: "https://login.olb42.com"
|
||||
gethomepage.dev/icon: "keycloak"
|
||||
gethomepage.dev/app: "keycloak"
|
||||
gethomepage.dev/namespace: "login"
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`login.olb42.com`)
|
||||
kind: Rule
|
||||
# No OIDC middleware: Keycloak is the identity provider itself.
|
||||
# Traefik passes traffic straight through to the Keycloak service.
|
||||
services:
|
||||
- name: keycloak-service
|
||||
namespace: login
|
||||
port: 8080
|
||||
tls:
|
||||
secretName: olb42-wildcard-tls
|
||||
@@ -0,0 +1,76 @@
|
||||
apiVersion: k8s.keycloak.org/v2alpha1
|
||||
kind: Keycloak
|
||||
metadata:
|
||||
name: keycloak
|
||||
namespace: login
|
||||
spec:
|
||||
# ── HA: 2 replicas with Infinispan jdbc-ping cluster discovery ────────────
|
||||
instances: 2
|
||||
|
||||
# ── Image: pin to the same major as the operator release ─────────────────
|
||||
image: quay.io/keycloak/keycloak:26.2.5
|
||||
startOptimized: false
|
||||
|
||||
# ── Database: CNPG cluster (login-postgres) ───────────────────────────────
|
||||
# CNPG auto-creates the secret <cluster-name>-app with username/password.
|
||||
db:
|
||||
vendor: postgres
|
||||
host: login-postgres-rw
|
||||
port: 5432
|
||||
database: keycloak
|
||||
usernameSecret:
|
||||
name: login-postgres-app
|
||||
key: username
|
||||
passwordSecret:
|
||||
name: login-postgres-app
|
||||
key: password
|
||||
|
||||
# ── HTTP: plain HTTP backend; TLS terminated at Traefik ───────────────────
|
||||
http:
|
||||
httpEnabled: true
|
||||
httpPort: 8080
|
||||
httpsPort: 8443
|
||||
# No TLS secret — Traefik handles TLS via wildcard cert
|
||||
|
||||
# ── Hostname ──────────────────────────────────────────────────────────────
|
||||
hostname:
|
||||
hostname: login.olb42.com
|
||||
admin: login.olb42.com
|
||||
# strict=false: allow X-Forwarded-Host from Traefik
|
||||
strict: false
|
||||
strictBackchannel: false
|
||||
|
||||
# ── Proxy: trust forwarded headers from Traefik ──────────────────────────
|
||||
proxy:
|
||||
headers: forwarded
|
||||
|
||||
# ── Bootstrap admin credentials (sealed secret — see admin.sealed.secret.yaml)
|
||||
bootstrapAdminSpec:
|
||||
secret:
|
||||
name: login-admin
|
||||
|
||||
# ── Additional Keycloak options ───────────────────────────────────────────
|
||||
additionalOptions:
|
||||
# jdbc-ping: DB-backed cluster discovery — no Kubernetes API/RBAC needed
|
||||
- name: cache-stack
|
||||
value: jdbc-ping
|
||||
- name: cache
|
||||
value: ispn
|
||||
- name: log-level
|
||||
value: INFO
|
||||
# Forward client IP through Traefik X-Forwarded-For chain
|
||||
- name: proxy-trusted-addresses
|
||||
value: "0.0.0.0/0"
|
||||
|
||||
# ── Resources ─────────────────────────────────────────────────────────────
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 250m
|
||||
limits:
|
||||
memory: 1500Mi
|
||||
cpu: "2"
|
||||
|
||||
# ── Ingress disabled: Traefik IngressRoute manages external access ─────────
|
||||
ingress:
|
||||
enabled: false
|
||||
@@ -0,0 +1,100 @@
|
||||
apiVersion: k8s.keycloak.org/v2alpha1
|
||||
kind: KeycloakRealmImport
|
||||
metadata:
|
||||
name: home-lab
|
||||
namespace: login
|
||||
spec:
|
||||
keycloakCRName: keycloak
|
||||
|
||||
# ── Realm definition ──────────────────────────────────────────────────────
|
||||
# This is a Keycloak realm export JSON embedded as a structured spec.
|
||||
# The operator applies this on startup and reconciles on changes.
|
||||
# Add clients, groups, and roles here; per-app clients can be managed in
|
||||
# their own app repos by adding additional KeycloakRealmImport resources
|
||||
# targeting this same keycloakCRName.
|
||||
realm:
|
||||
realm: home-lab
|
||||
displayName: "Home Lab"
|
||||
enabled: true
|
||||
loginWithEmailAllowed: true
|
||||
duplicateEmailsAllowed: false
|
||||
resetPasswordAllowed: true
|
||||
editUsernameAllowed: false
|
||||
bruteForceProtected: true
|
||||
permanentLockout: false
|
||||
maxFailureWaitSeconds: 900
|
||||
minimumQuickLoginWaitSeconds: 60
|
||||
waitIncrementSeconds: 60
|
||||
quickLoginCheckMilliSeconds: 1000
|
||||
maxDeltaTimeSeconds: 43200
|
||||
failureFactor: 10
|
||||
|
||||
# ── Session settings ──────────────────────────────────────────────────
|
||||
ssoSessionIdleTimeout: 1800
|
||||
ssoSessionMaxLifespan: 36000
|
||||
accessTokenLifespan: 300
|
||||
accessTokenLifespanForImplicitFlow: 900
|
||||
offlineSessionIdleTimeout: 2592000
|
||||
offlineSessionMaxLifespanEnabled: false
|
||||
|
||||
# ── Password policy ───────────────────────────────────────────────────
|
||||
passwordPolicy: "length(12) and notUsername(undefined)"
|
||||
|
||||
# ── Groups ────────────────────────────────────────────────────────────
|
||||
groups:
|
||||
- name: homelab-admin
|
||||
path: /homelab-admin
|
||||
- name: homelab-user
|
||||
path: /homelab-user
|
||||
- name: transmission-users
|
||||
path: /transmission-users
|
||||
- name: media-users
|
||||
path: /media-users
|
||||
- name: monitoring-users
|
||||
path: /monitoring-users
|
||||
|
||||
# ── Default client scopes ─────────────────────────────────────────────
|
||||
defaultDefaultClientScopes:
|
||||
- web-origins
|
||||
- acr
|
||||
- roles
|
||||
- profile
|
||||
- basic
|
||||
- email
|
||||
|
||||
# ── Clients ───────────────────────────────────────────────────────────
|
||||
# traefik-oidc: shared client for traefikoidc middleware (SSO cookie domain)
|
||||
# Per-app clients with dedicated redirect URIs and claim mappers should be
|
||||
# added as additional KeycloakRealmImport resources in each app's repo.
|
||||
clients:
|
||||
- clientId: traefik-oidc
|
||||
name: "Traefik OIDC"
|
||||
description: "Shared OIDC client for Traefik traefikoidc middleware"
|
||||
enabled: true
|
||||
publicClient: false
|
||||
standardFlowEnabled: true
|
||||
implicitFlowEnabled: false
|
||||
directAccessGrantsEnabled: false
|
||||
serviceAccountsEnabled: false
|
||||
authorizationServicesEnabled: false
|
||||
redirectUris:
|
||||
- "https://transmission-ng.olb42.com/oauth2/callback"
|
||||
- "https://home.olb42.com/oauth2/callback"
|
||||
- "https://dozzle.olb42.com/oauth2/callback"
|
||||
- "https://pgadmin4.olb42.com/oauth2/callback"
|
||||
webOrigins:
|
||||
- "+"
|
||||
attributes:
|
||||
pkce.code.challenge.method: "S256"
|
||||
post.logout.redirect.uris: "+"
|
||||
protocolMappers:
|
||||
- name: groups
|
||||
protocol: openid-connect
|
||||
protocolMapper: oidc-group-membership-mapper
|
||||
consentRequired: false
|
||||
config:
|
||||
full.path: "false"
|
||||
id.token.claim: "true"
|
||||
access.token.claim: "true"
|
||||
claim.name: "groups"
|
||||
userinfo.token.claim: "true"
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: login
|
||||
|
||||
resources:
|
||||
- ../../base/state
|
||||
- cnpg-cluster.yaml
|
||||
- keycloak-instance.yaml
|
||||
- keycloak-realm-home-lab.yaml
|
||||
- ingressroute.yaml
|
||||
- admin.sealed.secret.yaml
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/part-of: login
|
||||
app.kubernetes.io/environment: production
|
||||
Reference in New Issue
Block a user