net-kingdom/sso-mfa/k8s/lldap/test_bind_split.py
tegwick e6f4e28164
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
NK-WP-0054-T05 phase 3a: founder-run bind-split helper with end-to-end test
prepare-custody, plan, accounts and verify for ld-admin and the svc-* accounts;
named rotations for the Authelia bind and LLDAP_LDAP_USER_PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:00:49 +01:00

164 lines
5.3 KiB
Python

"""End-to-end test of bind-split.py against a throwaway LLDAP on the pinned image (NK-WP-0054-T05).
Synthetic only: generated passwords, 127.0.0.1, container and volume removed afterwards. Skipped when
docker or the pinned image is unavailable.
"""
import importlib.util
import json
import os
import re
import secrets
import subprocess
import uuid
from pathlib import Path
import pytest
HERE = Path(__file__).resolve().parent
def load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
split = load("bind_split", HERE / "bind-split.py")
available = (
subprocess.run(
["docker", "image", "inspect", split.IMAGE], capture_output=True, check=False
).returncode
== 0
)
pytestmark = pytest.mark.skipif(
not available, reason="docker or pinned LLDAP image unavailable"
)
@pytest.fixture
def lab(tmp_path):
pytest.importorskip("ldap3")
rehearsal = load("bind_split_rehearsal", HERE / "bind-split-rehearsal.py")
admin_password = secrets.token_hex(20)
root = tmp_path / "custody"
(root / "lldap").mkdir(parents=True, mode=0o700)
root.chmod(0o700)
env = root / "lldap/secrets.env"
env.write_text(
f"LLDAP_JWT_SECRET={secrets.token_hex(32)}\nLLDAP_LDAP_USER_PASS={admin_password}\n"
)
env.chmod(0o600)
directory = rehearsal.Directory(uuid.uuid4().hex[:10])
directory.start("admin", admin_password)
try:
yield root, directory.http, admin_password, directory
finally:
directory.remove()
def admin_fd(password):
read, write = os.pipe()
os.write(write, (password + "\n").encode())
os.close(write)
return read
def test_full_split_on_a_production_shaped_directory(lab, tmp_path):
root, endpoint, admin_password, _ = lab
assert split.prepare_custody(root)["changed_keys"][0] == "LLDAP_LDAP_USER_PASS"
text = (root / "lldap/secrets.env").read_text()
assert (
admin_password not in text
and (root / "lldap/secrets.env").stat().st_mode & 0o077 == 0
)
for key in ("LLDAP_LDAP_USER_PASS", *split.SERVICE_KEYS):
assert re.search(rf"^{key}=[A-Za-z0-9]{{40}}$", text, re.MULTILINE)
with pytest.raises(split.Refused):
split.prepare_custody(root)
assert (
split.main(
[
"plan",
"--endpoint",
endpoint,
"--admin-password-fd",
str(admin_fd(admin_password)),
"--receipt",
str(tmp_path / "plan.json"),
]
)
== 0
)
plan = json.loads((tmp_path / "plan.json").read_text())["result"]["accounts"]
assert (
all(plan[a]["action"] == "create" for a in split.TARGETS)
and plan["admin"]["in_lldap_admin"]
)
directory = split.Directory(endpoint)
token = directory.login("admin", admin_password)
passwords = split.custody_values(root)
dry = split.accounts(directory, token, passwords, apply=False)
assert not dry["applied"] and "ld-admin" not in directory.snapshot(token)[0]
done = split.accounts(directory, token, passwords, apply=True)
assert done["applied"] and all(done["signs_in"].values())
assert all(done["accounts"][a]["action"] == "ok" for a in split.TARGETS)
again = split.accounts(directory, token, passwords, apply=True)
assert all(again["accounts"][a]["action"] == "ok" for a in split.TARGETS)
result = split.verify(directory, token, passwords)
assert result["verdict"] == "pass"
# The human then changes their own password; the old shared one must stop working.
split.set_password(endpoint, token, "admin", secrets.token_hex(20))
retired = split.verify(
directory,
directory.login("ld-admin", passwords["ld-admin"]),
passwords,
admin_password,
)
assert retired["old_shared_password_refused"] and retired["verdict"] == "pass"
receipt = tmp_path / "plan.json"
assert admin_password not in receipt.read_text()
assert all(value not in receipt.read_text() for value in passwords.values())
def test_existing_account_outside_the_service_domain_is_refused(lab):
root, endpoint, admin_password, _ = lab
split.prepare_custody(root)
directory = split.Directory(endpoint)
token = directory.login("admin", admin_password)
directory.gql(
token,
"mutation($u: CreateUserInput!) { createUser(user: $u) { id } }",
{"u": {"id": "svc-keycape", "email": "someone@example.test"}},
)
result = split.accounts(directory, token, split.custody_values(root), apply=True)
assert (
not result["applied"]
and result["accounts"]["svc-keycape"]["action"] == "refuse"
)
assert "ld-admin" not in directory.snapshot(token)[0]
def test_wrong_admin_password_is_refused_before_any_change(lab, tmp_path):
root, endpoint, _, _ = lab
split.prepare_custody(root)
code = split.main(
[
"accounts",
"--endpoint",
endpoint,
"--admin-password-fd",
str(admin_fd("wrong")),
str(root),
"--apply",
"--receipt",
str(tmp_path / "r.json"),
]
)
assert code == 1 and not (tmp_path / "r.json").exists()