Prepare the openbao-admin signInDiagnosis flag rollout (NK-WP-0051-T02).
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

One-line, byte-preserving edit of sso/keycape-config with inspect, server
dry-run, attended apply and an exact rollback. No values are emitted.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
This commit is contained in:
tegwick 2026-10-15 00:16:15 +02:00
parent 916bc7d5b8
commit c7ad8a5bf0
4 changed files with 355 additions and 18 deletions

View file

@ -2,11 +2,18 @@
# Custodian Brief — net-kingdom
**Domain:** infotech
**Last synced:** 2026-10-14 21:54 UTC
**Last synced:** 2026-10-14 22:11 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### Implement deterministic posture and evidence feedback
Progress: 4/5 done | workplan_id: `9d7b04f9-3803-5613-b7a5-8bd606c77f5a`
**Open tasks:**
- ! Obtain audit-core freshness adoption `7d2029d2`
*(wait: audit-core tenancy.yaml must publish structured authoritative owner, observation/expiry and replacement-action metadata for WH-ENG-20260822-AUDIT-E2-03. Re-evaluate with explicit current as-of; later recovery prose does not renew E2.)*
### Publish the NetKingdom emission-cadence security profile
Progress: 4/5 done | workplan_id: `04685f94-1991-5e62-80d2-5669913e99fc`
@ -25,12 +32,13 @@ Progress: 3/6 done | workplan_id: `965ad365-6b81-50a1-a2a3-2d0c1fcce0b4`
- ! T06 — Resolve the `DataClassification` mismatch `bf8b3e1d`
*(wait: Requires InfoTechCanon ruling on public disclosure classification versus synthetic provenance, then ops-warden authoritative dataclass_floor mapping and tests; no public-to-synthetic alias.)*
### Implement deterministic posture and evidence feedback
Progress: 4/5 done | workplan_id: `9d7b04f9-3803-5613-b7a5-8bd606c77f5a`
### Remove bootstrap secret output and verify separated recovery custody
Progress: 1/3 done | workplan_id: `17d315f0-829e-57c7-85cc-3a7914152804`
**Open tasks:**
- ! Obtain audit-core freshness adoption `7d2029d2`
*(wait: audit-core tenancy.yaml must publish structured authoritative owner, observation/expiry and replacement-action metadata for WH-ENG-20260822-AUDIT-E2-03. Re-evaluate with explicit current as-of; later recovery prose does not renew E2.)*
- ! Verify owner-approved recovery and current native callers `d68cd4d3`
*(wait: Source caller reconciliation and exact owner-scoped isolated recovery ceremony are required; no live bootstrap or root retrieval is authorized by this source receipt.)*
- ► Reconcile other legacy custody callers and root-output boundaries `479d6970`
### Enterprise Federation & SAML — Expanded-Mode Keycloak Identity Broker
Progress: 1/9 done | workplan_id: `1075448f-d533-5f9e-94b7-c3adfe151a07`
@ -66,7 +74,7 @@ Progress: 2/6 done | workplan_id: `c1771c07-4466-5aa2-ad43-df63a471946b`
**Open tasks:**
- ! KeyCape shows the principal before the callback `a78219bb`
*(wait: Implemented in key-cape af3b1b0 (KEY-WP-0041). Waits for KEY-WP-0041-T02: image build, live signInDiagnosis flag on the OpenBao client, digest.)*
*(wait: Implemented in key-cape af3b1b0 (KEY-WP-0041). Image cdec9dd7 live 2026-10-15. Waits for KEY-WP-0041-T02: live signInDiagnosis flag on the OpenBao client in sso/keycape-config.)*
- ! Account site shows both sessions `bdc8b414`
*(wait: Implemented in user-engine 80f5870 (USER-WP-0039). Waits for USER-WP-0039-T02: deploy and a mismatched-session check.)*
- ! Enroll the negative-login factor `afb5816f`
@ -74,13 +82,6 @@ Progress: 2/6 done | workplan_id: `c1771c07-4466-5aa2-ad43-df63a471946b`
- ! Hand the OpenBao proof back to its owners `86fb4a6b`
*(wait: Implemented in ops-mason fca5591 (MASON-WP-0012) and railiance-platform 0f0282a (RPF-WP-0057). Waits for CCR-2026-0045 apply, overlay deploy, and the MASON-WP-0012-T04 live proof.)*
### Define an execution-attribution receipt for Railiance runs
Progress: 1/2 done | workplan_id: `e2533f3a-aa43-59b3-bff3-8e64b6149487`
**Open tasks:**
- ! Agree the evidence holder and schema with audit-core and Railiance `963120c1`
*(wait: Audit-core accepted evidence custody and published field/admission requirements in AUDIT-WP-0014-T01. Still needs Railiance owner confirmation of exact emitter and wire binding before canonical schema/validator implementation, followed by actual emission/readback and attribution/clock/unknown-case acceptance.)*
### Register Playwright human and agent consumers through governed identity delivery
Progress: 1/3 done | workplan_id: `06ba98d5-609e-5aaa-9bf7-4c4f4460e377`
@ -89,13 +90,12 @@ Progress: 1/3 done | workplan_id: `06ba98d5-609e-5aaa-9bf7-4c4f4460e377`
*(wait: Await owner-scoped registry/custody activation, native cluster authentication and individual transport, then real human and agent acceptance under CUST-WP-0080-T05.)*
- ► Deliver custody integration and guarded registration activation `a9dadd07`
### Remove bootstrap secret output and verify separated recovery custody
Progress: 1/3 done | workplan_id: `17d315f0-829e-57c7-85cc-3a7914152804`
### Define an execution-attribution receipt for Railiance runs
Progress: 1/2 done | workplan_id: `e2533f3a-aa43-59b3-bff3-8e64b6149487`
**Open tasks:**
- ! Verify owner-approved recovery and current native callers `d68cd4d3`
*(wait: Source caller reconciliation and exact owner-scoped isolated recovery ceremony are required; no live bootstrap or root retrieval is authorized by this source receipt.)*
- ► Reconcile other legacy custody callers and root-output boundaries `479d6970`
- ! Agree the evidence holder and schema with audit-core and Railiance `963120c1`
*(wait: Audit-core accepted evidence custody and published field/admission requirements in AUDIT-WP-0014-T01. Still needs Railiance owner confirmation of exact emitter and wire binding before canonical schema/validator implementation, followed by actual emission/readback and attribution/clock/unknown-case acceptance.)*
### Cut over NetKingdom identity to railiance01 and retire CoulombCore
Progress: 7/8 done | workplan_id: `d76ddccc-00c8-548a-b141-2cd660fa38da`

