"""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()