CUST-WP-0075: tenant-engine sender seed helper; re-qualify RPF-WP-0035-T02/T08
- scripts/tenant_engine_audit_core_sender_seed.py: silent attended row-7 seed for MASON-WP-0007-T05 (wrap-migrate technique, CAS 0, never overwrites, metadata-only --dry-run, documented exit codes), with offline fake-bao tests. - Lane review doc with the exact founder sequence and the read-only ESO token-create can-i check for MASON-WP-0007-T06. - RPF-WP-0035-T02: SECRETS-WP-0007-T04 is done; real blocker is live KeyCape registration and client custody of secrets-engine-openbao (blocked_on key-cape). Design stays proposed. - RPF-WP-0035-T08: receiver rollout with evidence_kind already accepted 2026-09-11; moved the misplaced D1 correction note here from T02. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 3775@bnt-lap001 Assistant-Session: cfacb6a5-ad5b-4f79-ac67-053e3b0d34a3
This commit is contained in:
parent
401765f184
commit
e7eb9db57e
6 changed files with 512 additions and 3 deletions
216
scripts/tenant_engine_audit_core_sender_seed.py
Executable file
216
scripts/tenant_engine_audit_core_sender_seed.py
Executable file
|
|
@ -0,0 +1,216 @@
|
|||
#!/usr/bin/env python3
|
||||
"""MASON-WP-0007-T05 row 7: attended, silent seed of tenant-engine's sender token.
|
||||
|
||||
Copies the one token of sender ``tenant-engine`` from the audit-core sender
|
||||
registry (KV v2 ``platform/workloads/audit-core/senders``, field
|
||||
``senders.json``) to the dedicated KV v2 path
|
||||
``platform/workloads/tenant-engine/audit-core-sender`` (one field ``token``),
|
||||
which the ESO role ``tenant-engine-audit-core-sender-eso`` reads. This is the
|
||||
user-engine / audit-core wrap-migrate technique: one platform-admin process
|
||||
reads the source and writes the target; the value only ever travels through
|
||||
this process's memory and the ``bao`` child's stdin, never argv, stdout,
|
||||
stderr, a file or the receipt.
|
||||
|
||||
Run from a railiance-platform checkout at the reviewed commit, after
|
||||
``scripts/openbao-tenant-engine-audit-core-sender-eso-attended.sh``:
|
||||
|
||||
BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \\
|
||||
warden access openbao-platform-admin-login --exec -- \\
|
||||
python3 scripts/tenant_engine_audit_core_sender_seed.py \\
|
||||
--confirm 'SEED tenant-engine audit-core-sender' \\
|
||||
--receipt "$HOME/te-sender-seed-receipt.json"
|
||||
|
||||
Add ``--dry-run`` to touch only KV metadata (no value is read or written).
|
||||
|
||||
The process prints nothing: stdout and stderr are redirected to /dev/null at
|
||||
start (warden fails closed on any child output). The optional ``--receipt``
|
||||
(created mode 0600, refused if it already exists) holds only versions,
|
||||
booleans and a fixed outcome code.
|
||||
|
||||
Exit codes:
|
||||
0 seed: written at version 1, or target already holds exactly this token
|
||||
dry-run: target absent, registry present; a seed would write
|
||||
1 unexpected internal error (contained; nothing is printed)
|
||||
2 precondition refused before any write: missing/wrong --confirm, not in a
|
||||
warden attended envelope, identity not platform-admin (or is root),
|
||||
registry absent or malformed, sender missing, sender holding zero or more
|
||||
than one token (rotation overlap in progress), sender read-capable,
|
||||
absence not provable, receipt path exists
|
||||
3 target exists and differs (other value, extra fields, deleted/destroyed
|
||||
current version); never overwritten
|
||||
4 write failed (CAS 0 refused or bao error)
|
||||
5 verify failed: readback differs or registry changed during the run
|
||||
6 dry-run only: target already exists (a seed run compares the value)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
SOURCE = 'workloads/audit-core/senders'
|
||||
TARGET = 'workloads/tenant-engine/audit-core-sender'
|
||||
SENDER = 'tenant-engine'
|
||||
FIELD = 'token'
|
||||
CONFIRM = 'SEED tenant-engine audit-core-sender'
|
||||
SCHEMA = 'platform.tenant-engine-audit-core-sender-seed.v1'
|
||||
|
||||
|
||||
class Exit(Exception):
|
||||
def __init__(self, code: int, reason: str):
|
||||
super().__init__(reason)
|
||||
self.code = code
|
||||
self.reason = reason
|
||||
|
||||
|
||||
def bao(args, payload=None):
|
||||
# Output is always captured; nothing from bao reaches the terminal.
|
||||
return subprocess.run(['bao', *args],
|
||||
input=None if payload is None else json.dumps(payload).encode(),
|
||||
capture_output=True, timeout=60)
|
||||
|
||||
|
||||
def read_json(path, *, absent_ok, code=2):
|
||||
"""Return parsed ``data`` of a read, None if provably absent, else refuse."""
|
||||
result = bao(['read', '-format=json', path])
|
||||
if result.returncode == 0:
|
||||
try:
|
||||
return json.loads(result.stdout)['data']
|
||||
except (ValueError, KeyError, TypeError):
|
||||
raise Exit(code, 'unparseable_read') from None
|
||||
if absent_ok and (b'404' in result.stderr or b'No value found' in result.stderr):
|
||||
return None
|
||||
raise Exit(code, 'read_failed_or_absence_not_proven')
|
||||
|
||||
|
||||
def live_version(meta):
|
||||
"""Current version number if it is live, else None (deleted/destroyed)."""
|
||||
cv = meta.get('current_version')
|
||||
if type(cv) is not int or cv < 1:
|
||||
return None
|
||||
entry = (meta.get('versions') or {}).get(str(cv)) or {}
|
||||
if entry.get('destroyed') or entry.get('deletion_time'):
|
||||
return None
|
||||
return cv
|
||||
|
||||
|
||||
def registry_token():
|
||||
raw = read_json('platform/data/' + SOURCE, absent_ok=False)
|
||||
try:
|
||||
version = raw['metadata']['version']
|
||||
rows = json.loads(raw['data']['senders.json'])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
raise Exit(2, 'registry_shape_invalid') from None
|
||||
if type(version) is not int or not isinstance(rows, list):
|
||||
raise Exit(2, 'registry_shape_invalid')
|
||||
match = [r for r in rows if isinstance(r, dict) and r.get('name') == SENDER]
|
||||
if len(match) != 1:
|
||||
raise Exit(2, 'sender_missing_or_duplicate')
|
||||
row = match[0]
|
||||
tokens = row.get('tokens') or ([row['token']] if row.get('token') else [])
|
||||
if not isinstance(tokens, list) or len(tokens) != 1:
|
||||
raise Exit(2, 'sender_token_count_not_one')
|
||||
token = tokens[0]
|
||||
if not isinstance(token, str) or not token or token != token.strip():
|
||||
raise Exit(2, 'sender_token_shape')
|
||||
if row.get('may_read') is not False or row.get('may_write') is not True:
|
||||
raise Exit(2, 'sender_not_write_only')
|
||||
return version, token
|
||||
|
||||
|
||||
def registry_version():
|
||||
meta = read_json('platform/metadata/' + SOURCE, absent_ok=True)
|
||||
if meta is None or live_version(meta) is None:
|
||||
raise Exit(2, 'registry_absent')
|
||||
return meta['current_version']
|
||||
|
||||
|
||||
def run(args, receipt):
|
||||
if args.confirm != CONFIRM:
|
||||
raise Exit(2, 'exact_confirmation_required')
|
||||
if (Path.home().parent.name != '.warden-attended-login'
|
||||
or os.environ.get('BAO_TOKEN') or os.environ.get('VAULT_TOKEN')):
|
||||
raise Exit(2, 'attended_warden_envelope_required')
|
||||
ident = read_json('auth/token/lookup-self', absent_ok=False)
|
||||
policies = ident.get('policies') or []
|
||||
if 'platform-admin' not in policies or 'root' in policies:
|
||||
raise Exit(2, 'attended_platform_admin_required')
|
||||
|
||||
receipt['registry_version_before'] = registry_version()
|
||||
target_meta = read_json('platform/metadata/' + TARGET, absent_ok=True)
|
||||
receipt['target_present_before'] = target_meta is not None
|
||||
if args.dry_run:
|
||||
receipt['value_reads'] = 0
|
||||
if target_meta is not None:
|
||||
receipt['target_version_before'] = target_meta.get('current_version')
|
||||
raise Exit(6, 'dry_run_target_present')
|
||||
return 'dry_run_would_write'
|
||||
|
||||
version, token = registry_token()
|
||||
if version != receipt['registry_version_before']:
|
||||
raise Exit(5, 'registry_changed_during_run')
|
||||
if target_meta is not None:
|
||||
if live_version(target_meta) is None:
|
||||
raise Exit(3, 'target_current_version_not_live')
|
||||
current = read_json('platform/data/' + TARGET, absent_ok=False)
|
||||
if (current or {}).get('data') != {FIELD: token}:
|
||||
raise Exit(3, 'target_differs')
|
||||
receipt['target_version'] = target_meta['current_version']
|
||||
return 'already_identical'
|
||||
|
||||
result = bao(['write', '-format=json', 'platform/data/' + TARGET, '-'],
|
||||
payload={'options': {'cas': 0}, 'data': {FIELD: token}})
|
||||
if result.returncode != 0:
|
||||
raise Exit(4, 'write_failed')
|
||||
|
||||
back = read_json('platform/data/' + TARGET, absent_ok=False, code=5)
|
||||
if (back or {}).get('data') != {FIELD: token} or back['metadata'].get('version') != 1:
|
||||
raise Exit(5, 'readback_mismatch')
|
||||
if registry_version() != version:
|
||||
raise Exit(5, 'registry_changed_during_run')
|
||||
receipt['target_version'] = 1
|
||||
return 'written'
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
os.dup2(devnull, 1)
|
||||
os.dup2(devnull, 2)
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--confirm', default='')
|
||||
parser.add_argument('--receipt', type=Path)
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
except SystemExit:
|
||||
return 2
|
||||
receipt = {'schema': SCHEMA, 'mode': 'dry-run' if args.dry_run else 'seed',
|
||||
'source': 'platform/' + SOURCE, 'target': 'platform/' + TARGET,
|
||||
'sender': SENDER, 'credential_values_emitted': False}
|
||||
fd = None
|
||||
if args.receipt is not None:
|
||||
try:
|
||||
fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
||||
except OSError:
|
||||
return 2
|
||||
try:
|
||||
receipt['outcome'] = run(args, receipt)
|
||||
code = 0
|
||||
except Exit as exc:
|
||||
receipt['outcome'] = exc.reason
|
||||
code = exc.code
|
||||
except Exception:
|
||||
receipt['outcome'] = 'contained_internal_error'
|
||||
code = 1
|
||||
receipt['exit_code'] = code
|
||||
if fd is not None:
|
||||
os.write(fd, (json.dumps(receipt, indent=2, sort_keys=True) + '\n').encode())
|
||||
os.close(fd)
|
||||
return code
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue