Record the passing hub-writer receipts.
Member and non-member verification both passed. The verifier accepts s. tokens and calls sys/capabilities with a token that is allowed to. Assistant: grok Assistant-Session: 01a13b2d-5f6d-7a32-b2c9-384625c55ca6
This commit is contained in:
parent
05882b189f
commit
97d1fed949
7 changed files with 116 additions and 45 deletions
|
|
@ -6,11 +6,12 @@
|
|||
|
||||
Logs in on auth/netkingdom role reuse-surface-hub-writer-workload-kv-read with
|
||||
``bao login -token-only``, so the operator's token helper is left alone. The
|
||||
lane token stays in memory. Capability queries run as the operator token
|
||||
already present in the environment or token helper, because the workload
|
||||
policy has no sys/capabilities-self grant. The receipt records policies,
|
||||
entity id, path capabilities, and pass/fail. It does not record the token or
|
||||
any KV value.
|
||||
lane token stays in memory. Capability queries use the first local token that
|
||||
may call ``sys/capabilities`` (``BAO_TOKEN`` or ``VAULT_TOKEN``, then
|
||||
``~/.local/openbao/platform-admin.token``, then ``~/.vault-token``), because
|
||||
the workload policy has no sys/capabilities-self grant. The receipt records
|
||||
policies, entity id, path capabilities, and pass/fail. It does not record the
|
||||
token or any KV value.
|
||||
|
||||
Sign in with a private browser window. A reused platform-admin session is the
|
||||
wrong principal for ``member``.
|
||||
|
|
@ -43,7 +44,8 @@ from ops_mason.reuse_surface_hub_writer import (
|
|||
)
|
||||
|
||||
BAO_ADDR = os.environ.get("BAO_ADDR", "http://127.0.0.1:18200")
|
||||
TOKEN_RE = re.compile(r"\b(?:hvs|hvb|hvr)\.[A-Za-z0-9_-]{8,}\b")
|
||||
# This OpenBao issues s. service tokens. hvs/hvb/hvr are the newer Vault shapes.
|
||||
TOKEN_RE = re.compile(r"\b(?:hvs|hvb|hvr|s)\.[A-Za-z0-9_-]{8,}\b")
|
||||
PATHS = [DATA, METADATA, PARENT_DATA, PARENT_METADATA, SIBLING]
|
||||
|
||||
|
||||
|
|
@ -58,14 +60,45 @@ def receipts_dir() -> Path:
|
|||
return path
|
||||
|
||||
|
||||
def operator_token() -> str:
|
||||
def token_candidates() -> list[tuple[str, str]]:
|
||||
found: list[tuple[str, str]] = []
|
||||
for name in ("BAO_TOKEN", "VAULT_TOKEN"):
|
||||
value = os.environ.get(name, "").strip()
|
||||
if value:
|
||||
return value
|
||||
helper = Path(os.environ.get("HOME", str(Path.home()))) / ".vault-token"
|
||||
if helper.is_file():
|
||||
return helper.read_text(encoding="utf-8").strip()
|
||||
found.append((name, value))
|
||||
home = Path(os.environ.get("HOME", str(Path.home())))
|
||||
for label, path in (
|
||||
("~/.local/openbao/platform-admin.token", home / ".local/openbao/platform-admin.token"),
|
||||
("~/.vault-token", home / ".vault-token"),
|
||||
):
|
||||
if path.is_file():
|
||||
value = path.read_text(encoding="utf-8").strip()
|
||||
if value:
|
||||
found.append((label, value))
|
||||
return found
|
||||
|
||||
|
||||
def write_capabilities(caller: str, subject: str, paths: list[str]) -> subprocess.CompletedProcess[str]:
|
||||
body = json.dumps({"token": subject, "paths": paths})
|
||||
tmp = tempfile.NamedTemporaryFile("w", prefix="rs-caps-", delete=False)
|
||||
try:
|
||||
os.fchmod(tmp.fileno(), stat.S_IRUSR | stat.S_IWUSR)
|
||||
tmp.write(body)
|
||||
tmp.close()
|
||||
return run_bao(["write", "-format=json", "sys/capabilities", f"@{tmp.name}"], caller)
|
||||
finally:
|
||||
try:
|
||||
os.remove(tmp.name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def operator_token() -> str:
|
||||
"""Return a token allowed to call sys/capabilities, or an empty string."""
|
||||
for _label, token in token_candidates():
|
||||
probe = write_capabilities(token, token, ["auth/token/lookup-self"])
|
||||
if probe.returncode == 0:
|
||||
return token
|
||||
return ""
|
||||
|
||||
|
||||
|
|
@ -90,8 +123,8 @@ def login() -> str:
|
|||
token="",
|
||||
)
|
||||
token = (proc.stdout or "").strip()
|
||||
if proc.returncode != 0 or not token or TOKEN_RE.fullmatch(token) is None:
|
||||
sys.stderr.write(redact(proc.stderr or "login refused\n"))
|
||||
if proc.returncode != 0 or TOKEN_RE.fullmatch(token) is None:
|
||||
sys.stderr.write(redact(proc.stderr or "login output was not a token\n"))
|
||||
return ""
|
||||
return token
|
||||
|
||||
|
|
@ -107,21 +140,10 @@ def lookup(token: str) -> dict:
|
|||
def capabilities(operator: str, lane: str) -> dict[str, list[str]]:
|
||||
if not operator:
|
||||
raise RuntimeError(
|
||||
"no operator token for sys/capabilities "
|
||||
"(BAO_TOKEN, VAULT_TOKEN, or ~/.vault-token)"
|
||||
"no token can call sys/capabilities "
|
||||
"(BAO_TOKEN, VAULT_TOKEN, ~/.local/openbao/platform-admin.token, or ~/.vault-token)"
|
||||
)
|
||||
body = json.dumps({"token": lane, "paths": PATHS})
|
||||
tmp = tempfile.NamedTemporaryFile("w", prefix="rs-caps-", delete=False)
|
||||
try:
|
||||
os.fchmod(tmp.fileno(), stat.S_IRUSR | stat.S_IWUSR)
|
||||
tmp.write(body)
|
||||
tmp.close()
|
||||
proc = run_bao(["write", "-format=json", "sys/capabilities", f"@{tmp.name}"], operator)
|
||||
finally:
|
||||
try:
|
||||
os.remove(tmp.name)
|
||||
except OSError:
|
||||
pass
|
||||
proc = write_capabilities(operator, lane, PATHS)
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(redact(proc.stderr or "sys/capabilities failed"))
|
||||
payload = json.loads(proc.stdout)
|
||||
|
|
@ -151,11 +173,17 @@ def main(argv: list[str]) -> int:
|
|||
if mode not in {"member", "non-member"}:
|
||||
sys.stderr.write("usage: verify-reuse-surface-hub-writer.py member|non-member\n")
|
||||
return 2
|
||||
operator = operator_token()
|
||||
if not operator:
|
||||
sys.stderr.write(
|
||||
"no token can call sys/capabilities. Checked BAO_TOKEN, VAULT_TOKEN, "
|
||||
"~/.local/openbao/platform-admin.token, and ~/.vault-token.\n"
|
||||
)
|
||||
return 1
|
||||
sys.stderr.write(
|
||||
f"Sign in as the {mode} identity in a private browser window.\n"
|
||||
"A platform-admin session is the wrong principal for member.\n"
|
||||
)
|
||||
operator = operator_token()
|
||||
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
lane = login()
|
||||
report: dict = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue