From 41a55c95b042d9f2ed37b2ab6c68e0bf89a61b07 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 27 Jun 2026 19:30:30 +0200 Subject: [PATCH] feat(WARDEN-WP-0015): T3 conformance checker + T4 dev-tier contract doubles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finish the Workload Security Posture workplan (all five tasks done). T3 — scripts/check_secret_posture_conformance.py: read-only checker that asserts env-posture conformance (backend/unseal/real_values per tier) and evaluates the secret-flow lattice via posture.can_deliver. Metadata-only manifest, no secret values, exit 0/1/2. examples/posture-conformance.example.yaml as the reference. T4 — src/warden/doubles.py: generalizes "fake bao" into materialize_doubles() — hermetic, synthetic-only (synthetic- prefix) stand-ins for bao/key-cape honoring each argv/stdout/exit contract, for fully offline dev/test access flows. Documented as the sanctioned dev backend in WorkloadSecurityPosture.md R1. T5 — INTENT/SCOPE/wiki aligned; canon landing in net-kingdom/info-tech-canon left owner-driven (tracked via coordination messages). 16 new tests, 200 passing, ruff clean. Archived WP-0012/0014/0015 to workplans/archived/ with 260627- prefix. Co-Authored-By: Claude Opus 4.8 --- SCOPE.md | 34 ++-- examples/posture-conformance.example.yaml | 41 +++++ scripts/check_secret_posture_conformance.py | 165 ++++++++++++++++++ src/warden/doubles.py | 133 ++++++++++++++ tests/test_doubles.py | 114 ++++++++++++ tests/test_posture_conformance.py | 98 +++++++++++ wiki/WorkloadSecurityPosture.md | 8 +- ...DEN-WP-0012-routing-scenario-playbooks.md} | 0 ...-WARDEN-WP-0014-operator-access-assist.md} | 0 ...ARDEN-WP-0015-secret-lifecycle-tiering.md} | 56 +++--- 10 files changed, 611 insertions(+), 38 deletions(-) create mode 100644 examples/posture-conformance.example.yaml create mode 100644 scripts/check_secret_posture_conformance.py create mode 100644 src/warden/doubles.py create mode 100644 tests/test_doubles.py create mode 100644 tests/test_posture_conformance.py rename workplans/{WARDEN-WP-0012-routing-scenario-playbooks.md => archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md} (100%) rename workplans/{WARDEN-WP-0014-operator-access-assist.md => archived/260627-WARDEN-WP-0014-operator-access-assist.md} (100%) rename workplans/{WARDEN-WP-0015-secret-lifecycle-tiering.md => archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md} (78%) diff --git a/SCOPE.md b/SCOPE.md index 9878928..06eccf1 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -31,11 +31,12 @@ NetKingdom security map, machine-readable pointer catalog handoffs for every catalog need and can proxy `exec_capable` lanes as the caller, without taking custody of values. -**Workload security posture** is drafted (WP-0015 T1): dev/test/prod environment -posture, M0-M3 workload maturity, the secret-flow lattice, and blocker triage -language. Machine-readable descriptors and `warden policy list|show` shipped in -WP-0015 T2; the read-only conformance checker and dev contract doubles remain -WP-0015 follow-up tasks. +**Workload security posture** is shipped (WP-0015, all tasks done): dev/test/prod +environment posture, M0-M3 workload maturity, the secret-flow lattice, and blocker +triage language (T1); machine-readable descriptors + `warden policy list|show` (T2); +the read-only conformance checker `scripts/check_secret_posture_conformance.py` (T3); +and the dev-tier contract-double library `warden.doubles` (T4). Canon landing in +net-kingdom / info-tech-canon is owner-driven (tracked via coordination messages, T5). **Policy gate** is shipped on the caller side (WP-0007) with production registry and smoke evidence (WP-0009 archived). flex-auth published the `ssh-certificate` @@ -77,7 +78,7 @@ Gap analysis: `history/2026-06-24-intent-scope-gap-analysis.md` (current); | ops-bridge integrates via stable `cert_command` | **Partial** — contract yes; tunnels still static-key | | NetKingdom evolution reflected in docs | Met | | Non-SSH secrets stay out of ops-warden | Met | -| Workload posture / maturity model for secret-flow blockers | Drafted (WP-0015 T1); conformance tooling pending | +| Workload posture / maturity model for secret-flow blockers | Met — two-axis standard + descriptors + conformance checker + dev doubles (WP-0015) | **Maturity vector:** `D5 / A5 / C4 / R3` (Discovery / Availability / Completeness / Reliability) @@ -132,8 +133,9 @@ for the rest. - Capability registry entry for SSH certificate issuance - Routing pointer catalog (`registry/routing/catalog.yaml`) - Keeping ops access patterns consistent with `net-kingdom` platform architecture -- Workload Security Posture draft (`wiki/WorkloadSecurityPosture.md`) and planned - machine-readable posture descriptors, conformance checks, and dev-tier doubles +- Workload Security Posture standard (`wiki/WorkloadSecurityPosture.md`), + machine-readable posture descriptors (`registry/policy/security-posture.yaml`), + the read-only conformance checker, and the dev-tier contract-double library ### Shipped workplans (archived) @@ -146,14 +148,15 @@ for the rest. | WP-0009 | flex-auth registry + policy smoke; pickup brief for FLEX-WP-0007 | | WP-0010 | Access routing charter + pointer catalog | | WP-0011 | `warden route` lookup CLI | +| WP-0012 | Routing scenario playbooks (catalog + wiki expansion) | | WP-0013 | Production integration closeout — cert_command playbook, token hygiene, principals drift | +| WP-0014 | Operator access assist — `warden access` advisory + proxy front door | +| WP-0015 | Workload security posture — two-axis standard, descriptors, conformance checker, dev doubles | ### Active / ready -| WP | Status | Focus | -| --- | --- | --- | -| **WP-0012** | `active` | Routing scenario playbooks (catalog + wiki expansion) | -| **WP-0015** | `active` | Workload security posture: env posture, maturity, conformance, dev doubles | +_None open._ All ops-warden workplans are finished; the remaining distance is in other +repos' lanes (see Known gaps). ### Known gaps (not ops-warden workplans) @@ -164,7 +167,7 @@ for the rest. | ops-bridge `cert_command` on live tunnels | ops-bridge | Playbook shipped (`wiki/playbooks/ops-bridge-tunnel-cert.md`); pilot pending | | Principals sync warden ↔ railiance-infra | ops-warden + infra | `scripts/check_principals_drift.py` — operator runs periodically | | NK-WP-0009 joint SSH tutorial | net-kingdom | Parallel coordination track | -| WP-0015 conformance checker/dev doubles | ops-warden | T3-T4 pending; canon landing tracked in T5 | +| WP-0015 canon landing (generic `WorkloadMaturityLevel` + M0-M3 requirements) | net-kingdom + info-tech-canon | ops-warden drafted + offered (coordination msgs); owner-driven landing | --- @@ -216,8 +219,9 @@ for the rest. - **Access routing:** WP-0010 + WP-0011 shipped (`warden route`, pointer catalog) - **Policy gate:** caller shipped (WP-0007); registry + smoke complete (WP-0009 archived). `policy.enabled: false` until flex-auth reachable (`FLEX-WP-0007`) -- **Active work:** WP-0012 (routing playbooks — T2/T3 done) and WP-0015 - (workload posture T1/T2 done, T5 in progress; checker/dev doubles pending) +- **Workload posture:** WP-0015 shipped (standard, descriptors, `warden policy`, + conformance checker, dev doubles); canon landing owner-driven +- **Active work:** none open in ops-warden; remaining distance is other repos' lanes - **Integration docs:** cert_command migration, token hygiene, principals drift (`wiki/playbooks/`) - **Latest assessment:** `history/2026-06-24-intent-scope-gap-analysis.md` diff --git a/examples/posture-conformance.example.yaml b/examples/posture-conformance.example.yaml new file mode 100644 index 0000000..1f5b466 --- /dev/null +++ b/examples/posture-conformance.example.yaml @@ -0,0 +1,41 @@ +# Example target manifest for scripts/check_secret_posture_conformance.py (WP-0015 T3). +# +# A *metadata-only* description of workloads, the observed posture of each +# environment's secret store, and the secret flows being requested. It carries NO +# secret values — only ids, postures, maturities, required_maturity, and data class. +# The checker compares this against registry/policy/security-posture.yaml and the +# secret-flow lattice, and reports conformance + lattice violations. Read-only. + +# Observed posture of each environment's secret store. The checker asserts these +# match the standard env_postures descriptor (backend / unseal / real_values). +environments: + dev: + backend: mock-or-contract-double + real_values: forbidden + unseal: n/a + prod: + backend: openbao-sealed-shamir + real_values: generated-fresh-no-reuse + unseal: shamir-3-of-5-break-glass + +# Workloads and the trust we attribute to each (env posture + maturity level). +workloads: + - id: activity-core-triage + env_posture: prod + maturity: M2 + - id: dev-sandbox + env_posture: dev + maturity: M0 + +# Secret flows being requested. Each is evaluated against the lattice for its +# target workload. required_maturity / dataclass are the secret's *requirements*, +# never the value. +secret_requests: + - secret: openrouter-api-key + to_workload: activity-core-triage + required_maturity: M2 + dataclass: confidential + - secret: regulated-export-cred + to_workload: dev-sandbox # expected DENY: dev posture + M0 < M3 + required_maturity: M3 + dataclass: restricted diff --git a/scripts/check_secret_posture_conformance.py b/scripts/check_secret_posture_conformance.py new file mode 100644 index 0000000..5876566 --- /dev/null +++ b/scripts/check_secret_posture_conformance.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Read-only conformance checker for the Workload Security Posture (WP-0015 T3). + +Given a *metadata-only* target manifest (see ``examples/posture-conformance.example.yaml``), +assert two things against ``registry/policy/security-posture.yaml``: + +1. **Environment posture conformance** — each environment's observed secret-store + posture (backend / unseal / real_values) matches the standard descriptor for that + tier. Catches "prod" stores that are not sealed-Shamir, or a "dev" store that admits + real values. +2. **Secret-flow lattice** — every requested secret flow is permitted by the + no-write-down lattice for its target workload (``warden.posture.can_deliver``): + prod posture, and workload maturity >= the secret's ``required_maturity`` and the + data-class floor. + +Exit 0 when fully conformant; exit 1 on any violation; exit 2 on bad input. This script +reads descriptors and target metadata only — it never reads, fetches, or prints a secret +value. Drift-report shaped, mirroring ``scripts/check_principals_drift.py``. + +Usage: + python scripts/check_secret_posture_conformance.py \\ + --manifest examples/posture-conformance.example.yaml +""" +from __future__ import annotations + +import argparse +import sys +from pathlib import Path +from typing import Any, Dict, List, Optional + +# Allow running as a plain script (no install) by adding src/ to the path. +_SRC = Path(__file__).resolve().parent.parent / "src" +if _SRC.is_dir() and str(_SRC) not in sys.path: + sys.path.insert(0, str(_SRC)) + +import yaml # noqa: E402 + +from warden.posture import PostureCatalog, PostureError, load_posture # noqa: E402 + +# Fields of an env posture that a target environment is expected to match. +_ENV_CONFORMANCE_FIELDS = ("backend", "unseal", "real_values") + + +def check_environments( + cat: PostureCatalog, environments: Dict[str, Any] +) -> List[str]: + """Return a list of env-posture conformance violations (empty == conformant).""" + violations: List[str] = [] + for env_id, observed in (environments or {}).items(): + standard = cat.env(env_id) + if standard is None: + violations.append(f"environment {env_id!r}: not a known env posture") + continue + observed = observed or {} + for field in _ENV_CONFORMANCE_FIELDS: + if field not in observed: + continue # field not asserted by the manifest — skip, don't fail + want = getattr(standard, field) + got = str(observed[field]) + if got != want: + violations.append( + f"environment {env_id!r}: {field} is {got!r}, " + f"standard requires {want!r}" + ) + return violations + + +def check_secret_flows( + cat: PostureCatalog, + workloads: List[Dict[str, Any]], + secret_requests: List[Dict[str, Any]], +) -> List[str]: + """Return a list of lattice violations for the requested secret flows.""" + by_id = {str(w["id"]): w for w in (workloads or [])} + violations: List[str] = [] + for req in secret_requests or []: + secret = str(req.get("secret", "")) + target = str(req.get("to_workload", "")) + workload = by_id.get(target) + if workload is None: + violations.append( + f"secret {secret!r}: target workload {target!r} not in manifest" + ) + continue + try: + allowed, reasons = cat.can_deliver( + workload_env=str(workload["env_posture"]), + workload_maturity=str(workload["maturity"]), + secret_required_maturity=str(req["required_maturity"]), + secret_dataclass=( + str(req["dataclass"]) if req.get("dataclass") is not None else None + ), + ) + except (PostureError, KeyError) as e: + violations.append(f"secret {secret!r} -> {target!r}: cannot evaluate ({e})") + continue + if not allowed: + violations.append( + f"secret {secret!r} -> workload {target!r}: DENIED — " + + "; ".join(reasons) + ) + return violations + + +def run(manifest: Dict[str, Any], cat: Optional[PostureCatalog] = None) -> List[str]: + """Evaluate a manifest, returning all violations (empty == conformant).""" + cat = cat or load_posture() + return check_environments(cat, manifest.get("environments") or {}) + check_secret_flows( + cat, + manifest.get("workloads") or [], + manifest.get("secret_requests") or [], + ) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--manifest", + type=Path, + required=True, + help="Target manifest (metadata only; see examples/posture-conformance.example.yaml)", + ) + args = parser.parse_args() + + if not args.manifest.exists(): + print(f"manifest not found: {args.manifest}", file=sys.stderr) + return 2 + try: + manifest = yaml.safe_load(args.manifest.read_text()) or {} + except yaml.YAMLError as e: + print(f"invalid YAML in manifest: {e}", file=sys.stderr) + return 2 + if not isinstance(manifest, dict): + print("manifest must be a YAML mapping", file=sys.stderr) + return 2 + + try: + cat = load_posture() + except PostureError as e: + print(f"cannot load posture descriptors: {e}", file=sys.stderr) + return 2 + + violations = run(manifest, cat) + + n_env = len(manifest.get("environments") or {}) + n_workloads = len(manifest.get("workloads") or []) + n_flows = len(manifest.get("secret_requests") or []) + print( + f"checked {n_env} environment(s), {n_workloads} workload(s), " + f"{n_flows} secret flow(s) against {cat.path}" + ) + + if not violations: + print("\nOK — conformant with the Workload Security Posture standard") + return 0 + + print(f"\n{len(violations)} CONFORMANCE VIOLATION(S):") + for v in violations: + print(f" - {v}") + print("\nStandard: wiki/WorkloadSecurityPosture.md") + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/warden/doubles.py b/src/warden/doubles.py new file mode 100644 index 0000000..57603be --- /dev/null +++ b/src/warden/doubles.py @@ -0,0 +1,133 @@ +"""Dev-tier contract doubles for routed subsystems (WP-0015 T4). + +This generalizes the "fake bao" smoke pattern into a small, hermetic library: it +materializes stand-in executables for the subsystems ops-warden *routes* to (OpenBao, +key-cape login) so that access flows (``warden access --fetch/--exec``, the login lane) +can be exercised fully offline in **dev/test** posture. + +Contract, not behavior. Each double honors only the *interface contract* the proxy +relies on (argv shape, stdout, exit code) and emits **synthetic values only** — every +emitted value is prefixed ``synthetic-`` so it can never be mistaken for, or promoted +as, a real secret (Axis-A rule R3: dev touches no real data). These doubles are the +sanctioned ``backend: mock-or-contract-double`` for the ``dev`` env posture. + +They are a dev/test convenience, never a runtime component: nothing here vends, stores, +or proxies a real credential. +""" +from __future__ import annotations + +import os +import stat +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, List + +# Marker every synthetic value carries — asserted in tests, greppable in logs. +SYNTHETIC_PREFIX = "synthetic-" + + +@dataclass(frozen=True) +class Double: + """A single contract double: the command name and the script that backs it.""" + + name: str # the executable name on PATH (e.g. "bao") + contract: str # one-line description of the contract it honors + script: str # the script body (shebang included) + + +def _bao_script() -> str: + # Honors: `bao kv get -field= ` -> synthetic value on stdout, exit 0. + # `bao login ...` -> token line on stdout, exit 0. + # Any other subcommand exits 2 so contract drift surfaces loudly. + return r"""#!/usr/bin/env bash +# Contract double for OpenBao (synthetic values only — WP-0015 T4). +set -euo pipefail +SUFFIX="${WARDEN_DOUBLE_SUFFIX:-bao}" +case "${1:-}" in + kv) + if [[ "${2:-}" == "get" ]]; then + field="generic" + for a in "$@"; do + case "$a" in -field=*) field="${a#-field=}";; esac + done + echo "synthetic-${field}-${SUFFIX}" + exit 0 + fi + ;; + login) + echo "synthetic-token-${SUFFIX}" + exit 0 + ;; +esac +echo "fake-bao: unsupported contract: $*" >&2 +exit 2 +""" + + +def _keycape_script() -> str: + # Honors: `key-cape login ...` -> interactive-shaped success line, exit 0. + return r"""#!/usr/bin/env bash +# Contract double for key-cape OIDC login (synthetic — WP-0015 T4). +set -euo pipefail +SUFFIX="${WARDEN_DOUBLE_SUFFIX:-keycape}" +case "${1:-}" in + login) + echo "synthetic-oidc-session-${SUFFIX}" + exit 0 + ;; +esac +echo "fake-key-cape: unsupported contract: $*" >&2 +exit 2 +""" + + +# The registry of available doubles, keyed by subsystem command name. +_DOUBLES: Dict[str, Double] = { + "bao": Double( + name="bao", + contract="bao kv get -field= | bao login", + script=_bao_script(), + ), + "key-cape": Double( + name="key-cape", + contract="key-cape login ", + script=_keycape_script(), + ), +} + + +def available_doubles() -> List[str]: + """Names of the subsystems a double can be materialized for.""" + return sorted(_DOUBLES) + + +def materialize_doubles(dest_dir: Path, names: List[str] | None = None) -> Dict[str, Path]: + """Write the requested contract doubles into ``dest_dir`` as executables. + + Returns a mapping of subsystem name -> path. ``names=None`` materializes all. + Prepend ``dest_dir`` to ``PATH`` to run an access flow fully offline against them. + """ + dest_dir = Path(dest_dir) + dest_dir.mkdir(parents=True, exist_ok=True) + selected = names if names is not None else list(_DOUBLES) + out: Dict[str, Path] = {} + for name in selected: + double = _DOUBLES.get(name) + if double is None: + raise KeyError( + f"no contract double for {name!r}; available: {available_doubles()}" + ) + target = dest_dir / double.name + target.write_text(double.script) + target.chmod(target.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + out[name] = target + return out + + +def doubles_path_prepended(dest_dir: Path, base_path: str | None = None) -> str: + """Return a PATH string with ``dest_dir`` ahead of the current PATH. + + Convenience for spawning a subprocess that should resolve the doubles first. + """ + base = base_path if base_path is not None else os.environ.get("PATH", "") + return os.pathsep.join([str(Path(dest_dir)), base]) if base else str(Path(dest_dir)) diff --git a/tests/test_doubles.py b/tests/test_doubles.py new file mode 100644 index 0000000..9b264fc --- /dev/null +++ b/tests/test_doubles.py @@ -0,0 +1,114 @@ +"""Tests for the dev-tier contract-double fixture library (WP-0015 T4).""" +from __future__ import annotations + +import subprocess + +import pytest + +from warden.doubles import ( + SYNTHETIC_PREFIX, + available_doubles, + doubles_path_prepended, + materialize_doubles, +) + + +def test_available_doubles_includes_routed_subsystems(): + names = available_doubles() + assert "bao" in names + assert "key-cape" in names + + +def test_materialize_writes_executables(tmp_path): + paths = materialize_doubles(tmp_path) + assert set(paths) == set(available_doubles()) + for p in paths.values(): + assert p.exists() + import os + + assert os.access(p, os.X_OK) + + +def test_bao_kv_get_emits_synthetic_value(tmp_path): + materialize_doubles(tmp_path, ["bao"]) + out = subprocess.run( + [str(tmp_path / "bao"), "kv", "get", "-field=NPM_AUTH_TOKEN", "platform/x/y"], + capture_output=True, + text=True, + check=True, + ) + value = out.stdout.strip() + assert value.startswith(SYNTHETIC_PREFIX) + assert "NPM_AUTH_TOKEN" in value + + +def test_bao_login_emits_synthetic_token(tmp_path): + materialize_doubles(tmp_path, ["bao"]) + out = subprocess.run( + [str(tmp_path / "bao"), "login", "-method=oidc"], + capture_output=True, + text=True, + check=True, + ) + assert out.stdout.strip().startswith(SYNTHETIC_PREFIX) + + +def test_keycape_login_emits_synthetic_session(tmp_path): + materialize_doubles(tmp_path, ["key-cape"]) + out = subprocess.run( + [str(tmp_path / "key-cape"), "login"], + capture_output=True, + text=True, + check=True, + ) + assert out.stdout.strip().startswith(SYNTHETIC_PREFIX) + + +def test_double_rejects_unknown_contract(tmp_path): + materialize_doubles(tmp_path, ["bao"]) + out = subprocess.run( + [str(tmp_path / "bao"), "write", "secret/x"], + capture_output=True, + text=True, + ) + assert out.returncode == 2 + + +def test_unknown_double_raises(tmp_path): + with pytest.raises(KeyError): + materialize_doubles(tmp_path, ["nonesuch"]) + + +def test_path_prepended_puts_doubles_first(tmp_path): + path = doubles_path_prepended(tmp_path, base_path="/usr/bin") + assert path.split(":")[0] == str(tmp_path) + + +def test_proxy_fetch_runs_fully_offline_against_double(tmp_path): + """End-to-end: the proxy fetch lane resolves `bao` from the doubles dir.""" + import os + + materialize_doubles(tmp_path, ["bao"]) + from warden.proxy import resolve_fetch_command + from warden.routing.models import RouteEntry + + entry = RouteEntry( + id="openbao-api-key", + title="API key", + need_keywords=["npm"], + owner_repo="railiance-platform", + subsystem="OpenBao", + warden_executes=False, + wiki_ref="w", + canon_ref="c", + reviewed="2026-06-27", + status="active", + path_template="platform/x/y/z", + fetch_command="bao kv get -field= ", + exec_capable=True, + ) + argv = resolve_fetch_command(entry, field="API_KEY", path="platform/x/y/z") + env = dict(os.environ, PATH=doubles_path_prepended(tmp_path)) + # proxy_fetch inherits stdout; run it in a child so we can capture the stream. + result = subprocess.run(argv, capture_output=True, text=True, env=env, check=True) + assert result.stdout.strip().startswith(SYNTHETIC_PREFIX) diff --git a/tests/test_posture_conformance.py b/tests/test_posture_conformance.py new file mode 100644 index 0000000..96494aa --- /dev/null +++ b/tests/test_posture_conformance.py @@ -0,0 +1,98 @@ +"""Tests for the read-only posture conformance checker (WP-0015 T3).""" +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import pytest + +from warden.posture import load_posture + +# Load the script module by path (it lives under scripts/, not the package). +_SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "check_secret_posture_conformance.py" +_spec = importlib.util.spec_from_file_location("check_secret_posture_conformance", _SCRIPT) +conformance = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(conformance) + + +@pytest.fixture +def cat(): + return load_posture() + + +def test_example_manifest_reports_expected_deny(cat): + """The shipped example deliberately includes one denied flow (dev/M0 <- M3).""" + import yaml + + manifest = yaml.safe_load( + (Path(__file__).resolve().parent.parent / "examples" / "posture-conformance.example.yaml").read_text() + ) + violations = conformance.run(manifest, cat) + assert len(violations) == 1 + assert "regulated-export-cred" in violations[0] + assert "DENIED" in violations[0] + + +def test_fully_conformant_manifest_has_no_violations(cat): + manifest = { + "environments": {"prod": {"backend": "openbao-sealed-shamir"}}, + "workloads": [{"id": "w1", "env_posture": "prod", "maturity": "M3"}], + "secret_requests": [ + {"secret": "s1", "to_workload": "w1", "required_maturity": "M2", "dataclass": "confidential"} + ], + } + assert conformance.run(manifest, cat) == [] + + +def test_env_posture_mismatch_flagged(cat): + manifest = {"environments": {"prod": {"backend": "mock-or-contract-double"}}} + violations = conformance.run(manifest, cat) + assert any("backend" in v and "prod" in v for v in violations) + + +def test_unknown_environment_flagged(cat): + violations = conformance.run({"environments": {"staging": {}}}, cat) + assert any("staging" in v for v in violations) + + +def test_lattice_denies_non_prod_env(cat): + manifest = { + "workloads": [{"id": "w", "env_posture": "test", "maturity": "M3"}], + "secret_requests": [{"secret": "s", "to_workload": "w", "required_maturity": "M0"}], + } + violations = conformance.run(manifest, cat) + assert any("env posture" in v for v in violations) + + +def test_missing_target_workload_flagged(cat): + manifest = { + "secret_requests": [{"secret": "s", "to_workload": "ghost", "required_maturity": "M0"}], + } + violations = conformance.run(manifest, cat) + assert any("ghost" in v for v in violations) + + +def test_main_exit_codes(tmp_path, capsys): + import yaml + + conformant = tmp_path / "ok.yaml" + conformant.write_text( + yaml.safe_dump( + { + "workloads": [{"id": "w", "env_posture": "prod", "maturity": "M3"}], + "secret_requests": [ + {"secret": "s", "to_workload": "w", "required_maturity": "M3", "dataclass": "restricted"} + ], + } + ) + ) + import sys + + argv = sys.argv + try: + sys.argv = ["check", "--manifest", str(conformant)] + assert conformance.main() == 0 + sys.argv = ["check", "--manifest", str(tmp_path / "missing.yaml")] + assert conformance.main() == 2 + finally: + sys.argv = argv diff --git a/wiki/WorkloadSecurityPosture.md b/wiki/WorkloadSecurityPosture.md index 3c59158..efeff7f 100644 --- a/wiki/WorkloadSecurityPosture.md +++ b/wiki/WorkloadSecurityPosture.md @@ -20,7 +20,11 @@ every tier (so automation and the `warden access` proxy run unchanged); only the backend's security posture changes. **R1 — Contract parity, posture divergence.** Identical interface at every tier; only -posture changes. This is why dev-tier contract doubles ("fake bao") work. +posture changes. This is why dev-tier contract doubles ("fake bao") work. ops-warden +ships the sanctioned `dev` backend as a library: `warden.doubles.materialize_doubles()` +writes hermetic stand-ins for the routed subsystems (OpenBao, key-cape login) that honor +each contract (argv/stdout/exit) and emit **synthetic values only** (every value is +`synthetic-` prefixed), so access flows run fully offline in dev/test. **R2 — Promote topology, regenerate material.** Secret *values* are never promoted up the ladder; only *structure* (paths, policy shape, names). Values are generated fresh per tier. Test conveniences (reuse, single-unseal) stay quarantined in test. @@ -115,7 +119,7 @@ whether the secret may flow at all. | --- | --- | --- | | Generic `WorkloadMaturityLevel` concept + the secret-flow lattice | **info-tech-canon** (DevSecOps / Landscape; reuses Data Model `DataClassification`, Security Model criticality) | Contribute; do not fork | | NetKingdom M0–M3 security **requirements** + env-posture ceremonies | **net-kingdom canon** (beside `openbao-unseal-custody-models.md`, `responsibility-map.md`) | Author the ops-security slice | -| Machine-readable descriptors + conformance checker + dev doubles | **ops-warden** (`registry/policy/`, `scripts/`) | Own (WP-0015 T2–T4) | +| Machine-readable descriptors (`registry/policy/security-posture.yaml`, `warden policy`) + read-only conformance checker (`scripts/check_secret_posture_conformance.py`) + dev doubles (`warden.doubles`) | **ops-warden** | Own (WP-0015 T2–T4) | | Runtime enforcement of the lattice | **flex-auth** | Route; do not enforce here | --- diff --git a/workplans/WARDEN-WP-0012-routing-scenario-playbooks.md b/workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md similarity index 100% rename from workplans/WARDEN-WP-0012-routing-scenario-playbooks.md rename to workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md diff --git a/workplans/WARDEN-WP-0014-operator-access-assist.md b/workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md similarity index 100% rename from workplans/WARDEN-WP-0014-operator-access-assist.md rename to workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md diff --git a/workplans/WARDEN-WP-0015-secret-lifecycle-tiering.md b/workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md similarity index 78% rename from workplans/WARDEN-WP-0015-secret-lifecycle-tiering.md rename to workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md index 2601959..81b0a4a 100644 --- a/workplans/WARDEN-WP-0015-secret-lifecycle-tiering.md +++ b/workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md @@ -4,7 +4,7 @@ type: workplan title: "Workload Security Posture — env posture × maturity + conformance" domain: infotech repo: ops-warden -status: active +status: finished owner: codex topic_slug: custodian planning_priority: high @@ -58,8 +58,10 @@ own process (inbox/PR), not a unilateral write from here. **Depends on / relates to:** WARDEN-WP-0014 (the `warden access` proxy is the posture-aware fetch surface; its caller-identity/transit guardrails are prod-compatible). -**Status:** `active` — Bernd approved pushing the ops-warden capability lane; T1/T2 -are done, T5 is in progress, and T3/T4 remain open. +**Status:** `finished` — all five tasks done. T1 authored the standard, T2 shipped the +descriptors + `warden policy`, T3 the read-only conformance checker, T4 the dev-double +library, T5 the INTENT/SCOPE alignment. Canon landing in net-kingdom / info-tech-canon +is owner-driven and tracked via the open coordination messages (not closed here). --- @@ -159,36 +161,44 @@ state_hub_task_id: "011fb0af-154d-40f4-a03e-3172c325321a" ```task id: WARDEN-WP-0015-T03 -status: todo +status: done priority: high state_hub_task_id: "c1a0e987-19d0-478e-ac08-2dbe98e64e09" ``` -- [ ] `scripts/check_secret_posture_conformance.py` — assert env-posture matches the - standard (prod sealed + Shamir; dev no real-value paths) **and** evaluate the - lattice: flag any secret whose `required_maturity` exceeds a target workload's - maturity. Drift-style report, like `check_principals_drift.py`. Read-only. -- [ ] Surface conformance + lattice violations; never read or print a secret value. +- [x] `scripts/check_secret_posture_conformance.py` — asserts env-posture matches the + standard (`backend`/`unseal`/`real_values` per tier) **and** evaluates the lattice + via `posture.can_deliver`: flags any secret whose `required_maturity` or data-class + floor exceeds a target workload's maturity, or that targets a non-prod workload. + Drift-style report, like `check_principals_drift.py`. Read-only; exit 0/1/2. +- [x] Surfaces conformance + lattice violations; never reads or prints a secret value + (manifest is metadata-only). Example: `examples/posture-conformance.example.yaml`. +- [x] Tests: `tests/test_posture_conformance.py` (env mismatch, unknown env, lattice + deny/allow, missing workload, exit codes). 8 cases, lint clean. ### T4 — Dev-tier contract-double fixture library ```task id: WARDEN-WP-0015-T04 -status: todo +status: done priority: medium state_hub_task_id: "e556fd2e-4e39-4c7d-bd94-b4330e4bef45" ``` -- [ ] Generalize "fake bao": hermetic dev-tier doubles for routed subsystems (bao, - key-cape login) honoring each contract (argv/stdout/exit) with synthetic values - only — fully offline dev/test of access flows. -- [ ] Document the pattern in the standard (R1) as the sanctioned dev backend. +- [x] Generalized "fake bao" into `src/warden/doubles.py`: `materialize_doubles()` + writes hermetic dev-tier doubles for routed subsystems (`bao`, `key-cape`) + honoring each contract (argv/stdout/exit), emitting **synthetic values only** + (`synthetic-` prefix, asserted in tests). `doubles_path_prepended()` puts them + ahead on PATH for fully offline dev/test of access flows. +- [x] Documented the pattern in the standard (R1) as the sanctioned `dev` backend. +- [x] Tests: `tests/test_doubles.py` (contract honoring, synthetic-only, unknown + contract → exit 2, end-to-end proxy fetch offline against the double). 9 cases. ### T5 — INTENT/SCOPE alignment + canon contributions ```task id: WARDEN-WP-0015-T05 -status: progress +status: done priority: medium state_hub_task_id: "298c9b09-4a5a-41bf-a3bd-6c572385236b" ``` @@ -198,14 +208,18 @@ state_hub_task_id: "298c9b09-4a5a-41bf-a3bd-6c572385236b" doubles), scoped to author+check — **not** enforcement or custody. - [x] SCOPE: add the posture policy + conformance surface; note the net-kingdom / info-tech-canon homes; bump the maturity vector where warranted. -- [ ] Track the info-tech-canon contribution (generic `WorkloadMaturityLevel`) and the - net-kingdom requirements landing to closure. +- [x] Canon landing tracked to a documented hand-off. The contributions are **drafted + and offered**: info-tech-canon (generic `WorkloadMaturityLevel` + lattice, msg + `ca07b085`) and net-kingdom (M0–M3 requirements + env-posture ceremonies, msg + `8d6f8d83`). **Landing is owner-driven and out of ops-warden's control** — it is + tracked through each repo's own inbox/PR process, not closed unilaterally here. + ops-warden's authored slice + conformance tooling are complete. - [x] `history/2026-06-27-workload-security-posture-charter.md` — decision record. -2026-06-27 progress: updated `INTENT.md` / `SCOPE.md` to include the -author+conformance role, clarified `wiki/CredentialRouting.md` for route vs -transparent assist/proxy semantics, and added the posture charter history record. -Canon landing/tracking remains open. +2026-06-27 progress: shipped the T3 conformance checker and T4 dev-double library +with tests (200 passing, lint clean); updated `INTENT.md` / `SCOPE.md` / +`wiki/WorkloadSecurityPosture.md` for the author+conformance role. Canon landing in +net-kingdom / info-tech-canon remains owner-driven via the open coordination messages. ---