WIP keycloak stack (gate probe)

This commit is contained in:
jakman85 committed 2026-08-24 19:52:25 -05:00
1 parent 254d50a20c
commit ce83c6af3b
28 files changed
+776 -102

No files matched your search

+9 -5
View File
@@ -305,9 +305,12 @@ spec:
- name: platform-health.identity
rules:
- alert: KeycloakDown
# Scope to the Keycloak SERVER StatefulSet pods (keycloak-0, keycloak-1). The keycloak
# namespace also holds the operator (keycloak-operator-*) and DB (keycloak-db-*) pods, so an
# un-scoped max() would be masked by those being ready — this must reflect the IdP itself.
expr: |
max(kube_pod_status_ready{namespace="keycloak", condition="true"}) == 0
or absent(kube_pod_status_ready{namespace="keycloak"})
max(kube_pod_status_ready{namespace="keycloak", pod=~"keycloak-[0-9]+", condition="true"}) == 0
or absent(kube_pod_status_ready{namespace="keycloak", pod=~"keycloak-[0-9]+"})
for: 5m
labels:
severity: critical
@@ -316,10 +319,11 @@ spec:
description: "No ready Keycloak pod for 5m — SSO is unavailable and dependent services cannot authenticate (docs/15)."
runbook_url: "https://home.keithjackson.dev/runbooks/keycloak-down"
- alert: KeycloakDatabaseDown
# Keycloak's PostgreSQL (Longhorn-backed, DR-critical). Metric from postgres-exporter or
# the bundled chart; confirm label set at bring-up.
# Keycloak's PostgreSQL runs as a CloudNativePG Cluster `keycloak-db` (pods keycloak-db-N).
# DR-critical, Longhorn-backed. (CNPG also exports cnpg_* metrics via its PodMonitor.)
expr: |
max(kube_pod_status_ready{namespace="keycloak", pod=~".*postgres.*", condition="true"}) == 0
max(kube_pod_status_ready{namespace="keycloak", pod=~"keycloak-db-.*", condition="true"}) == 0
or absent(kube_pod_status_ready{namespace="keycloak", pod=~"keycloak-db-.*"})
for: 5m
labels:
severity: critical
@@ -0,0 +1,25 @@
---
# Ingress for Grafana at grafana.home.keithjackson.dev (docs/09) — the endpoint of the native-OIDC SSO
# proof (log in once at Keycloak -> reach Grafana). CR of the Gateway API CRDs — carried by
# platform-config. Same-namespace backend; parentRef allowed via the monitoring namespace's
# gateway-access=shared label.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: grafana
namespace: monitoring
spec:
parentRefs:
- name: shared
namespace: gateway
sectionName: https
hostnames:
- grafana.home.keithjackson.dev
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: kube-prometheus-stack-grafana
port: 80
@@ -0,0 +1,26 @@
# =============================================================================
# TEMPLATE — NOT APPLIED, NOT ENCRYPTED (.yaml.example is ignored by kustomize and never
# auto-encrypted by SOPS). Reference scaffold + OPERATOR HAND-OFF.
#
# WHAT THIS IS: the OIDC client SECRET Grafana uses to authenticate to the Keycloak `homelab` realm's
# `grafana` client (kube-prometheus-stack.yaml -> grafana.envValueFrom.GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET).
# The value MUST equal the `grafana-client-secret` placed in platform/keycloak/keycloak-realm-clients
# (which the realm import injects into the Keycloak client). Consumed by name only (R-IDP-04).
#
# HAND-OFF STEPS (operator):
# (a) Use the SAME grafana-client-secret generated for keycloak-realm-clients (openssl rand -hex 32).
# (b) Copy to: platform/monitoring/grafana-oidc-credentials.sops.yaml
# (c) Fill `client-secret` below; encrypt:
# sops -e -i platform/monitoring/grafana-oidc-credentials.sops.yaml
# (d) Add under `resources:` in platform/monitoring/kustomization.yaml:
# - grafana-oidc-credentials.sops.yaml
# Grafana runs with local admin login until this lands (secretKeyRef optional:true), then OIDC works.
# =============================================================================
apiVersion: v1
kind: Secret
metadata:
name: grafana-oidc-credentials
namespace: monitoring
type: Opaque
stringData:
client-secret: REPLACE-ME
+30 -2
View File
@@ -150,8 +150,36 @@ spec:
memory: 128Mi
limits:
memory: 256Mi
# TODO(WS-SEC/WS-C2, later): Keycloak OIDC client (auth.generic_oauth) + root_url behind the
# Gateway; disable local login once SSO is proven. Secret referenced by name only.
# --- Keycloak native-OIDC SSO (ADR-0014, deliverable #4 SSO proof) ---
# Grafana is a confidential OIDC client of the Keycloak `homelab` realm. The client SECRET comes
# from the SOPS-encrypted grafana-oidc-credentials Secret via env (referenced by name only,
# R-IDP-04). optional:true so the RUNNING Grafana keeps starting before the operator fills the
# secret — local admin login (below) stays available as the fallback until SSO is proven.
envValueFrom:
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET:
secretKeyRef:
name: grafana-oidc-credentials
key: client-secret
optional: true
grafana.ini:
server:
root_url: https://grafana.home.keithjackson.dev
auth.generic_oauth:
enabled: true
name: Keycloak
client_id: grafana
scopes: "openid email profile"
auth_url: https://keycloak.home.keithjackson.dev/realms/homelab/protocol/openid-connect/auth
token_url: https://keycloak.home.keithjackson.dev/realms/homelab/protocol/openid-connect/token
api_url: https://keycloak.home.keithjackson.dev/realms/homelab/protocol/openid-connect/userinfo
login_attribute_path: preferred_username
email_attribute_path: email
# Map Keycloak groups (the `groups` claim) to Grafana roles (R-IDP-06): platform-admins ->
# Admin, media-users -> Editor, everyone else -> Viewer.
role_attribute_path: "contains(groups[*], 'platform-admins') && 'Admin' || contains(groups[*], 'media-users') && 'Editor' || 'Viewer'"
# Keep local admin login enabled as break-glass until SSO is proven end-to-end; flip to
# disable_login_form:true afterwards (docs/15 break-glass, R-IDP-05).
# TODO(WS-SEC, post-proof): set auth.disable_login_form=true once SSO login is verified.
# --- Bundled exporters / operator: keep small ---
prometheusOperator:
+4
View File
@@ -3,3 +3,7 @@ apiVersion: v1
kind: Namespace
metadata:
name: monitoring
labels:
# Expose Grafana at grafana.home.keithjackson.dev via the shared Gateway (docs/09) for the
# Keycloak native-OIDC SSO proof (grafana-httproute.yaml, WS-SEC Phase 6).
gateway-access: "shared"