keycloak: cleanup LDAP federation

This commit is contained in:
Brad Stein 2026-01-02 18:45:45 -03:00
parent b509234aee
commit 503a9264c5

View File

@ -2,7 +2,7 @@
apiVersion: batch/v1
kind: Job
metadata:
name: keycloak-ldap-federation-3
name: keycloak-ldap-federation-4
namespace: sso
spec:
backoffLimit: 2
@ -60,6 +60,7 @@ spec:
import os
import time
import urllib.parse
import urllib.error
import urllib.request
base_url = os.environ["KEYCLOAK_SERVER"].rstrip("/")
@ -176,11 +177,21 @@ spec:
raise SystemExit(f"Unexpected components response: {status}")
components = components or []
ldap_component = None
for c in components:
if c.get("providerId") == "ldap" and c.get("name") in ("openldap", "ldap"):
ldap_component = c
break
ldap_components = [c for c in components if c.get("providerId") == "ldap" and c.get("id")]
# Select a canonical LDAP federation provider deterministically.
# Duplicate LDAP providers can cause Keycloak admin/user queries to fail if any one of them is misconfigured.
candidates = []
for c in ldap_components:
if c.get("name") not in ("openldap", "ldap"):
continue
cfg = c.get("config") or {}
if (cfg.get("connectionUrl") or [None])[0] == ldap_url:
candidates.append(c)
if not candidates:
candidates = [c for c in ldap_components if c.get("name") in ("openldap", "ldap")]
candidates.sort(key=lambda x: x.get("id", ""))
ldap_component = candidates[0] if candidates else None
ldap_component_id = ldap_component["id"] if ldap_component else None
desired = {
@ -296,4 +307,56 @@ spec:
)
if status not in (201, 204):
raise SystemExit(f"Unexpected group mapper create status: {status}")
# Cleanup duplicate LDAP federation providers and their child components (mappers, etc).
# Keep only the canonical provider we updated/created above.
try:
status, fresh_components, _ = http_json(
"GET",
f"{base_url}/admin/realms/{realm}/components",
token,
)
if status != 200:
raise Exception(f"unexpected components status {status}")
fresh_components = fresh_components or []
dup_provider_ids = []
for c in fresh_components:
if c.get("providerId") != "ldap":
continue
if c.get("providerType") != "org.keycloak.storage.UserStorageProvider":
continue
cid = c.get("id")
if not cid or cid == ldap_component_id:
continue
dup_provider_ids.append(cid)
if dup_provider_ids:
for pid in dup_provider_ids:
# Delete child components first.
for child in fresh_components:
if child.get("parentId") != pid:
continue
child_id = child.get("id")
if not child_id:
continue
try:
http_json(
"DELETE",
f"{base_url}/admin/realms/{realm}/components/{child_id}",
token,
)
except urllib.error.HTTPError as e:
print(f"WARNING: failed to delete LDAP child component {child_id} (status={e.code})")
try:
http_json(
"DELETE",
f"{base_url}/admin/realms/{realm}/components/{pid}",
token,
)
except urllib.error.HTTPError as e:
print(f"WARNING: failed to delete duplicate LDAP provider {pid} (status={e.code})")
print(f"Cleaned up {len(dup_provider_ids)} duplicate LDAP federation providers")
except Exception as e:
print(f"WARNING: LDAP cleanup failed (continuing): {e}")
PY