Surface redacted directory bind failures before native onboarding
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Identity provider journey acceptance / provider (push) Successful in 6s
Build and Publish identity-provisioner / build-and-push (push) Successful in 10s

Map uncaught HTTPError from LLDAP login to a structured
dependency_unavailable response, add /readyz as the provisioner-to-directory
preflight, keep /healthz as process liveness, and run the contract in CI.
Auth rejection is not retried during cooldown.

NK-WP-0036-T05 remains in progress until the immutable image is published,
pinned with /readyz, and one native login/create/password-setup journey is
verified.

Assistant: grok
Assistant-Session: 01a09dc6-3f0e-78f1-a884-c8c703c24ddf
This commit is contained in:
tegwick 2026-09-14 04:46:29 +02:00
parent d90e3b27f2
commit c8e07615c3
9 changed files with 554 additions and 33 deletions

View file

@ -6,7 +6,7 @@ import os
from urllib.parse import parse_qs, urlsplit
import secrets
from provisioner import LLDAPProvisioner, _directory_username, dispatch
from provisioner import DependencyFailure, LLDAPProvisioner, _directory_username, dispatch
from password_setup import LLDAPPasswordSetter, PasswordSetupGrants
@ -19,6 +19,17 @@ class Handler(BaseHTTPRequestHandler):
path = urlsplit(self.path)
if path.path == "/healthz":
return self._send(200, {"status": "ok"})
if path.path == "/readyz":
try:
return self._send(200, self.provisioner.preflight())
except DependencyFailure as exc:
return self._send(503, exc.payload())
except RuntimeError:
return self._send(503, {
"error": "dependency_unavailable",
"dependency": "directory",
"reason": "unreachable",
})
if path.path == "/setup/password":
token = parse_qs(path.query).get("token", [""])[0]
if not self.password_setups.valid(token):
@ -41,6 +52,8 @@ class Handler(BaseHTTPRequestHandler):
return self._send(404, {"error": "not_found"})
except (ValueError, json.JSONDecodeError) as exc:
return self._send(400, {"error": "invalid_request", "message": str(exc)})
except DependencyFailure as exc:
return self._send(503, exc.payload())
except RuntimeError:
return self._send(503, {"error": "dependency_unavailable"})
response = asdict(result)
@ -64,7 +77,7 @@ class Handler(BaseHTTPRequestHandler):
return_to = self.password_setups.consume(token, password)
except ValueError as exc:
return self._html(400, _setup_page(token, str(exc)))
except RuntimeError:
except (DependencyFailure, RuntimeError):
return self._html(503, _failed_page())
return self._html(200, _complete_page(return_to))