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
104 lines
3.9 KiB
Python
Executable file
104 lines
3.9 KiB
Python
Executable file
#!/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:]))
|