Skip to content

Commit 7031da2

Browse files
docs(labda): Runbook fuer Wildcard-DNS, Cilium-LB und Vault-Zertifikate (#25)
Wie ein Cluster in LabDA seine Services unter eigenen Namen erreichbar macht: eine LoadBalancer-IP per Cilium L2, ein Wildcard-Record im PowerDNS, Zertifikate aus Vault-PKI. Am 2026-08-21 auf seed-labda-1 (rke2) Ende zu Ende durchgemessen und von einem Laptop ausserhalb des Cluster-Subnetzes per Namen aufgerufen. Als Vorlage geschrieben, nicht als Protokoll — durchparametrisiert mit <cluster>, weil der naechste Platform-Cluster in LabDA dasselbe braucht. Die beiden hinteren Abschnitte sind der eigentliche Wert: FALLEN. Vaults "403 permission denied" bedeutet DREI verschiedene Dinge — verboten, Pfad existiert nicht, oder die PKI-Rolle lehnt ab —, und der Fehler unterscheidet sie nicht. Ein ClusterIssuer meldet Ready ("VaultVerified"), obwohl jede Signieranfrage scheitert: geprueft wird nur die Anmeldung. Und auf macOS loesen dig und curl unterschiedlich auf — dig fragt Nameserver der Reihe nach, der System-Resolver parallel mit "schnellste gewinnt", weshalb ein Heimrouter mit 7ms NXDOMAIN den Firmen-Resolver mit 20ms schlaegt. BEKANNTE GRENZEN. allow_glob_domains: false an der PKI-Rolle verbietet Wildcard-ZERTIFIKATE (der Wildcard im DNS ist davon unberuehrt) — also ein Zertifikat pro Service. In-cluster loest die Zone nicht auf, weil der Node-Resolver den PowerDNS nicht erreicht. Und LabDA hat noch kein clusterbook, die IPs kommen deshalb von Hand aus 10.100.136.220-229; sobald es steht, ersetzt XIPReservation zwei Schritte in einem Zug. Keine Secrets im Text, nur Vault-Pfade — wie in docs/pdns-vsphere/. README-Tabelle und Referenz-Link mitgezogen.
1 parent 9913458 commit 7031da2

2 files changed

Lines changed: 226 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
| **[kustomize][kustomize]** | Kustomize provides a solution for customizing Kubernetes resource configuration free from templates. |
2222
| **[cilium][cilium]** | Cilium is an open source, cloud native solution for providing, securing, and observing network connectivity between workloads. |
2323
| **[certificates][certificates]** | Certificate management, PKI, TLS — tools and workflows for managing certificates in Kubernetes. |
24+
| **[labda-wildcard-dns-lb-certs][labda-wildcard-dns-lb-certs]** | LabDA runbook: one LoadBalancer IP per cluster via Cilium L2, a wildcard record in PowerDNS, and Vault-PKI certificates for the services underneath. |
2425
| **[velero][velero]** | Velero gives you tools to back up and restore your Kubernetes cluster resources and persistent volumes. |
2526
| **[tekton][tekton]** | Tekton is a powerful yet flexible Kubernetes-native open source framework for creating CI/CD systems. |
2627

@@ -109,6 +110,7 @@
109110
[kubernetes-monitoring]: https://github.com/stuttgart-things/docs/blob/main/kubernetes-monitoring.md
110111
[kubernetes-networking]: https://github.com/stuttgart-things/docs/blob/main/kubernetes-networking.md
111112
[kubernetes-workloads]: https://github.com/stuttgart-things/docs/blob/main/kubernetes-workloads.md
113+
[labda-wildcard-dns-lb-certs]: https://github.com/stuttgart-things/docs/blob/main/labda-wildcard-dns-lb-certs.md
112114
[kustomize]: https://github.com/stuttgart-things/docs/blob/main/kustomize.md
113115
[linux]: https://github.com/stuttgart-things/docs/blob/main/linux.md
114116
[minio]: https://github.com/stuttgart-things/docs/blob/main/minio.md

labda-wildcard-dns-lb-certs.md

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# LabDA: Wildcard-DNS + Cilium-LB + Vault-Zertifikate pro Cluster
2+
3+
Wie ein Cluster in LabDA seine Services unter eigenen Namen erreichbar macht — eine LoadBalancer-IP, ein Wildcard-Record, Zertifikate aus Vault-PKI.
4+
5+
**Am 2026-08-21 auf `seed-labda-1` (rke2, `10.100.136.52`) Ende zu Ende durchgemessen** und von einem Laptop ausserhalb des Cluster-Subnetzes per Namen aufgerufen. Vorlage fuer den naechsten Platform-Cluster.
6+
7+
```
8+
Cilium gibt eine LB-IP frei
9+
-> PowerDNS: *.<cluster>.4sthings.tiab.ssc.sva.de -> diese IP
10+
-> Service antwortet
11+
-> Zertifikat aus Vault-PKI
12+
```
13+
14+
## Die Landschaft, bevor es losgeht
15+
16+
Es gibt in LabDA **zwei getrennte DNS-Welten**, und die Verwechslung kostet am meisten Zeit:
17+
18+
| Zone | Autoritaet | fuer uns |
19+
|---|---|---|
20+
| `tiab.labda.sva.de`, `labda.sva.de` | Infoblox (`infoblox01.labwi`, `infoblox02.labda`, …) | Konzern-DNS, **nicht schreibbar**. Hier leben die Node-Namen. |
21+
| `4sthings.tiab.ssc.sva.de` | **PowerDNS `10.100.136.115`** (`pdns-vsphere.tiab.labda.sva.de`), API auf `:8443` | unsere Zone. Hier entstehen die Wildcards. |
22+
23+
Der PowerDNS antwortet auf `tiab.labda.sva.de` mit **REFUSED** — wer dort Cluster-Namen sucht, fragt den falschen Server. Die Delegation von Infoblox auf ihn ist korrekt eingetragen (`4sthings.tiab.ssc.sva.de NS -> pdns-vsphere.tiab.labda.sva.de`).
24+
25+
**Das Muster in der Zone ist ein Wildcard pro Cluster**, TTL 60, Typ A. Bestand am 2026-08-21: `*.app`/`*.mgmt`/`*.preapp`/`*.sthings-infra-dev` -> `.220` (Altbestand, antwortet nicht mehr), `*.test2` -> `.222`, `*.dev7` -> `.223`, `*.dev8` -> `.226`, `*.dev11` -> `.228`, `*.seed-labda-1` -> `.221`.
26+
27+
**IP-Bereich:** `10.100.136.220–229` ist fuer uns reserviert und im DNS ausgenommen. Es gibt in LabDA (Stand 2026-08-21) **kein clusterbook**, die IP wird also von Hand vergeben — freie pruefen, indem man die Wildcards der Zone auflistet (siehe Schritt 3).
28+
29+
Der API-Key des PowerDNS steht in Vault unter `apps/powerdns`, nicht in diesem Dokument.
30+
31+
---
32+
33+
## Schritt 1 — Vault: PKI-Rolle und Recht darauf
34+
35+
Der von der Platform emittierte Issuer (`vault-pki-k8s`) signiert `pki/sign/tiab.labda.sva.de` — die **falsche Domain** fuer diese Zone. `pkiRole` im Platform-XRD ist ein einzelner String und deckt nur einen Issuer ab, es braucht also einen zweiten.
36+
37+
Auf dem **LabDA-Vault** (`vault-vsphere.tiab.labda.sva.de:8200`) existiert die Rolle `4sthings.tiab.ssc.sva.de` bereits. Was fehlt, ist das Recht der cert-manager-Rolle darauf.
38+
39+
**1a. Policy anlegen** (Token mit Schreibrecht auf `sys/policies/acl/*`):
40+
41+
```hcl
42+
# vault policy write pki-issue-4sthings -
43+
path "pki/sign/4sthings.tiab.ssc.sva.de" {
44+
capabilities = ["create", "update"]
45+
}
46+
```
47+
48+
Nur `sign`, **nicht `issue`**: cert-manager benutzt ausschliesslich den sign-Endpunkt; `issue` wuerde zusaetzlich erlauben, sich den privaten Schluessel von Vault erzeugen zu lassen.
49+
50+
Eine eigene Policy statt `pki-issue` zu erweitern, weil `pki-issue` der XRD-Default fuer **jeden** Cluster ist — sie zu erweitern gaebe den Signierpfad an alles frei, was sie traegt.
51+
52+
**1b. Der Rolle zuordnen**, im ClusterStack (`stuttgart-things`, `crossplane/xrs/clusterstack/labda/<cluster>.yaml`):
53+
54+
```yaml
55+
spec:
56+
platform:
57+
vaultIssuer:
58+
pkiRole: tiab.labda.sva.de
59+
tokenPolicies:
60+
- pki-issue
61+
- pki-issue-4sthings
62+
```
63+
64+
`tokenPolicies`, nicht `policies` — letzteres verlangt einen AppRole, der Policies schreiben darf. Nach dem Anwenden reconciled der vault-auth-Workspace und schreibt die Policies an die Kubernetes-Auth-Rolle. Kontrolle:
65+
66+
```bash
67+
kubectl -n crossplane-system get workspace <cluster>-certmanager-vault-auth \
68+
-o json | jq -r '.spec.forProvider.vars[]|select(.key=="token_policies")|.value'
69+
```
70+
71+
## Schritt 2 — ClusterIssuer fuer die Zone
72+
73+
Gleiche Anmeldung wie der vorhandene Issuer, nur ein anderer Pfad:
74+
75+
```yaml
76+
apiVersion: cert-manager.io/v1
77+
kind: ClusterIssuer
78+
metadata:
79+
name: vault-pki-4sthings
80+
spec:
81+
vault:
82+
server: https://vault-vsphere.tiab.labda.sva.de:8200
83+
path: pki/sign/4sthings.tiab.ssc.sva.de
84+
caBundleSecretRef:
85+
key: ca.crt
86+
name: vault-pki-ca # legt der vaultIssuer-Zweig der Platform an
87+
auth:
88+
kubernetes:
89+
mountPath: /v1/auth/<cluster>-certmanager
90+
role: certmanager
91+
serviceAccountRef:
92+
name: cert-manager
93+
```
94+
95+
> **`Ready: True` heisst hier NICHT, dass er signieren darf.** cert-manager prueft beim Verify nur die Anmeldung. Der Beweis ist das erste ausgestellte Zertifikat.
96+
97+
## Schritt 3 — Cilium: LoadBalancer-IP
98+
99+
Voraussetzung ist `enable-l2-announcements=true` in der cilium-Config (auf den machinery-Clustern gesetzt). **L2-Announcement traegt nur auf einem echten Netzsegment** — auf kind waere die IP von aussen tot.
100+
101+
Freie IP finden:
102+
103+
```bash
104+
curl -sSk -H "X-API-Key: $PDNS_KEY" \
105+
https://pdns-vsphere.tiab.labda.sva.de:8443/api/v1/servers/localhost/zones/4sthings.tiab.ssc.sva.de. \
106+
| jq -r '.rrsets[]|select(.name|startswith("*"))|"\(.name) -> \(.records[0].content)"'
107+
```
108+
109+
**Achtung auf die API-Versionen** — bei Cilium 1.19.3 sind sie gemischt:
110+
111+
```yaml
112+
apiVersion: cilium.io/v2 # Pool: schon v2
113+
kind: CiliumLoadBalancerIPPool
114+
metadata:
115+
name: <cluster>-lb
116+
spec:
117+
blocks:
118+
- start: 10.100.136.221
119+
stop: 10.100.136.221
120+
---
121+
apiVersion: cilium.io/v2alpha1 # L2-Policy: noch v2alpha1
122+
kind: CiliumL2AnnouncementPolicy
123+
metadata:
124+
name: <cluster>-l2
125+
spec:
126+
loadBalancerIPs: true
127+
nodeSelector:
128+
matchLabels:
129+
kubernetes.io/os: linux
130+
```
131+
132+
Kontrolle: `kubectl get ciliumloadbalancerippool` muss `IPS AVAILABLE 1` und `CONFLICTING False` zeigen.
133+
134+
## Schritt 4 — Wildcard im PowerDNS
135+
136+
```bash
137+
Z=4sthings.tiab.ssc.sva.de.
138+
curl -sSk -H "X-API-Key: $PDNS_KEY" -X PATCH \
139+
-d '{"rrsets":[{"name":"*.<cluster>.'"$Z"'","type":"A","ttl":60,
140+
"changetype":"REPLACE","records":[{"content":"10.100.136.221","disabled":false}]}]}' \
141+
"https://pdns-vsphere.tiab.labda.sva.de:8443/api/v1/servers/localhost/zones/$Z"
142+
```
143+
144+
HTTP **204** heisst angenommen. Kontrolle direkt am autoritativen Server, nicht ueber den eigenen Resolver:
145+
146+
```bash
147+
dig +short @10.100.136.115 beliebig.<cluster>.4sthings.tiab.ssc.sva.de
148+
```
149+
150+
## Schritt 5 — Service mit Zertifikat
151+
152+
**Ein Zertifikat pro Service**, nicht eines pro Cluster — siehe „Bekannte Grenzen".
153+
154+
```yaml
155+
apiVersion: cert-manager.io/v1
156+
kind: Certificate
157+
metadata: {name: test-svc, namespace: default}
158+
spec:
159+
secretName: test-svc-tls
160+
issuerRef: {name: vault-pki-4sthings, kind: ClusterIssuer}
161+
commonName: test.<cluster>.4sthings.tiab.ssc.sva.de
162+
dnsNames: ["test.<cluster>.4sthings.tiab.ssc.sva.de"]
163+
---
164+
apiVersion: v1
165+
kind: Service
166+
metadata: {name: test-svc, namespace: default}
167+
spec:
168+
type: LoadBalancer
169+
loadBalancerIP: 10.100.136.221
170+
selector: {app: test-svc}
171+
ports: [{port: 443, targetPort: 443}]
172+
```
173+
174+
Ausstellung dauert ~20 s. Der Rest ist ein beliebiger Pod, der TLS mit `test-svc-tls` terminiert.
175+
176+
## Verifikation
177+
178+
Die Reihenfolge ist bewusst so — jeder Schritt schliesst eine Fehlerursache aus:
179+
180+
```bash
181+
# 1. autoritativ: gibt es den Namen?
182+
dig +short @10.100.136.115 test.<cluster>.4sthings.tiab.ssc.sva.de
183+
184+
# 2. LB, L2 und Zertifikat, am DNS vorbei
185+
curl --cacert vault-ca.pem \
186+
--resolve test.<cluster>.4sthings.tiab.ssc.sva.de:443:10.100.136.221 \
187+
https://test.<cluster>.4sthings.tiab.ssc.sva.de/
188+
189+
# 3. mit echter Aufloesung, vom Arbeitsplatz
190+
curl -k https://test.<cluster>.4sthings.tiab.ssc.sva.de/
191+
```
192+
193+
Schritt 2 **mit `--cacert` gegen die Vault-CA**, nicht mit `-k`: sonst prueft man nur, dass irgendein TLS steht.
194+
195+
---
196+
197+
## Fallen, die uns Zeit gekostet haben
198+
199+
**Vaults `403 permission denied` bedeutet drei verschiedene Dinge:** verboten, Pfad existiert nicht, oder die PKI-Rolle lehnt die Anfrage ab (z. B. ein Wildcard bei `allow_glob_domains: false`). Der Fehler unterscheidet das nicht — er verraet absichtlich nicht, ob ein Pfad existiert. Nur ein Blick in `pki/roles/<name>` trennt es auf.
200+
201+
**Ein `Ready`-Status ist kein Koennen.** Der ClusterIssuer meldete `VaultVerified`, obwohl jede Signieranfrage mit 403 scheiterte.
202+
203+
**macOS: `dig` und `curl` loesen unterschiedlich auf.** `dig` fragt die Nameserver der Reihe nach, der System-Resolver (und damit `curl` und Browser) fragt sie **parallel und nimmt die schnellste Antwort**. Ein Heimrouter, der in 7 ms NXDOMAIN sagt, schlaegt den Firmen-Resolver mit 20 ms. Abhilfe pro Arbeitsplatz:
204+
205+
```bash
206+
sudo tee /etc/resolver/4sthings.tiab.ssc.sva.de >/dev/null <<'EOF'
207+
nameserver 10.100.136.115
208+
EOF
209+
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
210+
```
211+
212+
Dabei den **PowerDNS selbst** eintragen, nicht den Firmen-Resolver: bei Split-Tunneling wird `10.100.0.0/16` geroutet, `10.10.0.0/16` aber nicht — und `scutil`s `reach: Reachable` ist da optimistisch. Dass der PowerDNS nicht rekursiv ist, stoert nicht, die Datei leitet ihm nur seine eigene Zone zu.
213+
214+
**Negative Antworten haelt die Zone 60 Minuten** (SOA-Minimum 3600 s), waehrend die Records selbst TTL 60 haben. Ein NXDOMAIN auf einen frisch angelegten Namen ist trotzdem zuerst ein *Resolver*-Verdacht, kein Cache-Verdacht.
215+
216+
## Bekannte Grenzen
217+
218+
**Keine Wildcard-Zertifikate.** Die PKI-Rolle `4sthings.tiab.ssc.sva.de` hat `allow_glob_domains: false` (`allowed_domains: 4sthings.tiab.ssc.sva.de`, `allow_subdomains: true`, `allow_bare_domains: false`). Ein `*.<cluster>.…` wird mit 403 abgelehnt, ein normaler Name darunter in ~20 s ausgestellt. Der Wildcard im **DNS** ist davon unberuehrt. Wer ein Wildcard-**Zertifikat** will, muss die Rolle im Vault aendern — dann traegt allerdings ein einziger kompromittierter Pod den Schluessel fuer alle Namen des Clusters.
219+
220+
**In-cluster loest die Zone nicht auf.** CoreDNS forwardet `.` an die `/etc/resolv.conf` des Nodes, dort steht `10.100.101.5` — und dieser Resolver erreicht `10.100.136.115:53` nicht (er beantwortet die Elternzonen und die NS-Abfrage, laeuft aber beim Folgen der Delegation in den Timeout). Von aussen ist das egal; fuer Service-zu-Service ueber diese Namen braucht es einen CoreDNS-Stub auf `10.100.136.115`, den ein Pod nachweislich direkt erreicht.
221+
222+
**Der PowerDNS selbst ist alt.** 4.5.2, Uptime 40,8 Monate, Pflicht-Sicherheitsadvisory (2022-01) fuellt 99 % des Log-Rings, kein DNSSEC, kein NS-Record im Apex der Zone (die Delegation traegt der Elternteil). RFC2136-Updates werden abgelehnt — fuer statische Wildcards irrelevant.
223+
224+
**Fuer ein ganzes Team gehoert die Zone ins Split-DNS des VPN.** Sonst braucht jeder Arbeitsplatz die `/etc/resolver`-Datei, und wer sie vergisst, haelt den Dienst fuer kaputt.

0 commit comments

Comments
 (0)