#!/usr/bin/env python3 """Validate the committed realm template or a runtime Keycloak CLI export.""" from __future__ import annotations import argparse import json from pathlib import Path from typing import Any REALM_NAME = "keycloak-patterns" PUBLIC_CLIENT = "spa-public" CONFIDENTIAL_CLIENTS = { "token-mediating-confidential": "${TOKEN_MEDIATING_CLIENT_SECRET}", "bff-confidential": "${BFF_CLIENT_SECRET}", "edge-proxy": "${EDGE_PROXY_CLIENT_SECRET}", } EXPECTED_ROLES = {"admin-role", "user-role"} EXPECTED_USERS = {"admin-user", "regular-user"} def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument( "realm_file", nargs="?", type=Path, default=Path("keycloak/import/keycloak-patterns-realm.json"), ) parser.add_argument( "--runtime", action="store_true", help="validate an expanded CLI export instead of the committed template", ) return parser.parse_args() def require(condition: bool, message: str) -> None: if not condition: raise SystemExit(f"realm validation failed: {message}") def indexed(items: list[dict[str, Any]], key: str) -> dict[str, dict[str, Any]]: return {str(item[key]): item for item in items} def validate(path: Path, runtime: bool) -> None: document = json.loads(path.read_text(encoding="utf-8")) require(document.get("realm") == REALM_NAME, f"realm must be {REALM_NAME}") require(document.get("enabled") is True, "realm must be enabled") require(document.get("accessTokenLifespan") == 300, "access token TTL must be 300s") require(document.get("revokeRefreshToken") is True, "refresh token rotation must be enabled") require(document.get("refreshTokenMaxReuse") == 0, "refresh token max reuse must be 0") clients = indexed(document.get("clients", []), "clientId") expected_client_ids = {PUBLIC_CLIENT, *CONFIDENTIAL_CLIENTS} require(expected_client_ids <= clients.keys(), "all four pattern clients must exist") if not runtime: require(clients.keys() == expected_client_ids, "template must declare exactly four clients") spa = clients[PUBLIC_CLIENT] require(spa.get("publicClient") is True, "spa-public must be a public client") require("secret" not in spa, "spa-public must not have a client secret") require(spa.get("standardFlowEnabled") is True, "spa-public standard flow must be enabled") require( spa.get("directAccessGrantsEnabled") is False, "spa-public direct access grants must be disabled", ) require( spa.get("attributes", {}).get("pkce.code.challenge.method") == "S256", "spa-public must enforce PKCE S256", ) for client_id, placeholder in CONFIDENTIAL_CLIENTS.items(): client = clients[client_id] require(client.get("publicClient") is False, f"{client_id} must be confidential") require( client.get("clientAuthenticatorType") == "client-secret", f"{client_id} must use client-secret authentication", ) secret = client.get("secret") if runtime: require(isinstance(secret, str) and len(secret) >= 16, f"{client_id} secret missing") require(not secret.startswith("${"), f"{client_id} placeholder was not resolved") else: require(secret == placeholder, f"{client_id} must use an env placeholder") roles = { role["name"] for role in document.get("roles", {}).get("realm", []) if "name" in role } require(EXPECTED_ROLES <= roles, "admin-role and user-role must exist") users = indexed(document.get("users", []), "username") require(EXPECTED_USERS <= users.keys(), "both baseline users must exist") if not runtime: expected_passwords = { "admin-user": "${ADMIN_USER_PASSWORD}", "regular-user": "${REGULAR_USER_PASSWORD}", } for username, placeholder in expected_passwords.items(): credentials = users[username].get("credentials", []) require(len(credentials) == 1, f"{username} must have one initial credential") require( credentials[0].get("value") == placeholder, f"{username} password must use an env placeholder", ) print( f"realm validated: {REALM_NAME}, four pattern clients, " f"{len(EXPECTED_ROLES)} roles, {len(EXPECTED_USERS)} users" ) if __name__ == "__main__": arguments = parse_args() validate(arguments.realm_file, arguments.runtime)