Add attended prune of retired bao.coulomb.social callbacks from platform-admin
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Read-preserve-write via the reviewed loopback helper; keeps the tunnel
callback and every other setting; also captures non-secret OIDC mount config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 150322@bnt-lap001
Assistant-Session: 16a7b788-374e-4915-a1df-fc87ffd9a5e4
This commit is contained in:
codex 2026-09-24 00:49:39 +02:00
parent 85f6a14892
commit 7f25af3cb1
3 changed files with 166 additions and 0 deletions

View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
# Silent child for the governed attended login; results go to the receipt only.
set -euo pipefail
exec python3 "$(dirname "$0")/openbao_platform_admin_callback_prune.py" "$@" >/dev/null 2>&1

View file

@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Silent contained removal of the retired public callbacks from platform-admin.
Removes exactly the two bao.coulomb.social callbacks retired by RPF-WP-0025-T03
from auth/netkingdom/role/platform-admin. Every other role setting is kept,
using the reviewed read/payload/write helpers of the loopback-callback script.
Refuses unless the tunnel callback is present and stays present. Writes
nothing if the retired callbacks are already gone. One non-secret receipt; no
output.
"""
from datetime import datetime, timezone
import importlib.util
import json
import os
from pathlib import Path
import sys
HERE = Path(__file__).resolve().parent
_spec = importlib.util.spec_from_file_location('loopback', HERE / 'openbao_operator_loopback_callback.py')
loopback = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(loopback)
ROLE = loopback.ROLE
KEEP = loopback.CALLBACK # http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback
RETIRED = (
'https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback',
'https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback',
)
Refused = loopback.Refused
MOUNTS = ('netkingdom', 'keycape')
CONFIG_FIELDS = ('oidc_discovery_url', 'oidc_client_id', 'default_role', 'bound_issuer',
'oidc_response_mode', 'oidc_response_types', 'jwt_supported_algs',
'provider_config', 'namespace_in_state')
def read_mount_configs():
"""Non-secret OIDC mount settings; OpenBao never returns oidc_client_secret."""
import subprocess
configs = {}
for mount in MOUNTS:
try:
result = subprocess.run(['bao', 'read', '-format=json', 'auth/%s/config' % mount],
capture_output=True, check=True, timeout=30)
data = json.loads(result.stdout)['data']
configs[mount] = {key: data[key] for key in CONFIG_FIELDS if key in data}
except Exception:
configs[mount] = {'error': 'read_failed'}
return configs
def prune(read=loopback.read_role, write=loopback.write_role):
original = read()
uris = original['allowed_redirect_uris']
if KEEP not in uris:
raise Refused('tunnel_callback_missing')
remaining = [uri for uri in uris if uri not in RETIRED]
if remaining == uris:
return False, uris
desired = dict(loopback.payload(original), allowed_redirect_uris=remaining)
if loopback.preserved(read()) != loopback.preserved(original):
raise Refused('role_changed_before_write')
write(desired)
after = read()
if sorted(after.get('allowed_redirect_uris', [])) != sorted(remaining):
raise Refused('readback_callbacks_mismatch')
if loopback.preserved(after) != loopback.preserved(original):
raise Refused('readback_settings_changed')
return True, after['allowed_redirect_uris']
def write_receipt(path, status, **extra):
fd = os.open(Path(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
body = {
'schema': 'railiance-platform.openbao-callback-prune.v1',
'observed_at': datetime.now(timezone.utc).isoformat(),
'role': ROLE, 'retired': list(RETIRED), 'status': status,
'credential_values_emitted': False, **extra,
}
with os.fdopen(fd, 'w', encoding='utf-8') as handle:
json.dump(body, handle, indent=2, sort_keys=True)
handle.write('\n')
def main(argv):
if len(argv) != 2 or argv[0] != '--receipt':
return 2
receipt = argv[1]
try:
loopback.require_attended()
changed, uris = prune()
write_receipt(receipt, 'pruned' if changed else 'already_pruned',
changed=changed, allowed_redirect_uris=uris,
mount_configs=read_mount_configs())
return 0
except Exception as error:
try:
write_receipt(receipt, loopback.classify(error), changed=False)
except Exception:
pass
return 1
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -0,0 +1,58 @@
import copy
import importlib.util
import json
from pathlib import Path
import pytest
spec = importlib.util.spec_from_file_location(
'prune', Path(__file__).resolve().parents[1] / 'scripts/openbao_platform_admin_callback_prune.py')
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
DECLARED = json.loads((Path(__file__).resolve().parents[1] / 'openbao/auth/netkingdom-platform-admin-role.json').read_text())
def live(role):
state = {'role': copy.deepcopy(role)}
writes = []
def write(value):
writes.append(value)
state['role'].update(value)
return state, writes, (lambda: copy.deepcopy(state['role'])), write
def test_prunes_only_the_two_retired_callbacks_and_keeps_settings():
role = dict(DECLARED, allowed_redirect_uris=list(DECLARED['allowed_redirect_uris']) + list(m.RETIRED)
if not set(m.RETIRED) <= set(DECLARED['allowed_redirect_uris']) else list(DECLARED['allowed_redirect_uris']))
state, writes, read, write = live(role)
changed, uris = m.prune(read, write)
assert changed and len(writes) == 1
assert not set(m.RETIRED) & set(uris)
assert m.KEEP in uris and 'http://localhost:8250/oidc/callback' in uris
assert state['role']['token_policies'] == ['platform-admin', 'operator-custody']
assert state['role']['bound_claims'] == {'groups': ['net-kingdom-admins']}
def test_idempotent_when_already_pruned():
role = dict(DECLARED, allowed_redirect_uris=[u for u in DECLARED['allowed_redirect_uris'] if u not in m.RETIRED])
_, writes, read, write = live(role)
assert m.prune(read, write)[0] is False and writes == []
def test_refuses_without_tunnel_callback():
role = dict(DECLARED, allowed_redirect_uris=list(m.RETIRED))
_, writes, read, write = live(role)
with pytest.raises(m.Refused, match='tunnel_callback_missing'):
m.prune(read, write)
assert writes == []
def test_refuses_when_readback_drops_other_settings():
role = copy.deepcopy(DECLARED)
calls = iter([copy.deepcopy(role), copy.deepcopy(role),
dict(copy.deepcopy(role), token_ttl=60,
allowed_redirect_uris=[u for u in role['allowed_redirect_uris'] if u not in m.RETIRED])])
with pytest.raises(m.Refused, match='readback_settings_changed'):
m.prune(lambda: next(calls), lambda value: None)