#!/usr/bin/env python3 # /// script # dependencies = ["PyYAML>=6,<7"] # /// """Check that every known credential has a rotation lane (NK-WP-0055-T15). Sources of "known credentials", all in this repository: - service-identity declarations whose custody is not `none` (federated logins hold nothing); - the backticked secret names in Credential Management v0.2 section 6; - the numbered rows of the crisis custody list that are not struck through (retired). Each lane must name its blast radius, verification step and owner lane. NetKingdom owner-lane references must exist in `workplans/`; other repositories' references are checked for shape only. Offline and read-only; the register holds names and procedures, never values. """ from __future__ import annotations import argparse import glob import re import sys from pathlib import Path import yaml ROOT = Path(__file__).resolve().parents[2] CLASSES = {"service", "database", "client-secret", "openbao-issued", "break-glass"} STATES = {"admitted", "designed", "deferred", "none"} LEVELS = {"minimal", "low", "medium", "high"} TASK_REF = re.compile(r"^[A-Z][A-Z-]*-WP-\d{4}(-T\d{2})?$") def declared_identities(root): """(identities that hold a credential, every declared identity).""" found, every = set(), set() for path in sorted( glob.glob(str(root / "tools/service-identity/declarations/*.yaml")) ): doc = yaml.safe_load(Path(path).read_text()) for ident in doc.get("identities", []): name = f"{doc['service']}/{ident['name']}" every.add(name) if set(ident.get("custody") or []) - {"none"}: found.add(name) return found, every def standard_secrets(root): text = (root / "canon/standards/credential-management_v0.2.md").read_text() section = text.split("## 6. Secret Rotation", 1)[1].split("\n## ", 1)[0] return set(re.findall(r"^\| `([A-Z][A-Z0-9_]+)` \|", section, flags=re.MULTILINE)) def crisis_items(root): text = (root / "docs/crisis-custody-list.md").read_text() return { m.group(1) for m in re.finditer(r"^\| (\d+[a-z]?) \| (?!~~)", text, flags=re.MULTILINE) } def nk_tasks(root): ids = set() for path in glob.glob(str(root / "workplans/**/*.md"), recursive=True): ids.update( re.findall( r"^id: (NK-WP-\d{4}(?:-T\d{2})?)$", Path(path).read_text(), flags=re.MULTILINE, ) ) return ids def check(register, root): errors = [] lanes = register.get("lanes", []) seen = {"identities": {}, "credentials": {}, "crisis_items": {}} known_nk = nk_tasks(root) for lane in lanes: lid = lane.get("id", "?") if lane.get("class") not in CLASSES: errors.append(f"{lid}: class must be one of {sorted(CLASSES)}") if lane.get("state") not in STATES: errors.append(f"{lid}: state must be one of {sorted(STATES)}") radius = lane.get("blast_radius") or {} if ( radius.get("level") not in LEVELS or not radius.get("affects") or not radius.get("effect") ): errors.append(f"{lid}: blast_radius needs level, affects and effect") for field in ("path", "verification"): if not str(lane.get(field) or "").strip(): errors.append(f"{lid}: {field} is empty") refs = lane.get("owner_lane") or [] if not refs: errors.append(f"{lid}: owner_lane is empty") for ref in refs: if not TASK_REF.match(ref): errors.append(f"{lid}: owner_lane {ref!r} is not a workplan or task id") elif ref.startswith("NK-WP-") and ref not in known_nk: errors.append(f"{lid}: owner_lane {ref} not found in workplans/") for key, owners in seen.items(): for item in lane.get(key) or []: item = str(item) if item in owners: errors.append(f"{lid}: {key} {item} already in lane {owners[item]}") owners[item] = lid holding, declared = declared_identities(root) secrets, items = standard_secrets(root), crisis_items(root) for kind, required, allowed in ( ("identities", holding, declared), ("credentials", secrets, secrets), ("crisis_items", items, items), ): for missing in sorted(required - set(seen[kind])): errors.append( f"no lane for {kind[:-1] if kind != 'identities' else 'identity'} {missing}" ) for extra in sorted(set(seen[kind]) - allowed): errors.append( f"lane {seen[kind][extra]} names unknown {kind} entry {extra}" ) return lanes, errors def main(argv=None): parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument( "register", nargs="?", default=str(ROOT / "tools/rotation-lanes/lanes.yaml") ) parser.add_argument("--root", default=str(ROOT)) args = parser.parse_args(argv) lanes, errors = check( yaml.safe_load(Path(args.register).read_text()), Path(args.root) ) by_state = {} for lane in lanes: by_state[lane.get("state")] = by_state.get(lane.get("state"), 0) + 1 for error in errors: print(f"ERROR: {error}") print( f"rotation lanes: {len(lanes)} ({', '.join(f'{n} {s}' for s, n in sorted(by_state.items()))}); " f"{len(errors)} error(s)" ) return 1 if errors else 0 if __name__ == "__main__": sys.exit(main())