Files
box/bin/identity-resolve.py
operator 41499e069d feat(identity): per-scope network identity plane (slices 1-5)
Fingerprint map + pure resolver (account umbrella / key-level scope
rule), live Warp provider on warp-* structures, broker lifecycle
(up/down/cycle/exec/routes/status/bind), wireguard+socks boilerplate
stubs, agent-manager bind integration. CLI carries emails and
fingerprints only; key bytes never appear. 41 committed tests.
2026-10-08 04:03:45 +00:00

187 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""identity-resolve.py — Pure identity resolution for the identity plane.
Reads identity-map.json (fingerprints only, never key material) and
resolves an API-key fingerprint to its network-identity scope:
api_key -> account_origin(s); one origin rolls scope UP to the
umbrella account, two or more keep scope DOWN at the key itself.
This module is pure + total (missing/corrupt map -> empty, unknown
fingerprint -> None). CLI output carries emails and fingerprints only;
key bytes never appear here — there is no code path that reads them
except `fp`, which hashes stdin and prints only the digest.
Usage:
identity-resolve.py fp < keyfile # print sha256: fingerprint
identity-resolve.py lookup <fingerprint> # print scope JSON
identity-resolve.py check # validate map schema
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional
REPO_ROOT = Path(__file__).resolve().parent.parent
MAP_FILE = REPO_ROOT / "identity-map.json"
LABEL_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,22}$")
def fingerprint_hex(material: bytes) -> str:
"""sha256: fingerprint of raw key bytes."""
return "sha256:" + hashlib.sha256(material).hexdigest()
def load_map(path: str | Path = MAP_FILE) -> Dict[str, Any]:
"""Load the identity map. Missing/corrupt -> {"accounts": {}}."""
try:
with open(path, "r") as f:
data = json.load(f)
if isinstance(data, dict) and isinstance(
data.get("accounts"), dict):
return data
except Exception:
pass
return {"accounts": {}}
def find_key(map_data: Dict[str, Any],
fp: str) -> Optional[Dict[str, Any]]:
"""Locate a key record by fingerprint.
Returns {"email", "key"} or None. Top-level '_' entries ignored.
"""
if not fp:
return None
accounts = map_data.get("accounts")
if not isinstance(accounts, dict):
return None
for email, rec in accounts.items():
if not isinstance(rec, dict):
continue
keys = rec.get("keys")
if not isinstance(keys, list):
continue
for k in keys:
if isinstance(k, dict) and k.get("fp") == fp:
return {"email": email, "key": k}
return None
def resolve_scope(map_data: Dict[str, Any],
fp: str) -> Optional[Dict[str, Any]]:
"""Resolve a fingerprint to its scope unit.
Single origin -> {"scope": "account", "unit": email, ...}.
Multiple origins -> {"scope": "key", "unit": fp, ...}.
Unknown fingerprint -> None. Result carries emails + fingerprints
only (no key material exists anywhere in this module).
"""
found = find_key(map_data, fp)
if found is None:
return None
key = found["key"]
origins = key.get("origins")
if not isinstance(origins, list) or not origins:
return None
origins = [str(o) for o in origins]
if len(origins) == 1:
return {"scope": "account", "unit": origins[0],
"email": found["email"], "origins": origins,
"label": key.get("label", "")}
return {"scope": "key", "unit": fp, "email": found["email"],
"origins": origins, "label": key.get("label", "")}
def scope_slug(scope: Dict[str, Any]) -> str:
"""Deterministic netvm label for a scope (fits label validation).
Account scopes: id-<email-fragment>-<hash7>. Key scopes:
id-k-<fp-hex-prefix>. Always matches ^[a-z0-9][a-z0-9-]{0,22}$.
"""
unit = str(scope.get("unit", ""))
if scope.get("scope") == "key":
hexpart = re.sub(r"[^0-9a-f]", "", unit.lower())[:12] or "0"
return "id-k-%s" % hexpart
frag = re.sub(r"[^a-z0-9]+", "-", unit.lower()).strip("-")[:12]
frag = frag.strip("-") or "x"
tag = hashlib.sha256(unit.encode()).hexdigest()[:7]
return "id-%s-%s" % (frag, tag)
def check_map(map_data: Dict[str, Any]) -> List[str]:
"""Validate map schema. Returns a list of problem strings (empty OK)."""
problems: List[str] = []
accounts = map_data.get("accounts")
if not isinstance(accounts, dict):
return ["top-level 'accounts' must be an object"]
seen_fps: Dict[str, str] = {}
for email, rec in accounts.items():
if not isinstance(email, str) or "@" not in email:
problems.append("account key %r is not an email" % (email,))
if not isinstance(rec, dict) or not isinstance(
rec.get("keys"), list):
problems.append("account %r: 'keys' must be a list" % (email,))
continue
for i, k in enumerate(rec["keys"]):
where = "%s.keys[%d]" % (email, i)
if not isinstance(k, dict):
problems.append("%s: not an object" % where)
continue
fp = k.get("fp", "")
if not re.fullmatch(r"sha256:[0-9a-f]{64}", str(fp)):
problems.append("%s: bad fingerprint %r" % (where, fp))
elif fp in seen_fps:
problems.append("%s: fingerprint already listed under %s"
% (where, seen_fps[fp]))
else:
seen_fps[fp] = email
origins = k.get("origins")
if not isinstance(origins, list) or not origins or not all(
isinstance(o, str) and o for o in origins):
problems.append("%s: 'origins' must be a non-empty "
"string list" % where)
return problems
def main(argv: Optional[List[str]] = None) -> int:
ap = argparse.ArgumentParser(prog="identity-resolve.py")
ap.add_argument("--map", default=str(MAP_FILE),
help="identity map (default: identity-map.json)")
sub = ap.add_subparsers(dest="cmd", required=True)
sub.add_parser("fp", help="print sha256: fingerprint of stdin bytes")
p = sub.add_parser("lookup", help="resolve a fingerprint to scope JSON")
p.add_argument("fp")
sub.add_parser("check", help="validate the map schema")
args = ap.parse_args(argv)
if args.cmd == "fp":
sys.stdout.write(fingerprint_hex(sys.stdin.buffer.read()) + "\n")
return 0
if args.cmd == "lookup":
scope = resolve_scope(load_map(args.map), args.fp)
if scope is None:
print("unknown fingerprint (not in map)")
return 1
scope["slug"] = scope_slug(scope)
print(json.dumps(scope, indent=2))
return 0
problems = check_map(load_map(args.map))
if problems:
print("%s INVALID:" % args.map)
for prob in problems:
print(" - %s" % prob)
return 1
print("%s OK" % args.map)
return 0
if __name__ == "__main__":
sys.exit(main())