Posts

Pocket ID als Lab-OIDC-Provider (Zot-Beispiel)

Ein kleiner IdP unter auth.this-is-fine.io — natives OpenID-Login für die Zot-Registry.

Keycloak ist ein Schweizer Taschenmesser — und für ein Homelab oft ein ganzes Werkzeugkasten, den man nur einmal im Jahr aufklappt. Pocket ID ist das Gegenteil: ein schlanker OpenID Connect -Provider, der genug kann, damit Menschen sich per SSO anmelden, ohne dass ein Operator erst Realms, Themes und Session-Cluster pflegt.

Auf hydra läuft Pocket ID unter https://auth.this-is-fine.io, verwaltet per Flux und erreichbar über eine HTTPRoute am Envoy Gateway (shared-gateway-external, LB 10.103.0.1). Dieser Beitrag zeigt, wie Zot auf oci.this-is-fine.io natives OpenID gegen diesen Issuer nutzt — ohne OAuth-Sidecar vor der Registry-UI.

git clone https://seed.this-is-fine.io/z6MQ7ck2rSh7h4qEgbRYg3ftnrGv.git lab

Relevante Pfade: k8s/clusters/hydra/applications/pocket-id/, k8s/clusters/hydra/applications/zot/zot/app/zot.json.

Wann Pocket ID reicht

Spricht eine App OIDC nativ, braucht man im Grunde vier Dinge: Issuer-URL, client_id, exakte Redirect-URIs und ein Secret mit Client-Credentials. Der Rest ist Discovery über /.well-known/openid-configuration. Zot liest genau das aus k8s/clusters/hydra/applications/zot/zot/app/zot.json und leitet Menschen zur Consent-UI — die Registry übernimmt Login und Session, kein Envoy-OIDC-Shim davor.

Von Harbor zu Zot auf ARM64

Warum das Lab Harbor gegen eine leichtere OCI-Registry getauscht hat, nach dem Umzug der Talos-Nodes auf aarch64.

Harbor ist ein vollständiges Registry-Produkt: Scanning, Replication, Projekte und mehrere Backing-Services. Auf aarch64 ist es schwergewichtig und passte schlecht zu vier Turing-RK1-Modulen auf hydra — ARM-Support holt noch auf. Nach dem Wechsel der Talos-Nodes von x86-64 auf ARM64 blieb Harbor im Apps-Overlay auskommentiert (FIXME: Still no ARM64 support); Zot bedient oci.this-is-fine.io.

Gebraucht wurden vor allem push, pull, sign und admit — nicht Harbors Scanning-UI und Replikations-Policies. Manifeste: k8s/clusters/hydra/applications/zot/; Harbor-Referenz unter k8s/clusters/hydra/applications/harbor/ bleibt im Repo.

git clone https://seed.this-is-fine.io/z6MQ7ck2rSh7h4qEgbRYg3ftnrGv.git lab

Harbor vs. Zot

Harbor (geplant)Zot (läuft)
FootprintViele Pods (core, jobs, DB, Redis, …)Registry-Prozess + PVC (30 Gi ceph-block)
ARM64Nicht deployedPasst zu den RK1-Boards
APIHarbor-spezifischOCI — crane, skopeo, cosign
GitOpsGroßer Helm-StackHelmRelease + zot.json
Hostnamehub.this-is-fine.io (ungenutzt)oci.this-is-fine.io

Harbor-Manifeste und HelmRepository bleiben als Referenz; das Overlay aktiviert sie nicht.

Cilium BGP und ein UniFi UDM-SE als Lab-iBGP-Router

Kubernetes-LoadBalancer-IPs ins LAN annoncieren mit Ciliums BGP Control Plane und dem UDM als Peer.

Ein Service vom Typ LoadBalancer auf Bare Metal ohne Cloud-Integration endet gern in Pending. MetalLB löst das mit L2-ARP oder BGP — im Lab übernimmt Cilium (CNI 1.19.2 auf hydra) die BGP Control Plane v2 : Worker-Nodes annonzieren Service-IPs an den UniFi UDM-SE (CLUSTER_BGP_ROUTER_ADDR 192.168.100.1/32), der als Default-Gateway und iBGP-Peer im LAN 192.168.100.0/26 fungiert.

Damit erreichen Phones, Laptops und das Tailnet die Envoy-Gateway-VIPs ohne NodePort — stabile 10.103.0.1 / .0.2 / .0.3 aus Pool 10.103.0.0/28, nicht „welcher Node hat heute zufällig Port 30080“. Pod-DNS für *.tif.internal läuft über Service tailnet-dns auf ClusterIP 10.96.0.54 (Wert TAILNET_DNS_CLUSTERIP in k8s/clusters/hydra/flux/vars/cluster-config.yaml).

git clone https://seed.this-is-fine.io/z6MQ7ck2rSh7h4qEgbRYg3ftnrGv.git lab

Cilium-BGP-CRs: k8s/clusters/hydra/applications/kube-system/cilium/conf/ (Template unter k8s/templates/cluster/applications/kube-system/cilium/conf/). Werte aus k8s/clusters/hydra/flux/vars/cluster-config.yaml.

