#!/usr/bin/env python3 """Resolve security-zone declarations without guessing workload identity.""" from __future__ import annotations import argparse import hashlib import json import sys from datetime import date from pathlib import Path from typing import Any, Iterable, Mapping import yaml STANDARD = "security-zones_v0.1" INPUT_STANDARD = "zone-resolver-input_v0.1" PROFILE_STANDARD = "security-zone-control-profile_v0.1" MATURITY_RANK = {"M0": 0, "M1": 1, "M2": 2, "M3": 3} CRITICALITY_FLOOR = {"low": 0, "medium": 1, "high": 2, "critical": 3} DATACLASS_FLOOR = { "synthetic": 0, "internal": 1, "confidential": 2, "restricted": 3, } ZONE_FLOOR = { "z0-experimental": 0, "z1-operational": 1, "z2-protected": 2, "z2-continuity": 2, "z3-critical": 3, } PROFILE_ZONES = frozenset({*ZONE_FLOOR, "unknown"}) class DeclarationError(ValueError): """A declaration violates the security-zones_v0.1 contract.""" class ProfileError(ValueError): """A control profile lacks authoritative, total provenance.""" def _required(mapping: Mapping[str, Any], key: str, where: str) -> Any: value = mapping.get(key) if value is None or value == "" or value == []: raise DeclarationError(f"{where}.{key} is required") return value def _canonical(value: Any) -> Any: """Return a stable, mapping- and list-order-independent JSON value.""" if isinstance(value, Mapping): return {str(key): _canonical(value[key]) for key in sorted(value)} if isinstance(value, list): items = [_canonical(item) for item in value] return sorted( items, key=lambda item: json.dumps( item, sort_keys=True, separators=(",", ":"), default=str ), ) if isinstance(value, date): return value.isoformat() return value def _digest(value: Any) -> str: encoded = json.dumps( _canonical(value), sort_keys=True, separators=(",", ":"), default=str ).encode() return "sha256:" + hashlib.sha256(encoded).hexdigest() def _parse_date(value: Any, where: str) -> date: if isinstance(value, date): return value try: return date.fromisoformat(str(value)) except ValueError as exc: raise DeclarationError(f"{where} must be an ISO date") from exc def _services(document: Mapping[str, Any]) -> Iterable[dict[str, Any]]: services = document.get("services") if services is not None: if not isinstance(services, list) or not services: raise DeclarationError("services must be a non-empty list") if "zones" in document or "workload_identity" in document: raise DeclarationError( "multi-service declarations keep zones and workload_identity per service" ) yield from services return yield dict(document) def _validate_identity(service: str, identity: Any) -> dict[str, Any]: if not isinstance(identity, dict): raise DeclarationError(f"{service}.workload_identity must be a mapping") name = str(_required(identity, "name", f"{service}.workload_identity")) if name != service: raise DeclarationError( f"{service}.workload_identity.name must equal service, got {name!r}" ) _required(identity, "kind", f"{service}.workload_identity") _required(identity, "responsible_repo", f"{service}.workload_identity") bindings = _required( identity, "identity_bindings", f"{service}.workload_identity" ) if not isinstance(bindings, list) or not bindings: raise DeclarationError( f"{service}.workload_identity.identity_bindings must be a non-empty list" ) seen: set[tuple[str, str, str, str]] = set() for index, binding in enumerate(bindings): where = f"{service}.workload_identity.identity_bindings[{index}]" if not isinstance(binding, dict): raise DeclarationError(f"{where} must be a mapping") for key in ("scheme", "authority", "subject", "principal_type"): _required(binding, key, where) if binding["principal_type"] not in {"service", "agent"}: raise DeclarationError(f"{where}.principal_type must be service or agent") identity_key = ( str(binding["scheme"]), str(binding["authority"]), str(binding["subject"]), str(binding["principal_type"]), ) if identity_key in seen: raise DeclarationError(f"{where} duplicates an identity binding") seen.add(identity_key) return identity def _binding_refs(identity: Mapping[str, Any] | None) -> list[str]: if not identity: return [] return sorted( "/".join( str(binding[key]) for key in ("scheme", "authority", "subject") ) for binding in identity["identity_bindings"] ) def _validate_workload_ref( service: str, workload_ref: Any, identity: Mapping[str, Any] | None, ) -> dict[str, Any]: if workload_ref is None: if identity is None: return { "applicability": "applicable", "rapp_id": None, "name": None, "deployable": None, } return { "applicability": "applicable", "rapp_id": None, "name": str(identity["name"]), "deployable": None, } if not isinstance(workload_ref, Mapping): raise DeclarationError(f"{service}.workload_ref must be a mapping") applicability = str( _required(workload_ref, "applicability", f"{service}.workload_ref") ) if applicability not in {"applicable", "not-applicable"}: raise DeclarationError( f"{service}.workload_ref.applicability must be applicable or not-applicable" ) rapp_id = workload_ref.get("rapp_id") or None name = workload_ref.get("name") or None deployable = workload_ref.get("deployable") or None if applicability == "not-applicable": if any(value is not None for value in (rapp_id, name, deployable)): raise DeclarationError( f"{service}.workload_ref not-applicable must not carry a workload tuple" ) if identity is not None: raise DeclarationError( f"{service} cannot be both an authoritative workload and not-applicable" ) return { "applicability": applicability, "rapp_id": None, "name": None, "deployable": None, } if deployable is not None and rapp_id is None: raise DeclarationError( f"{service}.workload_ref.deployable requires rapp_id" ) if name is not None: name = str(name) if identity is not None and name is not None and name != identity["name"]: raise DeclarationError( f"{service}.workload_ref.name must equal workload_identity.name" ) return { "applicability": applicability, "rapp_id": str(rapp_id) if rapp_id is not None else None, "name": name, "deployable": str(deployable) if deployable is not None else None, } def _admission(service: str, zones: Mapping[str, Any]) -> tuple[str, str]: membership = str(_required(zones, "membership", f"{service}.zones")) if membership not in ZONE_FLOOR: raise DeclarationError(f"{service}.zones.membership is unknown: {membership!r}") context = _required(zones, "context", f"{service}.zones") if not isinstance(context, Mapping): raise DeclarationError(f"{service}.zones.context must be a mapping") maturity = str(_required(context, "maturity", f"{service}.zones.context")) criticality = str( _required(context, "criticality", f"{service}.zones.context") ) dataclass = str( _required(context, "data_classification", f"{service}.zones.context") ) if maturity not in MATURITY_RANK: raise DeclarationError(f"{service}.zones.context.maturity is invalid") if criticality not in CRITICALITY_FLOOR: raise DeclarationError(f"{service}.zones.context.criticality is invalid") if dataclass == "public": return "unknown", "public_data_classification_floor_unresolved" if dataclass == "n/a": _required(context, "data_classification_reason", f"{service}.zones.context") data_floor = 0 elif dataclass in DATACLASS_FLOOR: data_floor = DATACLASS_FLOOR[dataclass] else: raise DeclarationError( f"{service}.zones.context.data_classification is invalid" ) zone_rank = ZONE_FLOOR[membership] context_rank = max(CRITICALITY_FLOOR[criticality], data_floor) if zone_rank < context_rank: return "unsatisfied", f"{membership}_below_M{context_rank}_context_floor" if MATURITY_RANK[maturity] < zone_rank: return "unsatisfied", f"{maturity}_below_M{zone_rank}_zone_floor" if membership == "z2-continuity": supported = { str(fact) for item in zones["evidence"] if isinstance(item, Mapping) for fact in item.get("supports", []) } required = {"continuity-dependency", "recovery"} if not required.issubset(supported): return "unsatisfied", "continuity_evidence_incomplete" return "satisfied", "admission_floor_met" def _base_record( service: str, source: str, source_revision: str | None, workload_ref: Mapping[str, Any], identity: Mapping[str, Any] | None, ) -> dict[str, Any]: return { "subject_id": service, "workload_id": workload_ref.get("name"), "workload_ref": dict(workload_ref), "identity_bindings": _binding_refs(identity), "declared_zone": None, "admission": "unknown", "admission_reason": "zone_membership_absent", "effective_zone": "unknown", "membership_revision": None, "membership_revision_reason": "source_revision_absent" if source_revision is None else "zone_membership_absent", "guarantees": ["non-inferred-resolution"], "source": source, "source_revision": source_revision, } def resolve_service( service_entry: dict[str, Any], source: str, *, source_revision: str | None = None, workload_ref: Mapping[str, Any] | None = None, ) -> dict[str, Any]: service = str(_required(service_entry, "service", source)) identity_value = service_entry.get("workload_identity") identity = ( _validate_identity(service, identity_value) if identity_value is not None else None ) reference = _validate_workload_ref(service, workload_ref, identity) record = _base_record(service, source, source_revision, reference, identity) if reference["applicability"] == "not-applicable": record.update( { "workload_id": None, "admission": "not-applicable", "admission_reason": "catalog_declared_not_applicable", "effective_zone": None, "membership_revision_reason": "not_applicable", "guarantees": [ "catalog-declared-not-applicable", "non-inferred-resolution", ], } ) return record if reference["name"] is None: record["admission_reason"] = "workload_reference_unresolved" record["membership_revision_reason"] = "workload_reference_unresolved" return record record["workload_id"] = reference["name"] if identity is None: if service_entry.get("zones") is not None: raise DeclarationError( f"{service}.workload_identity is required beside zones" ) record["admission_reason"] = "workload_identity_unresolved" record["membership_revision_reason"] = "workload_identity_unresolved" record["guarantees"].append("explicit-workload-reference") return record record["guarantees"].extend( ["authoritative-workload-identity", "explicit-workload-reference"] ) zones = service_entry.get("zones") if zones is None: return record if not isinstance(zones, dict): raise DeclarationError(f"{service}.zones must be a mapping") if zones.get("standard") != STANDARD: raise DeclarationError(f"{service}.zones.standard must be {STANDARD}") for key in ( "responsible_party", "justification", "evidence", "reviewed", "review_due", ): _required(zones, key, f"{service}.zones") if not isinstance(zones["evidence"], list) or not zones["evidence"]: raise DeclarationError(f"{service}.zones.evidence must be a non-empty list") reviewed = _parse_date(zones["reviewed"], f"{service}.zones.reviewed") review_due = _parse_date(zones["review_due"], f"{service}.zones.review_due") if review_due <= reviewed: raise DeclarationError(f"{service}.zones.review_due must be after reviewed") admission, reason = _admission(service, zones) membership = str(zones["membership"]) effective = membership if admission == "satisfied" else "unknown" revision = None revision_reason = "source_revision_absent" if source_revision: revision = _digest( { "source_revision": source_revision, "workload_ref": reference, "workload_identity": identity, "zones": zones, } ) revision_reason = "source_bound" record.update( { "declared_zone": membership, "admission": admission, "admission_reason": reason, "effective_zone": effective, "membership_revision": revision, "membership_revision_reason": revision_reason, "guarantees": sorted( set( record["guarantees"] + ["explicit-zone-membership"] + (["source-revision-bound-membership"] if revision else []) ) ), } ) return record def validate_control_profile(profile: Any) -> dict[str, Any]: if not isinstance(profile, Mapping): raise ProfileError("control profile must be a mapping") if profile.get("standard") != PROFILE_STANDARD: raise ProfileError(f"control profile standard must be {PROFILE_STANDARD}") profile_id = profile.get("profile_id") version = profile.get("version") if not profile_id or not version: raise ProfileError("control profile requires profile_id and version") controls = profile.get("controls") if not isinstance(controls, Mapping) or not controls: raise ProfileError("control profile controls must be a non-empty mapping") normalized: dict[str, Any] = { "standard": PROFILE_STANDARD, "profile_id": str(profile_id), "version": str(version), "controls": {}, } for control_id in sorted(controls): definition = controls[control_id] where = f"controls.{control_id}" if not isinstance(definition, Mapping): raise ProfileError(f"{where} must be a mapping") policy_owner = definition.get("policy_owner") pep_owner = definition.get("pep_owner") policy_ref = definition.get("policy_ref") if not policy_owner or not pep_owner or not policy_ref: raise ProfileError( f"{where} requires policy_owner, pep_owner, and policy_ref" ) if "/" not in str(control_id) or not str(control_id).startswith( f"{policy_owner}/" ): raise ProfileError( f"{where} id must be owner-qualified by policy_owner" ) mappings = definition.get("zones") if not isinstance(mappings, Mapping): raise ProfileError(f"{where}.zones must be a mapping") supplied = set(mappings) if supplied != PROFILE_ZONES: missing = sorted(PROFILE_ZONES - supplied) extra = sorted(supplied - PROFILE_ZONES) raise ProfileError( f"{where}.zones must be total; missing={missing}, extra={extra}" ) normalized_zones: dict[str, dict[str, Any]] = {} for zone in sorted(PROFILE_ZONES): rule = mappings[zone] if not isinstance(rule, Mapping): raise ProfileError(f"{where}.zones.{zone} must be a mapping") stance = rule.get("stance") failure_mode = rule.get("failure_mode") if stance not in {"enforced", "advisory", "exempt"}: raise ProfileError(f"{where}.zones.{zone}.stance is invalid") if stance == "exempt": if failure_mode not in {None, ""}: raise ProfileError( f"{where}.zones.{zone} exempt must not have failure_mode" ) failure_mode = None elif failure_mode not in {"fail_open", "fail_closed"}: raise ProfileError( f"{where}.zones.{zone}.failure_mode is invalid" ) normalized_zones[zone] = { "stance": stance, "failure_mode": failure_mode, } normalized["controls"][str(control_id)] = { "policy_owner": str(policy_owner), "pep_owner": str(pep_owner), "policy_ref": str(policy_ref), "zones": normalized_zones, } return normalized def project_controls(record: dict[str, Any], profile: Mapping[str, Any]) -> None: zone = record.get("effective_zone") if zone not in PROFILE_ZONES: return record["control_profile"] = { "id": profile["profile_id"], "version": profile["version"], } record["controls"] = [] for control_id, definition in profile["controls"].items(): rule = definition["zones"][zone] record["controls"].append( { "id": control_id, "policy_owner": definition["policy_owner"], "pep_owner": definition["pep_owner"], "policy_ref": definition["policy_ref"], "stance": rule["stance"], "failure_mode": rule["failure_mode"], } ) def compare_snapshots( records: Iterable[Mapping[str, Any]], previous: Mapping[str, Any] | None, ) -> dict[str, Any]: current_by_id = {str(record["subject_id"]): record for record in records} previous_records = previous.get("records", []) if previous else [] previous_by_id = { str(record["subject_id"]): record for record in previous_records if isinstance(record, Mapping) and record.get("subject_id") } current_ids = set(current_by_id) previous_ids = set(previous_by_id) changed: list[dict[str, Any]] = [] for subject_id in sorted(current_ids & previous_ids): current = current_by_id[subject_id] prior = previous_by_id[subject_id] fields = ( "workload_ref", "identity_bindings", "declared_zone", "admission", "effective_zone", "membership_revision", ) if any(_canonical(current.get(key)) != _canonical(prior.get(key)) for key in fields): changed.append( { "subject_id": subject_id, "before_revision": prior.get("membership_revision"), "after_revision": current.get("membership_revision"), } ) return { "baseline": "previous" if previous is not None else "initial", "added": sorted(current_ids - previous_ids), "removed": sorted(previous_ids - current_ids), "changed": changed, } def _resolve_documents( sources: Iterable[tuple[Path, str | None, Mapping[str, Any]]], *, profile: Mapping[str, Any] | None = None, previous: Mapping[str, Any] | None = None, ) -> dict[str, Any]: records: list[dict[str, Any]] = [] errors: list[dict[str, str]] = [] profile_errors: list[str] = [] validated_profile: Mapping[str, Any] | None = None if profile is not None: try: validated_profile = validate_control_profile(profile) except ProfileError as exc: profile_errors.append(str(exc)) for path, source_revision, workload_refs in sources: try: document = yaml.safe_load(path.read_text()) or {} if not isinstance(document, Mapping): raise DeclarationError("document must be a mapping") for entry in _services(document): if not isinstance(entry, dict): raise DeclarationError("service entry must be a mapping") service = str(_required(entry, "service", str(path))) record = resolve_service( entry, str(path), source_revision=source_revision, workload_ref=workload_refs.get(service), ) if validated_profile is not None: project_controls(record, validated_profile) records.append(record) except (OSError, yaml.YAMLError, DeclarationError) as exc: errors.append({"source": str(path), "error": str(exc)}) records.sort(key=lambda record: str(record["subject_id"])) duplicate_ids = sorted( subject_id for subject_id in {record["subject_id"] for record in records} if sum(record["subject_id"] == subject_id for record in records) > 1 ) if duplicate_ids: errors.append( { "source": "resolved-records", "error": f"duplicate subject ids: {duplicate_ids}", } ) return { "ok": not errors and not profile_errors, "standard": STANDARD, "records": records, "changes": compare_snapshots(records, previous), "errors": errors, "profile_errors": profile_errors, } def resolve_paths( paths: Iterable[Path], *, source_revision: str | None = None, source_revisions: Mapping[str, str] | None = None, workload_refs: Mapping[str, Any] | None = None, profile: Mapping[str, Any] | None = None, previous: Mapping[str, Any] | None = None, ) -> dict[str, Any]: revisions = source_revisions or {} refs = workload_refs or {} sources = [ ( path, revisions.get(str(path), source_revision), refs, ) for path in paths ] return _resolve_documents(sources, profile=profile, previous=previous) def resolve_manifest( manifest: Mapping[str, Any], *, base_dir: Path, profile: Mapping[str, Any] | None = None, previous: Mapping[str, Any] | None = None, ) -> dict[str, Any]: if manifest.get("standard") != INPUT_STANDARD: raise DeclarationError(f"manifest.standard must be {INPUT_STANDARD}") entries = manifest.get("sources") if not isinstance(entries, list) or not entries: raise DeclarationError("manifest.sources must be a non-empty list") sources: list[tuple[Path, str | None, Mapping[str, Any]]] = [] for index, entry in enumerate(entries): if not isinstance(entry, Mapping): raise DeclarationError(f"manifest.sources[{index}] must be a mapping") path_value = _required(entry, "path", f"manifest.sources[{index}]") path = Path(str(path_value)) if not path.is_absolute(): path = base_dir / path source_revision = entry.get("source_revision") refs = entry.get("workload_refs") or {} if not isinstance(refs, Mapping): raise DeclarationError( f"manifest.sources[{index}].workload_refs must be a mapping" ) sources.append( ( path, str(source_revision) if source_revision else None, refs, ) ) result = _resolve_documents(sources, profile=profile, previous=previous) subjects = manifest.get("subjects") or [] if not isinstance(subjects, list): raise DeclarationError("manifest.subjects must be a list") for index, subject in enumerate(subjects): if not isinstance(subject, Mapping): raise DeclarationError(f"manifest.subjects[{index}] must be a mapping") subject_id = str( _required(subject, "subject_id", f"manifest.subjects[{index}]") ) reference = _validate_workload_ref( subject_id, subject.get("workload_ref"), None ) if reference["applicability"] != "not-applicable": raise DeclarationError( f"manifest.subjects[{index}] is only for explicit not-applicable subjects" ) record = _base_record( subject_id, str(subject.get("source") or "manifest.subjects"), str(subject["source_revision"]) if subject.get("source_revision") else None, reference, None, ) record.update( { "workload_id": None, "admission": "not-applicable", "admission_reason": "catalog_declared_not_applicable", "effective_zone": None, "membership_revision_reason": "not_applicable", "guarantees": [ "catalog-declared-not-applicable", "non-inferred-resolution", ], } ) result["records"].append(record) result["records"].sort(key=lambda record: str(record["subject_id"])) subject_ids = [str(record["subject_id"]) for record in result["records"]] duplicates = sorted( subject_id for subject_id in set(subject_ids) if subject_ids.count(subject_id) > 1 ) if duplicates: result["errors"].append( { "source": "manifest", "error": f"duplicate subject ids: {duplicates}", } ) result["ok"] = False result["changes"] = compare_snapshots(result["records"], previous) return result def _load_mapping(path: Path, where: str) -> dict[str, Any]: try: value = yaml.safe_load(path.read_text()) or {} except (OSError, yaml.YAMLError) as exc: raise DeclarationError(f"could not read {where}: {exc}") from exc if not isinstance(value, dict): raise DeclarationError(f"{where} must be a mapping") return value def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("paths", nargs="*", type=Path) parser.add_argument("--manifest", type=Path) parser.add_argument("--source-revision") parser.add_argument("--control-profile", type=Path) parser.add_argument("--previous", type=Path) args = parser.parse_args() if bool(args.manifest) == bool(args.paths): parser.error("provide either declaration paths or --manifest") try: profile = ( _load_mapping(args.control_profile, "control profile") if args.control_profile else None ) previous = ( _load_mapping(args.previous, "previous snapshot") if args.previous else None ) if args.manifest: manifest = _load_mapping(args.manifest, "manifest") result = resolve_manifest( manifest, base_dir=args.manifest.parent, profile=profile, previous=previous, ) else: result = resolve_paths( args.paths, source_revision=args.source_revision, profile=profile, previous=previous, ) except DeclarationError as exc: result = { "ok": False, "standard": STANDARD, "records": [], "changes": {"baseline": "initial", "added": [], "removed": [], "changed": []}, "errors": [{"source": "input", "error": str(exc)}], "profile_errors": [], } print(json.dumps(result, indent=2, sort_keys=True)) return 0 if result["ok"] else 1 if __name__ == "__main__": sys.exit(main())