Ich habe einen Kubernetes-Cluster auf Dual-Stack umgebaut, damit git IPv6-only werden konnte — dann sagte AWS Nein
Ein selbst gehostetes GitLab lässt sich nicht auf inbound-IPv6-only umstellen, solange es der AWS-IAM-OIDC-Provider ist. Der Grund ist unauffällig, bis man ihn kennt: AWS STS holt den Verifikationsschlüssel (JWKS) für jeden OIDC-Login ausschließlich über IPv4 — kein A-Record, kein AssumeRoleWithWebIdentity, jede CI-Pipeline mit AWS-OIDC-Login tot. Ich hatte mir vorgenommen, öffentliches IPv4 Schritt für Schritt aus dem gesamten Estate zu entfernen, zuletzt die GitLab-Box, die git. und registry. bedient. Der Umbau sollte ein Einzeiler sein: A-Records droppen, v4 in der Security-Group zu, fertig. War er nicht — gleich zweimal nicht. Dieser Beitrag zeigt beide Blocker, wie ich sie eingekreist habe, und warum der zweite mehr über OIDC-Vertrauensmodelle lehrt als jede Dokumentation.
TL;DR
TL;DR: Ein selbst gehostetes GitLab lässt sich nicht auf inbound-IPv6-only umstellen, solange es der AWS-IAM-OIDC-Provider ist. AWS STS holt den OIDC-Verifikationsschlüssel (JWKS) ausschließlich über IPv4. Kein A-Record → kein AssumeRoleWithWebIdentity → jede CI-Pipeline, die per OIDC an AWS geht, fällt aus.
Der Plan: weniger öffentliches IPv4
Öffentliche IPv4-Adressen sind bei AWS inzwischen kostenpflichtig — und jede offene v4-Adresse ist Angriffsfläche. Also habe ich Schritt für Schritt IPv4 aus dem Estate entfernt: Die CI-Worker-Flotte läuft IPv6-only (Egress über NAT66), Registries und Paketquellen laufen dual-stack, ein kleiner dedizierter Pool behält v4 nur für die Jobs, die es nachweislich brauchen.
Das letzte Stück: die GitLab-Box, die sowohl git. als auch registry. bedient, inbound auf IPv6-only stellen. Die Box selbst ist längst IPv6-fähig — globale Adresse, Default-v6-Route, funktionierender v6-Egress, curl -6 liefert 200. Sollte also ein Einzeiler sein: A-Records droppen, v4 in der Security-Group zumachen, fertig — war er nicht, gleich zweimal nicht.
Blocker 1: das Pod-Netz war einstöckig
Erster Versuch, sofort zurückgerollt. Das Prod-k3s lief single-stack IPv4 — ohne --cluster-cidr/--service-cidr-Flags, also mit den Defaults, und die Pods bekamen nur eine IPv4-Adresse. ArgoCDs Repo-Server-Pod konnte ein AAAA-only-git schlicht nicht erreichen (network is unreachable) — sämtliche GitOps-Syncs standen still.
Die Krux: Die Service-CIDR ist nach dem Cluster-Init unveränderlich. Dual-Stack nachrüsten heißt also Cluster neu aufsetzen. Ich habe das als Blue/Green-Rebuild gemacht — neuer Node, dual-stack von Anfang an, Daten rüber, DNS umgeschwenkt, alter Node als Rollback stehen gelassen. Nettes Nebenprodukt: ein echter Disaster-Recovery-Drill unter kontrollierten Bedingungen, Downtime rund 15 Minuten.
Jetzt hatten die Pods IPv6, ArgoCD synct über v6 — Blocker 1 erledigt. Also git wieder auf IPv6-only, diesmal musste es klappen.
Der Twist: GitLab ist der OIDC-Provider
Minuten nach dem Umschalten wurde jede tofu apply-Pipeline rot:
Error: failed to retrieve credentials … operation error STS:
AssumeRoleWithWebIdentity … InvalidIdentityToken: Couldn't retrieve
verification key from your identity provider
Und nicht nur Terraform — auch die GitLab-Patch-Automatik und das Container-Signing. Alles, was per OIDC an AWS ging, war tot.
Der Grund, sobald man ihn sieht, ist offensichtlich: Die CI authentifiziert sich schlüssellos an AWS über GitLab als OIDC-Provider. Der Ablauf:
- Der CI-Job bekommt ein signiertes OIDC-Token von GitLab.
- Er ruft
sts:AssumeRoleWithWebIdentitymit diesem Token auf. - AWS STS validiert die Signatur — und holt dafür den öffentlichen Schlüssel vom JWKS-Endpoint des Providers:
git.…/oauth/discovery/keys.
Schritt 3 ist der Haken. Dieser Fetch kommt von AWS' Infrastruktur nach außen — und zwar über IPv4. Sobald git. keinen A-Record mehr hat, kann AWS den Schlüssel nicht laden. Das Token ist technisch einwandfrei; AWS kann es nur nicht prüfen. Ergebnis: InvalidIdentityToken.
Das Fiese: AWS cached den Schlüssel eine Weile. Der Apply direkt nach dem Umschalten lief noch grün — der nächste, nach Cache-Ablauf, nicht mehr. Neun Minuten Differenz. Wer nur den ersten sieht, hält die Umstellung für geglückt.
Wie man es einkreist
Zwei Kommandos genügen:
# Löst der Name noch auf v4 auf?
dig +short A git.example.eu # → leer
# Ist der JWKS-Endpoint über v4 erreichbar? (das, was STS tut)
curl -4 git.example.eu/oauth/discovery/keys # → 000 (fail)
curl -6 git.example.eu/oauth/discovery/keys # → 200 (ok)
v6 antwortet, v4 nicht, AWS kann nur v4 — die Diagnose steht. Der Fix war der Revert (A-Records und v4-Ingress zurück), und weil die CI sich nicht selbst reparieren kann — der Fix ist ein Apply —, musste ich ihn einmal außerhalb der Pipeline einspielen.
Die Lehre
Es ist dieselbe Lektion wie bei Blocker 1, nur mit einem Consumer, den man leicht übersieht:
Bevor du einen A-Record droppst, prüfe für jeden Consumer, dass er den Namen über IPv6 erreicht — auch externe SaaS wie AWS STS, nicht nur die offensichtlichen Clients in deinem eigenen Netz.
„Der Host ist IPv6-fähig“ reicht nicht. Entscheidend ist, ob alle, die ihn ansprechen, ihn über IPv6 ansprechen können. Ein OIDC-Provider ist per Definition etwas, das von außen validiert wird — und dieser Validator gehört nicht dir.
Der Ausweg
git kann inbound-IPv6-only werden — aber erst, wenn kein externer AWS-Aufrufer es mehr über IPv4 erreichen muss. Konkret: die CI-Jobs die Apply-Rolle nicht mehr per GitLab-OIDC assumen lassen, sondern über die EC2-Instance-Role des CI-Runners — ein Aufrufer innerhalb von AWS, ganz ohne JWKS-Fetch von außen. Bis dahin bleibt git dual-stack. Rund 3,60 € im Monat für eine Elastic IP sind kein Grund, die halbe CI zu brechen.
Das dual-stack Cluster war die Mühe trotzdem wert — nur eben aus einem anderen Grund als geplant.
Häufige Fragen
Was ist der langfristige Fix, damit git doch IPv6-only werden kann?+
Die CI-Jobs die Apply-Rolle nicht mehr über GitLab-OIDC assumen lassen, sondern über die EC2-Instance-Role des CI-Runners — ein Aufrufer innerhalb von AWS, der keinen externen JWKS-Fetch mehr benötigt. Sobald kein externer AWS-Aufrufer git mehr über IPv4 erreichen muss, kann die Umstellung auf inbound-IPv6-only ohne diesen Blocker wiederholt werden.
Wie erkenne ich dieses Problem, bevor ich selbst einen A-Record droppe?+
Mit zwei Befehlen: dig +short A <host> zeigt, ob noch ein IPv4-Eintrag existiert, und ein Vergleich von curl -4 gegen curl -6 auf den relevanten Endpoint (z. B. den JWKS-Discovery-Pfad) zeigt, ob der Dienst über IPv6 tatsächlich erreichbar ist. Entscheidend ist, das für jeden Consumer zu prüfen, der die Adresse braucht — nicht nur für die offensichtlichen Clients im eigenen Netz.
Warum ließ sich Dual-Stack nicht einfach am laufenden Cluster nachrüsten?+
Die Service-CIDR ist in k3s nach dem Cluster-Init unveränderlich. War der Cluster einmal single-stack IPv4 initialisiert — hier ohne gesetzte --cluster-cidr/--service-cidr-Flags, also mit den IPv4-Defaults —, bleibt nur ein Neuaufbau, um Dual-Stack sauber zu bekommen.
Warum lief der erste tofu apply nach der Umstellung noch grün?+
AWS STS cached den JWKS-Schlüssel eine Weile. Der Apply direkt nach dem Umschalten griff noch auf den gecachten Schlüssel zu und lief grün — erst der nächste Apply, nach Ablauf des Caches rund neun Minuten später, schlug fehl. Wer nur den ersten Testlauf prüft, hält die Migration fälschlich für erfolgreich.
Betrifft das nur GitLab als OIDC-Provider, oder auch andere Identity-Provider?+
Strukturell betrifft es jeden selbst gehosteten OIDC-Provider, dessen JWKS-Endpoint ein externer Cloud-Verifier wie AWS STS über das Netz erreichen muss — das ist eine Eigenschaft des OIDC-Vertrauensmodells, keine GitLab-Besonderheit. Ob andere Identity-Provider oder andere Cloud-Anbieter denselben IPv4-only-Fetch-Pfad verwenden, haben wir hier nicht einzeln verifiziert; wer eine vergleichbare Migration plant, sollte das für die eigene Provider-/Cloud-Kombination gezielt testen.
Warum nicht einfach bei Dual-Stack bleiben, statt IPv6-only zu erzwingen?+
Öffentliches IPv4 ist bei AWS kostenpflichtig, und jede offene v4-Adresse vergrößert die Angriffsfläche — deshalb der Plan, es Schritt für Schritt loszuwerden. Für git kam aber der Punkt, an dem der Nutzen (eine gesparte Elastic IP für rund 3,60 €/Monat) den Schaden (komplett ausfallende OIDC-Pipelines) nicht rechtfertigt. Bis der OIDC-Trust-Pfad umgebaut ist, bleibt git bewusst dual-stack.
Fazit
Zwei Blocker, eine Lektion: „IPv6-fähig“ ist eine Eigenschaft des Hosts, nicht der Migration. Entscheidend ist, ob jeder Consumer — der offensichtliche im eigenen Netz genauso wie der unauffällige externe SaaS-Validator — den Namen über IPv6 erreicht, bevor der A-Record fällt. Der Blue/Green-Rebuild des Clusters war die richtige Entscheidung und lieferte einen echten DR-Drill gratis dazu. git bleibt vorerst dual-stack, bis die CI ihre AWS-Rolle über die Instance-Role statt über GitLab-OIDC assumed — dann fällt der externe JWKS-Fetch weg, und IPv6-only wird wieder möglich.
Ich begleite Ihre IPv6-Migration und Kubernetes-Dual-Stack-Umbauten — inklusive der Consumer, die man leicht übersieht.
DNS- und Netzwerk-Audit vor dem A-Record-Drop, Cluster-CIDR-Planung für Neuaufbauten, und die Prüfung externer Abhängigkeiten wie OIDC-Provider, Webhooks oder SaaS-Integrationen auf IPv6-Erreichbarkeit.
Plattform-Betrieb statt Beratung auf Papier: Ich prüfe, baue um und härte Ihre Infrastruktur laufend.
Über den Autor
![[Translate to English:] Foto von Kai Ole Hartwig.](/fileadmin/_processed_/e/9/csm_ole-neu_73323ad80d.jpeg)
Kai Ole Hartwig
Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.
