Show the OpenBao identity warden access evaluates

reuse-surface-hub-write-token does not select a caller. The data plane
uses the ambient token, which can be the coding-agent role denied by
agent-high-risk-boundary while an attended platform-admin lookup still
shows read. Advisory and proxy output now report that entity, role, and
policies without the token. CCR-2026-0005 still has no OIDC binding for
workload-kv-read-reuse-surface-runtime.

Assistant: grok
Assistant-Session: 01a13b2d-0bc1-7a91-ab1d-325bcfb09bf0
This commit is contained in:
tegwick 2026-10-14 18:21:02 +02:00
parent 6e1430073c
commit 8dbf62515a
6 changed files with 367 additions and 1 deletions

View file

@ -1172,6 +1172,41 @@ def route_find(
# warden access — operator front door (advisory; proxy lands in T3)
# ---------------------------------------------------------------------------
def _openbao_caller_view(entry) -> Optional[dict]:
"""Sanitized data-plane identity. Absent for lanes that do not use it."""
if entry.warden_executes or entry.lane == "login" or not entry.exec_capable:
return None
from warden.openbao_caller import caller_boundary_note, describe_ambient_openbao_caller
described = describe_ambient_openbao_caller()
note = caller_boundary_note(described, high_risk=bool(entry.is_high_risk))
if note:
described = {**described, "boundary_note": note}
return described
def _print_openbao_caller(entry) -> None:
described = _openbao_caller_view(entry)
if described is None:
return
if described.get("error"):
console.print(
f" openbao : [yellow]{described['source']}[/yellow] — {described['error']}"
)
return
policies = ", ".join(described.get("policies") or []) or "(none)"
role = described.get("oidc_role") or "(none)"
console.print(
f" openbao : {described['source']} entity {described.get('entity_id') or '(none)'}"
)
console.print(f" role : {role} policies: {policies}")
if described.get("boundary_note"):
console.print(f" caller : [yellow]{described['boundary_note']}[/yellow]")
console.print(
" gate id : [dim]flex-auth uses policy.caller_auth, not this OpenBao entity[/dim]"
)
def _access_json(entry, expanded, gate: str, domain: Optional[str]) -> dict:
"""Stable, secret-free JSON shape for agentic operators. WP-0014 T2."""
payload = _entry_summary(entry)
@ -1184,6 +1219,9 @@ def _access_json(entry, expanded, gate: str, domain: Optional[str]) -> dict:
"policy_ref": expanded.policy_ref,
"exec_capable": expanded.exec_capable,
}
caller = _openbao_caller_view(entry)
if caller is not None:
payload["openbao_caller"] = caller
if entry.warden_executes:
payload["next_action"] = "ops-warden issues this directly — see cert_command"
payload["cert_command"] = entry.cert_command
@ -1372,6 +1410,25 @@ def _access_proxy(
)
raise typer.Exit(6)
# After the refusals above, so a denied gate does not look like a fetch.
# The lookup uses the OpenBao CLI and must not run on those paths.
if not is_login:
caller = _openbao_caller_view(entry)
if caller is not None:
if caller.get("error"):
err.print(
f"[yellow]OpenBao caller:[/yellow] {caller['source']} — {caller['error']}"
)
else:
err.print(
"[dim]OpenBao caller[/dim] "
f"{caller['source']} entity {caller.get('entity_id') or '(none)'} "
f"role {caller.get('oidc_role') or '(none)'} "
f"policies {', '.join(caller.get('policies') or []) or '(none)'}"
)
if caller.get("boundary_note"):
err.print(f"[yellow]{caller['boundary_note']}[/yellow]")
action = "login" if is_login else ("exec" if do_exec else "fetch")
err.print(
f"[dim]proxy {action}: {entry.id} → {entry.owner_repo} "
@ -1566,6 +1623,7 @@ def access(
if expanded.auth_method:
console.print(f" auth : {expanded.auth_method}")
_print_openbao_caller(entry)
if expanded.path_template:
console.print(f" path : {expanded.path_template}")
if expanded.fetch_command and entry.lane != "login":

View file

@ -0,0 +1,137 @@
"""Sanitized view of the OpenBao identity a proxied fetch will use.
``warden access`` does not select a caller. The data-plane command inherits the
process environment, and the OpenBao CLI then resolves a token in this order:
``BAO_TOKEN``, ``VAULT_TOKEN``, then ``~/.vault-token``. That identity is
independent of the flex-auth caller (``policy.caller_auth``).
The lookup response contains the token itself. This module keeps only the
fields an operator needs to see which principal will be evaluated.
"""
from __future__ import annotations
import json
import os
import subprocess
from pathlib import Path
from typing import Mapping, Optional, Sequence
_TOKEN_KEYS = ("BAO_TOKEN", "VAULT_TOKEN")
_DROPPED = {"id", "accessor", "client_token", "token", "wrap_info"}
def ambient_token_source(env: Optional[Mapping[str, str]] = None) -> str:
"""Which credential the OpenBao CLI will use. Never reads the value."""
current = os.environ if env is None else env
if current.get("BAO_TOKEN", "").strip():
return "BAO_TOKEN"
if current.get("VAULT_TOKEN", "").strip():
return "VAULT_TOKEN"
if (Path.home() / ".vault-token").is_file():
return "vault-token-file"
return "none"
def describe_ambient_openbao_caller(
env: Optional[Mapping[str, str]] = None,
*,
command: Sequence[str] = ("bao", "token", "lookup", "-format=json"),
timeout: float = 10.0,
) -> dict:
"""Return entity, policies, and auth path for the ambient token.
``id`` and ``accessor`` from the lookup payload are dropped. On failure the
result carries ``error`` and no raw CLI output.
"""
current = os.environ if env is None else env
source = ambient_token_source(current)
described = {
"source": source,
"entity_id": None,
"display_name": None,
"auth_path": None,
"policies": [],
"oidc_role": None,
"ttl": None,
"error": None,
}
if source == "none":
described["error"] = "no ambient OpenBao token"
return described
try:
completed = subprocess.run( # noqa: S603 — fixed argv, caller environment
list(command),
capture_output=True,
text=True,
timeout=timeout,
env=dict(current),
check=False,
)
except FileNotFoundError:
described["error"] = "bao client not found"
return described
except subprocess.TimeoutExpired:
described["error"] = "token lookup timed out"
return described
except OSError:
described["error"] = "token lookup could not start"
return described
if completed.returncode != 0:
described["error"] = "token lookup failed"
return described
try:
payload = json.loads(completed.stdout or "")
except json.JSONDecodeError:
described["error"] = "token lookup returned non-JSON"
return described
data = payload.get("data") if isinstance(payload, dict) else None
if not isinstance(data, dict):
described["error"] = "token lookup returned no data"
return described
meta = data.get("meta") if isinstance(data.get("meta"), dict) else {}
role = meta.get("role")
policies = data.get("policies")
described.update(
{
"entity_id": _text(data.get("entity_id")),
"display_name": _text(data.get("display_name")),
"auth_path": _text(data.get("path")),
"policies": [str(item) for item in policies] if isinstance(policies, list) else [],
"oidc_role": role if isinstance(role, str) and role else None,
"ttl": data.get("ttl") if isinstance(data.get("ttl"), int) else None,
"error": None,
}
)
# Guard the contract even if a future field is copied by mistake.
for key in _DROPPED:
described.pop(key, None)
return described
def caller_boundary_note(described: Mapping[str, object], *, high_risk: bool) -> Optional[str]:
"""One line when this token cannot be the lane's scoped reader."""
policies = described.get("policies")
names = set(policies) if isinstance(policies, list) else set()
if high_risk and "agent-high-risk-boundary" in names:
return (
"this token carries agent-high-risk-boundary, which denies data read "
"on high-risk lanes; a platform-admin capabilities check is a different caller"
)
if "platform-admin" in names:
return (
"this token is the broad platform-admin role; do not attach the lane's "
"scoped read policy to this entity"
)
return None
def _text(value: object) -> Optional[str]:
if isinstance(value, str) and value.strip():
return value
return None