Topologie: Gateway lernt, was Kubernetes meint

              ┌─────────────────────────┐
              │  UniFi UDM-SE (ASN 65535)│
              │  Default Gateway / iBGP   │
              │  192.168.100.1          │
              └────────────┬────────────┘
                           │  iBGP (ASN 64512, bgp-policy=worker)
         ┌─────────────────┼─────────────────┐
         ▼                 ▼                 ▼
    ┌─────────┐       ┌─────────┐       ┌─────────┐
    │ Worker 1│       │ Worker 2│       │ Worker 3│
    │ .32     │       │ .33     │       │ .34     │
    └─────────┘       └─────────┘       └─────────┘
         │                 │                 │
         └─────────────────┴─────────────────┘
              advertise LoadBalancer pool 10.103.0.0/28
                .0.1  shared-gateway-external
                .0.2  shared-gateway-internal
                .0.3  shared-gateway-tailnet

    Controller (192.168.100.31): bgp-policy=controller  →  keine BGP-Sessions
    K8s API VIP: 192.168.100.30 (Talos, nicht BGP-Pool)
flowchart TB UDM["UDM-SE ASN 65535\n192.168.100.1"] subgraph workers["hydra workers bgp-policy=worker"] W1["192.168.100.32"] W2["192.168.100.33"] W3["192.168.100.34"] end subgraph lb["CiliumLoadBalancerIPPool 10.103.0.0/28"] E[".0.1 external"] I[".0.2 internal"] T[".0.3 tailnet"] end UDM <-->|iBGP 64512| workers workers --> lb LAN["LAN 192.168.100.0/26"] --> UDM TS["Headscale tag:fabric"] --> T

Worker tragen Ingress-Dataplane; die Control Plane bleibt außerhalb der Session — Talos-Labels bgp-policy worker vs. controller in k8s/clusters/hydra/bootstrap/talos/talconfig.yaml.

Envoy Gateway und der Wechsel von Traefik zu Gateway API

Drei shared Gateways ersetzen Traefik-CRDs — Headscale als Beispiel (öffentliche API, private UI).

Lange Zeit war Traefik der Ingress-Workhorse im Lab — IngressRoute, Middleware, Entrypoints, alles controller-spezifisch. Das funktionierte, aber jede Migration zu einem anderen Ingress-Controller wäre ein Rewrite gewesen, nicht ein Re-Apply.

Gateway API standardisiert Listener, Routes und TLS am Gateway; Envoy Gateway ist auf hydra die Implementation — ein Helm-Release, drei shared Gateways unter k8s/clusters/common/applications/envoy-gateway-system/envoy-gateway/gateway/, pro App eine HTTPRoute (oder TCPRoute) statt Traefik-only-CRDs. Envoy-spezifisches Tuning geht über BackendTrafficPolicy und verwandte CRDs.

Traefik-Gewohnheiten vs. Gateway API

TraefikGateway API
IngressRoute + EntrypointsHTTPRoute + parentRefs an einem Gateway-Listener
Pro-App TLS / Middleware CRDsTLS am Gateway; Filter auf HTTPRoute; Envoy-Policies bei Bedarf
Traefik-Host-Regelnhostnames und matches auf gateway.networking.k8s.io/v1

Die mentale Verschiebung: wer verbindet bestimmt das Gateway — Internet, Lab-Browser mit internal CA, oder Tailnet-Client — nicht „welcher Ingress-Controller-Zufall“.

Cosign und Kyverno für ZeroClaw-Container-Images

CI signiert OCI-Digests mit Cosign — Kyverno verifyImages lehnt unsignierte ZeroClaw-Pods bei Admission ab.

Selbst gebaute Images im Homelab verdienen dieselbe Disziplin wie in Produktion: in CI bauen, den Digest signieren, beim Pod-CREATE prüfen. Ein Tag wie :latest ist bequem und unzuverlässig; eine Signatur auf sha256:… bleibt an genau den Bits hängen, die die Pipeline gebaut und nach Zot geschoben hat.

Auf dem Primärcluster hydra (Kontext admin@hydra, LAN 192.168.100.0/26) läuft die Kette aus Cosign ( Sigstore ), privater Zot -Registry unter oci.this-is-fine.io und Kyverno mit verifyImages . Unsignierte ZeroClaw-Pods starten nicht — der ReplicaSet bleibt stehen, Kyverno sagt im Event, warum. CI hält den privaten Signing-Key; die ClusterPolicy den passenden Public Key. Manifeste: k8s/clusters/hydra/applications/kube-system/kyverno/policies/ für eigene Images, k8s/clusters/common/applications/kube-system/kyverno/policies/ für Flux-Controller. Repo zum Nachbauen:

git clone https://seed.this-is-fine.io/z6MQ7ck2rSh7h4qEgbRYg3ftnrGv.git lab

Supply-Chain im Überblick

Forge-CI baut reproduzierbar, pushed mit skopeo (kein docker push zur Registry — das Tier verträgt keinen Push-Sturm), signiert danach den Digest. Zot legt Image und Sigstore-Artefakt nebeneinander ab. Beim Pod-CREATE prüft Kyverno; cosign verify vom Laptop nutzt dieselbe Logik wie der Admission-Webhook.