Files
home-network-k3s-impl/platform-config/kustomization.yaml
T
jakman85andClaude Opus 4.8 5fb097d019 platform: deploy Headlamp dashboard with Keycloak OIDC + per-user RBAC
Add Headlamp (kubernetes-sigs/headlamp Helm chart) at
headlamp.home.keithjackson.dev with native Keycloak OIDC login and PER-USER
RBAC: Headlamp forwards each user's id_token to the k3s apiserver, so the
existing oidc:* group bindings (platform-admins->cluster-admin, viewers->view)
apply per user. The ServiceAccount is deliberately NOT bound to cluster-admin
(clusterRoleBinding.create=false), since user requests ride the forwarded token.

Key integration nuance: the apiserver is configured oidc-client-id=kubernetes,
so it only accepts tokens whose aud contains `kubernetes`. The new `headlamp`
Keycloak client carries an oidc-audience-mapper (included.client.audience=
kubernetes) so tokens carry aud=[headlamp, kubernetes] -- accepted by both
Headlamp's own verifier and the apiserver, leaving the apiserver config
untouched. A groups mapper emits the `groups` claim the apiserver maps to RBAC.

- platform/headlamp: namespace, HelmRepository/HelmRelease, SOPS OIDC secret
  (externalSecret envFrom), default-deny NetworkPolicy + Cilium ingress
  (gateway)/egress (apiserver+Keycloak) policies, README. Wired into
  platform/kustomization.yaml (platform tier).
- platform/headlamp/httproute.yaml wired into platform-config (Gateway API CR,
  dependsOn platform), modeled on grafana-httproute.
- keycloak-realm-import: add `headlamp` confidential client + groups/audience
  mappers + HEADLAMP_CLIENT_SECRET placeholder. Client also created LIVE via
  kcadm (realm import no-ops on the existing realm).
- keycloak-realm-clients.sops: add headlamp-client-secret (mirrored into
  headlamp-oidc-credentials clientSecret). Both encrypted to the repo age key.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-28 11:44:33 -05:00

69 lines
4.5 KiB
YAML

---
# platform-config — the CONFIGS half of the controllers/configs split (flux2 idiomatic).
#
# WHY: the `platform` Flux Kustomization installs the CONTROLLERS (HelmReleases) that register CRDs
# (cert-manager.io, kyverno.io, …). The custom resources BELOW use those CRDs, so applying them in
# the SAME pass fails atomically ("no matches for kind ClusterIssuer") and blocks the whole platform
# apply. This Kustomization carries the CRs and is gated `dependsOn: platform` (wait) so the CRDs
# exist first. See clusters/base/platform-config.yaml + DECISIONS-LOG.
#
# Files are referenced IN PLACE via ../platform/... (Flux builds with LoadRestrictionsNone, and the
# Kyverno policies must stay under platform/kyverno/ for the CI L0 `kyverno test` gate). Build/verify
# locally with: kubectl kustomize --load-restrictor LoadRestrictionsNone platform-config
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
# cert-manager: DNS-01 issuers (need cert-manager.io CRDs from the cert-manager HelmRelease)
- ../platform/cert-manager/clusterissuers.yaml
# gateway: the Gateway + wildcard Certificate (Certificate needs cert-manager.io; Gateway needs the
# GatewayClass, which stays in platform/gateway as a controller resource)
- ../platform/gateway/gateway.yaml
- ../platform/gateway/wildcard-certificate.yaml
# kyverno: ClusterPolicies (need kyverno.io CRDs from the Kyverno HelmRelease)
- ../platform/kyverno/policies/require-default-deny-baseline.yaml
- ../platform/kyverno/policies/validate-default-deny-shape.yaml
- ../platform/kyverno/policies/restrict-host-namespaces.yaml
- ../platform/kyverno/policies/require-egress-class-label.yaml
- ../platform/kyverno/policies/require-auth-mode-label.yaml
- ../platform/kyverno/admission/secret-provenance.yaml
# cilium: L2 LB IP pool + announcement policy (cilium.io CRDs already registered by Cilium)
- cilium/l2-ip-pool.yaml
- cilium/l2-announcement-policy.yaml
# longhorn/external-snapshotter: the driver.longhorn.io VolumeSnapshotClass (needs the
# snapshot.storage.k8s.io CRDs from platform/external-snapshotter). Enables CSI snapshots +
# CDI snapshot smart-clone.
- ../platform/longhorn/volumesnapshotclass.yaml
# monitoring: PrometheusRule alert rules (need monitoring.coreos.com CRDs from kube-prometheus-stack)
- ../platform/monitoring/alerts.yaml
# kubevirt/cdi (Phase 5): the KubeVirt + CDI CRs consume the kubevirt.io / cdi.kubevirt.io CRDs the
# operators register; the ServiceMonitors/alerts consume monitoring.coreos.com. All CRs of platform-
# installed CRDs -> applied here, gated dependsOn platform (F-MON1). The dashboard is a plain
# ConfigMap kept beside them for locality.
- ../platform/kubevirt/kubevirt-cr.yaml
- ../platform/kubevirt/cdi-cr.yaml
# CDI StorageProfiles: snapshot smart-clone for `longhorn`, and RWX/Block + snapshot clone for the
# `longhorn-vm` virt-default class (consume cdi.kubevirt.io; clears CDIDefaultStorageClassDegraded).
- ../platform/kubevirt/cdi-storageprofiles.yaml
- ../platform/kubevirt/servicemonitor.yaml
- ../platform/kubevirt/alerts.yaml
- ../platform/kubevirt/dashboard.yaml
# --- Phase 6 — Identity / SSO (ADR-0014) ---
# keycloak: the CNPG DB Cluster + Keycloak instance + realm import (CRs of the CNPG / Keycloak
# operator CRDs installed by platform/cnpg + platform/keycloak) + its ingress + ServiceMonitor.
# ${KEYCLOAK_REPLICAS} in keycloak-instance.yaml is resolved by this Kustomization's substituteFrom.
- ../platform/keycloak/postgres-cluster.yaml
- ../platform/keycloak/keycloak-instance.yaml
- ../platform/keycloak/keycloak-realm-import.yaml
- ../platform/keycloak/k8s-oidc-rbac.yaml # maps OIDC groups -> cluster roles (kubectl SSO RBAC)
- ../platform/keycloak/httproute.yaml
- ../platform/keycloak/servicemonitor.yaml
# oauth2-proxy: forward-auth httproute + ServiceMonitor MOVED OUT of platform-config. They now
# travel with the oauth2-proxy controller in its own post-platform Flux Kustomization
# (clusters/base/oauth2-proxy.yaml, dependsOn platform-config). Keeping them here — while the
# controller sat in the gating `platform` tier — was one half of the twice-proven cold-start
# deadlock (platform never Ready -> platform-config never reconciles -> these never apply).
# monitoring SSO proof: Grafana native-OIDC login ingress (Gateway API CR)
- ../platform/monitoring/grafana-httproute.yaml
# headlamp: Kubernetes dashboard ingress (Gateway API CR; native-OIDC + per-user RBAC, ADR-0014)
- ../platform/headlamp/httproute.yaml