View file

@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""Turn on KeyCape's sign-in diagnosis page for the openbao-admin client.
NK-WP-0051-T02 / KEY-WP-0041-T02. Adds one line, `signInDiagnosis: true`, to
the existing openbao-admin registration in sso/keycape-config. The field
contract is docs/sign-in-diagnosis.md; the image that honours the flag is
key-cape sha256:cdec9dd7 (source 8c6c071), live since 2026-10-15.
Modes default to non-mutating. `inspect` reads and plans; `dry-run` sends a
server-side dry-run patch; `apply` writes and is reserved for the attended
operator with `--attended` and the observed uid/resourceVersion. `--operation
remove` is the rollback: it removes only that one line. Either way the issuer
reads the change only after `kubectl -n sso rollout restart deploy/keycape`.
Exact-match edit: flag absent -> add; already true -> noop; any other value,
a missing or duplicated openbao-admin client -> fail closed. The signing key,
every other client and every other config byte are preserved. No Secret value,
parser message or Kubernetes error text leaves the process.
"""
from __future__ import annotations
import argparse
import base64
import copy
import importlib.util
import json
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent
def module(name, filename):
spec = importlib.util.spec_from_file_location(name, ROOT / filename)
result = importlib.util.module_from_spec(spec)
spec.loader.exec_module(result)
return result
# Reuse the owner lane's contained kubectl, refusal type and cluster pin.
lane = module('portal_rollout', 'portal-client-rollout.py')
pin = lane.pin
Refused = lane.Refused
require = lane.require
CLIENT_ID = 'openbao-admin'
FLAG = 'signInDiagnosis'
LINE = FLAG + ': true'
def _inspect(secret):
raw, config, _ = pin.issuer_document(secret)
require(config['authelia'].get('issuer') == pin.VERIFIED_UPSTREAM_ISSUER,
'verified_issuer_pin_required')
clients = config.get('clients')
require(isinstance(clients, list) and clients, 'client_sequence_required')
require(all(isinstance(c, dict) and 'clientId' in c for c in clients), 'client_shape_unexpected')
ids = [c['clientId'] for c in clients]
require(len(ids) == len(set(ids)), 'duplicate_client_id')
require(CLIENT_ID in ids, 'openbao_client_missing')
position = ids.index(CLIENT_ID)
value = clients[position].get(FLAG, None)
require(value in (None, True), 'existing_flag_differs_requires_reconciliation')
root = yaml.compose(raw)
sequence = next(v for k, v in root.value if k.value == 'clients')
require(isinstance(sequence, yaml.nodes.SequenceNode) and not sequence.flow_style,
'block_client_sequence_required')
item = sequence.value[position]
require(isinstance(item, yaml.nodes.MappingNode) and not item.flow_style,
'block_client_mapping_required')
return raw, config, position, item
def _line_end(raw, index):
end = raw.find('\n', index)
return len(raw) if end < 0 else end + 1
def enable(secret):
"""Return (base64 config, changed) with only the flag line added."""
raw, config, position, item = _inspect(secret)
if config['clients'][position].get(FLAG) is True:
return secret['data']['config.yaml'], False
key, value = item.value[0]
require(isinstance(value, yaml.nodes.ScalarNode)
and key.start_mark.line == value.end_mark.line, 'unexpected_item_layout')
index = _line_end(raw, value.end_mark.index)
prefix = '' if raw[index - 1:index] == '\n' else '\n'
updated = raw[:index] + prefix + ' ' * key.start_mark.column + LINE + '\n' + raw[index:]
expected = copy.deepcopy(config)
expected['clients'][position][FLAG] = True
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def disable(secret):
"""Rollback: return (base64 config, changed) with only the flag line removed."""
raw, config, position, item = _inspect(secret)
if FLAG not in config['clients'][position]:
return secret['data']['config.yaml'], False
key = next(k for k, _ in item.value if k.value == FLAG)
require(key is not item.value[0][0], 'unexpected_item_layout')
start = raw.rfind('\n', 0, key.start_mark.index) + 1
end = _line_end(raw, key.start_mark.index)
require(raw[start:end].strip() == LINE, 'unexpected_item_layout')
updated = raw[:start] + raw[end:]
expected = copy.deepcopy(config)
del expected['clients'][position][FLAG]
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def run(args, kubectl=None):
kubectl = kubectl or lane.kubectl
cluster = kubectl(['get', 'namespace', 'kube-system', '-o', 'json'])
require(cluster['metadata']['uid'] == lane.CLUSTER_UID, 'cluster_identity_changed')
before = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(not before['metadata'].get('ownerReferences'), 'controller_owned_secret')
metadata = pin.safe_metadata(before)
encoded, changed = (enable if args.operation == 'add' else disable)(before)
receipt = {'schema': 'netkingdom.keycape-openbao-signin-diagnosis.v1',
'target': 'sso/keycape-config', 'before': metadata, 'operation': args.operation,
'client_id': CLIENT_ID, 'flag': FLAG, 'change_needed': changed, 'mode': args.mode,
'values_emitted': False, 'changed': False}
if args.mode == 'inspect':
return receipt
if args.mode == 'apply':
require(args.attended, 'attended_operator_required')
require(metadata == {'uid': args.expected_uid, 'resource_version': args.expected_resource_version},
'observed_revision_changed')
if changed:
patch = [
{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'replace', 'path': '/data/config.yaml', 'value': encoded},
]
command = ['-n', 'sso', 'patch', 'secret', 'keycape-config', '--type=json',
'--patch-file=/dev/stdin', '-o', 'json']
if args.mode == 'dry-run':
command += ['--dry-run=server']
result = kubectl(command, patch)
require(result['data'] == dict(before['data'], **{'config.yaml': encoded}), 'patch_response_mismatch')
if args.mode == 'apply':
after = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(after['metadata']['uid'] == metadata['uid']
and after['data'] == dict(before['data'], **{'config.yaml': encoded}),
'readback_mismatch_stop_without_stale_replay')
receipt.update(changed=changed, after=pin.safe_metadata(after), restart_required=changed)
receipt.update(unrelated_config_bytes_preserved=True, other_secret_data_unchanged=True)
return receipt
def main(argv=None, kubectl=None):
parser = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
parser.add_argument('mode', nargs='?', default='inspect', choices=['inspect', 'dry-run', 'apply'])
parser.add_argument('--operation', default='add', choices=['add', 'remove'])
parser.add_argument('--expected-uid')
parser.add_argument('--expected-resource-version')
parser.add_argument('--attended', action='store_true', help='required for apply')
args = parser.parse_args(argv)
try:
print(json.dumps(run(args, kubectl), sort_keys=True))
return 0
except Refused as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except pin.IssuerPinError as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except Exception:
# Parser and Kubernetes errors can contain full Secret material.
print(json.dumps({'status': 'failed', 'reason': 'contained_operation_failed'}))
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,156 @@
import base64
import contextlib
import importlib.util
import io
import json
from pathlib import Path
import unittest
spec = importlib.util.spec_from_file_location(
'openbao_signin_diagnosis_rollout', Path(__file__).with_name('openbao-signin-diagnosis-rollout.py'))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
UID = '00000000-0000-4000-8000-000000000001'
CLUSTER = {'metadata': {'uid': module.lane.CLUSTER_UID}}
OPENBAO = (' - clientId: openbao-admin\n'
' displayName: Railiance OpenBao Admin\n'
' redirectUris:\n'
' - http://localhost:8250/oidc/callback\n'
' clientType: public\n')
OTHER = (' - clientId: user-engine-portal\n'
' clientType: public\n')
def fixture(clients=OTHER + OPENBAO, tail='tokenLifetime: 15m\n'):
# Synthetic marker values only; no live Secret is loaded by this suite.
raw = ('# preserve this comment\nissuer: https://kc.coulomb.social\n'
'authelia:\n issuer: https://auth.coulomb.social\n clientSecret: synthetic\n'
'clients:\n' + clients + tail)
secret = {'metadata': {'uid': UID, 'resourceVersion': '7'},
'data': {'config.yaml': base64.b64encode(raw.encode()).decode(),
'signing-key.pem': base64.b64encode(b'synthetic').decode()}}
return secret, raw
def decode(encoded):
return base64.b64decode(encoded).decode()
class EditTests(unittest.TestCase):
def test_enable_adds_exactly_one_line_after_client_id(self):
secret, raw = fixture()
encoded, changed = module.enable(secret)
self.assertTrue(changed)
expected = raw.replace(' - clientId: openbao-admin\n',
' - clientId: openbao-admin\n signInDiagnosis: true\n')
self.assertEqual(decode(encoded), expected)
def test_enable_is_noop_when_already_true(self):
secret, _ = fixture(OPENBAO.replace('clientType: public', 'clientType: public\n signInDiagnosis: true'))
encoded, changed = module.enable(secret)
self.assertFalse(changed)
self.assertEqual(encoded, secret['data']['config.yaml'])
def test_other_flag_value_fails_closed(self):
secret, _ = fixture(OPENBAO + ' signInDiagnosis: false\n')
for operation in (module.enable, module.disable):
with self.assertRaises(module.Refused):
operation(secret)
def test_missing_or_duplicate_client_fails_closed(self):
for clients in (OTHER, OPENBAO + OPENBAO):
with self.assertRaises(module.Refused):
module.enable(fixture(clients)[0])
def test_unverified_upstream_issuer_refused(self):
secret, raw = fixture()
raw = raw.replace('https://auth.coulomb.social', 'https://evil.test')
secret['data']['config.yaml'] = base64.b64encode(raw.encode()).decode()
with self.assertRaises(module.Refused):
module.enable(secret)
def test_rollback_restores_exact_bytes(self):
secret, raw = fixture()
enabled, _ = module.enable(secret)
secret['data']['config.yaml'] = enabled
restored, changed = module.disable(secret)
self.assertTrue(changed)
self.assertEqual(decode(restored), raw)
def test_rollback_absent_is_noop(self):
secret, _ = fixture()
encoded, changed = module.disable(secret)
self.assertFalse(changed)
self.assertEqual(encoded, secret['data']['config.yaml'])
class RunTests(unittest.TestCase):
"""Drive run() against a fake kubectl; nothing reaches a cluster."""
def setUp(self):
self.secret, _ = fixture()
self.calls = []
def fake(self, arguments, payload=None):
self.calls.append((arguments, payload))
if arguments[:2] == ['get', 'namespace']:
return CLUSTER
if 'patch' in arguments:
data = dict(self.secret['data'], **{'config.yaml': payload[-1]['value']})
if '--dry-run=server' not in arguments:
self.secret = {'metadata': {'uid': UID, 'resourceVersion': '8'}, 'data': data}
return {'data': data}
return self.secret
def main(self, *argv):
out = io.StringIO()
with contextlib.redirect_stdout(out):
code = module.main(list(argv), kubectl=self.fake)
return code, out.getvalue()
def patches(self):
return [c for c in self.calls if 'patch' in c[0]]
def test_default_is_inspect_and_never_patches(self):
code, out = self.main()
self.assertEqual(code, 0)
receipt = json.loads(out)
self.assertTrue(receipt['change_needed'])
self.assertEqual(receipt['before'], {'uid': UID, 'resource_version': '7'})
self.assertEqual(self.patches(), [])
self.assertNotIn('synthetic', out)
def test_dry_run_requires_observed_revision_and_is_server_side(self):
code, out = self.main('dry-run')
self.assertEqual((code, json.loads(out)['reason']), (1, 'observed_revision_changed'))
code, out = self.main('dry-run', '--expected-uid', UID, '--expected-resource-version', '7')
self.assertEqual(code, 0)
self.assertIn('--dry-run=server', self.patches()[-1][0])
self.assertNotIn('synthetic', out)
def test_apply_requires_attended_and_reports_restart(self):
code, out = self.main('apply', '--expected-uid', UID, '--expected-resource-version', '7')
self.assertEqual((code, json.loads(out)['reason']), (1, 'attended_operator_required'))
self.assertEqual(self.patches(), [])
code, out = self.main('apply', '--attended', '--expected-uid', UID, '--expected-resource-version', '7')
receipt = json.loads(out)
self.assertEqual(code, 0)
self.assertTrue(receipt['changed'] and receipt['restart_required'])
self.assertEqual(receipt['after']['resource_version'], '8')
self.assertEqual(self.patches()[-1][1][:2], [
{'op': 'test', 'path': '/metadata/uid', 'value': UID},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': '7'}])
self.assertNotIn('synthetic', out)
def test_failures_never_print_secret_material(self):
def broken(arguments, payload=None):
raise RuntimeError('synthetic value leaked in error text')
out = io.StringIO()
with contextlib.redirect_stdout(out):
self.assertEqual(module.main([], kubectl=broken), 1)
self.assertNotIn('synthetic', out.getvalue())
if __name__ == '__main__':
unittest.main()

View file

@ -113,6 +113,11 @@ current flow. The page has one action, continue, and a line that says how
to sign in as someone else. A groups
refusal from OpenBao then follows a screen that already named the uid.
The live flag is set with
`sso-mfa/k8s/keycape/openbao-signin-diagnosis-rollout.py` (inspect, server
dry-run, attended apply on the observed uid/resourceVersion; `--operation
remove` is the rollback), followed by an issuer restart.
Acceptance is a deployed key-cape image. The log line contains no token,
code, email, or one-time password. This repo records the image digest in
the T01 document.