85 lines
3.8 KiB
Markdown
85 lines
3.8 KiB
Markdown
# Hetzner-DNS: die alte API ist abgeschaltet
|
|
|
|
**Gemessen am 31. Juli 2026, am Live-Server und von außen.**
|
|
|
|
```
|
|
https://dns.hetzner.com/api/v1/zones → 301 → https://console.hetzner.com/ (HTML)
|
|
https://api.hetzner.cloud/v1/zones → 401 ohne Token, 200 mit dem Token des Betreibers
|
|
```
|
|
|
|
Hetzner hat die DNS-Verwaltung am 7. Oktober 2025 in die Cloud-Konsole verlegt.
|
|
Die alte DNS-Konsole ging am **20. Mai 2026** in den Nur-Lese-Betrieb; seither
|
|
leitet der alte Endpunkt auf die Weboberfläche um. **Alte Token und Zonen-IDs
|
|
sind mit der neuen Konsole nicht kompatibel** und wurden nicht übernommen.
|
|
|
|
## Was dadurch kaputt ist
|
|
|
|
- `App\Services\Dns\DnsTokenCheck` — meldet `zone_not_found`, obwohl Token und
|
|
Zone in Ordnung sind. Seit v1.3.77 meldet es immerhin ehrlich
|
|
`zone_list_unreadable` mit dem Anfang der HTML-Antwort.
|
|
- `App\Services\Dns\HttpHetznerDnsClient` — **das ist das Teure.** Er legt den
|
|
A-Eintrag jeder Kundeninstanz an. Solange er auf den alten Endpunkt zeigt,
|
|
stirbt jede Bereitstellung in `ConfigureDnsAndTls` — nachdem der Kunde bezahlt
|
|
hat. Genau der Satz steht als Begründung auf der Bereitschaftsseite.
|
|
- Der Token des Betreibers ist bereits ein Cloud-Token und antwortet mit 200.
|
|
Es fehlt **nur** der Code.
|
|
|
|
## Die neue Schnittstelle
|
|
|
|
Aus `https://docs.hetzner.cloud/cloud.spec.json` gezogen, nicht geraten.
|
|
|
|
**Anmeldung:** `Authorization: Bearer <token>` — nicht mehr `Auth-API-Token`.
|
|
|
|
**Basis:** `https://api.hetzner.cloud/v1`
|
|
|
|
| Zweck | Aufruf |
|
|
|---|---|
|
|
| Zonen auflisten | `GET /zones` → `{"zones":[{id,name,…}], "meta":{…}}` |
|
|
| Eine Zone direkt | `GET /zones/{id_or_name}` — **der Name geht direkt**, kein Suchen mehr |
|
|
| Eintragssatz anlegen | `POST /zones/{zone}/rrsets` |
|
|
| Eintragssatz ersetzen | `POST /zones/{zone}/rrsets/{name}/{type}/actions/set_records` |
|
|
| Eintragssatz löschen | `DELETE /zones/{zone}/rrsets/{name}/{type}` |
|
|
|
|
**Das Modell hat sich geändert:** keine einzelnen Records mehr, sondern
|
|
**RRSets**, adressiert über `{name}/{typ}`. Ein RRSet trägt mehrere Werte.
|
|
`set_records` ersetzt den ganzen Satz — damit wird das Schreiben idempotent, was
|
|
der alte Client mit Suchen-und-Aktualisieren nachbauen musste.
|
|
|
|
Rumpf beim Anlegen, Pflichtfelder `name`, `type`, `records`:
|
|
|
|
```json
|
|
{ "name": "kunde", "type": "A", "ttl": 60,
|
|
"records": [{"value": "203.0.113.10"}] }
|
|
```
|
|
|
|
**Zwei Fallen im `name`:**
|
|
|
|
1. Er darf **nicht** auf den Zonennamen enden. `kunde`, nicht
|
|
`kunde.clupilot.cloud` — der alte Client hat den vollen Namen geschickt.
|
|
2. Für die Zonenwurzel ist es `@`, und er muss klein geschrieben sein.
|
|
|
|
`PUT /zones/{zone}/rrsets/{name}/{type}` ist **nicht** das Schreiben der Werte —
|
|
es setzt nur `labels`. Wer dort die Records erwartet, schreibt ins Leere.
|
|
|
|
## Was zu tun ist
|
|
|
|
1. `HttpHetznerDnsClient` auf Basis, Kopfzeile und RRSets umstellen. Der
|
|
Zonen-Lookup entfällt: der Name geht direkt in den Pfad.
|
|
2. `DnsTokenCheck` dito. Die Schreibprobe bleibt der Kern der Prüfung — ein
|
|
lesender Token sieht sonst genauso aus wie ein schreibender, und der
|
|
Unterschied fällt erst auf, wenn ein Kunde bezahlt hat.
|
|
3. `FakeHetznerDnsClient` und die Tests nachziehen.
|
|
4. Den Namen ohne Zonensuffix bilden — Falle 1 oben. Ein Test, der genau das
|
|
festhält, ist billiger als der erste Kunde, dessen Adresse
|
|
`kunde.clupilot.cloud.clupilot.cloud` heißt.
|
|
|
|
## Wieso das so lange gesucht wurde
|
|
|
|
Die Prüfung stufte nur 401 und 403 als abgelehnt ein und las danach den Rumpf.
|
|
Jede andere Antwort — hier eine 301 auf eine HTML-Seite — hatte kein `zones`,
|
|
und `?? []` machte daraus „keine Zone in diesem Konto". Eine Aussage über ein
|
|
fremdes Hetzner-Konto, hergeleitet aus einer Antwort, die niemand angesehen hat.
|
|
v1.3.76 und v1.3.77 haben das behoben; die Meldung zeigt jetzt Status und die
|
|
ersten Zeichen der Antwort. Ohne diese zwei Schritte wäre die Ursache weiter im
|
|
Dunkeln.
|