Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
148 lines
7.4 KiB
Python
148 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Contained lab-only init/unseal. Production uses the Platform custody ceremony.
|
|
|
|
The local model selection is a scope check, not an authorization grant. Caller
|
|
must supply an owner-admitted lab kubeconfig and independently observed cluster
|
|
UID. Private init output is retained for reconciliation after any unknown write.
|
|
"""
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
spec = importlib.util.spec_from_file_location('private_service_files', Path(__file__).with_name('apply-service-secrets.py'))
|
|
private = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(private)
|
|
|
|
|
|
class Refused(Exception):
|
|
pass
|
|
|
|
|
|
def run(arguments, payload=None, allowed=(0,)):
|
|
# Inherit only routing/executable inputs, never ambient bearer credentials or
|
|
# shell hooks. Diagnostics and command output remain inside this process.
|
|
env = {k: os.environ[k] for k in ('PATH', 'HOME', 'KUBECONFIG') if k in os.environ}
|
|
result = subprocess.run(['kubectl', *arguments], input=payload, stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL, timeout=60, env=env)
|
|
if result.returncode not in allowed or len(result.stdout) > 1024 * 1024:
|
|
raise Refused()
|
|
return result.stdout
|
|
|
|
|
|
def status(namespace, pod):
|
|
data = json.loads(run(['-n', namespace, 'exec', pod, '--', 'bao', 'status', '-format=json'], allowed=(0, 2)))
|
|
if not isinstance(data, dict) or type(data.get('initialized')) is not bool or type(data.get('sealed')) is not bool:
|
|
raise Refused()
|
|
return data
|
|
|
|
|
|
def init_material(raw):
|
|
value = json.loads(raw)
|
|
shares = value.get('unseal_keys_b64')
|
|
if (not isinstance(shares, list) or not shares or len(shares) > 16
|
|
or any(not isinstance(x, str) or not re.fullmatch(r'[A-Za-z0-9+/=]{16,256}', x) for x in shares)
|
|
or len(set(shares)) != len(shares)
|
|
or not isinstance(value.get('root_token'), str) or not value['root_token']):
|
|
raise Refused()
|
|
return shares
|
|
|
|
|
|
def main(args):
|
|
if args == ['--dry-run']:
|
|
print('Plan only: validate explicit lab custody and cluster UID; retain private init material; unseal and verify. No files or cluster state inspected.')
|
|
return
|
|
if len(args) > 1 or (args and args[0].startswith('-')):
|
|
raise Refused()
|
|
root = Path(__file__).resolve().parents[2]
|
|
metadata = Path(os.environ.get('OPENBAO_CONSOLE_METADATA', str(root / '.local/security-bootstrap.json')))
|
|
model = json.loads(metadata.read_bytes())
|
|
if model.get('deployment_profile') != 'lab' or model.get('openbao_unseal_custody_model') != 'sops-held-automation':
|
|
raise Refused()
|
|
expected = os.environ.get('OPENBAO_EXPECTED_CLUSTER_UID', '')
|
|
if not re.fullmatch(r'[a-zA-Z0-9-]{1,128}', expected) or not os.environ.get('KUBECONFIG'):
|
|
raise Refused()
|
|
if os.environ.get('OPENBAO_RUN_CONFIGURE_INITIAL', '0') != '0':
|
|
raise Refused() # Platform configuration is a separate owner operation.
|
|
namespace = os.environ.get('OPENBAO_NAMESPACE', 'openbao')
|
|
release = os.environ.get('OPENBAO_RELEASE', 'openbao')
|
|
if any(not re.fullmatch(r'[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', x) for x in (namespace, release)):
|
|
raise Refused()
|
|
shares = int(os.environ.get('OPENBAO_KEY_SHARES', '3'))
|
|
threshold = int(os.environ.get('OPENBAO_KEY_THRESHOLD', '2'))
|
|
if not 2 <= threshold <= shares <= 16:
|
|
raise Refused()
|
|
# Pin the actual target before credential input or any mutation.
|
|
obj = json.loads(run(['get', 'namespace', 'kube-system', '-o', 'json']))
|
|
if obj.get('metadata', {}).get('uid') != expected:
|
|
raise Refused()
|
|
pod = release + '-0'
|
|
target = json.loads(run(['-n', namespace, 'get', 'pod', pod, '-o', 'json']))
|
|
pod_uid = target.get('metadata', {}).get('uid')
|
|
if (not isinstance(pod_uid, str) or not pod_uid or target.get('metadata', {}).get('namespace') != namespace
|
|
or target.get('metadata', {}).get('name') != pod):
|
|
raise Refused()
|
|
directory = args[0] if args else str(Path(__file__).parent / 'secrets')
|
|
did_init = False
|
|
with private.custody(directory, ()) as _:
|
|
parent = Path(directory) / 'openbao'
|
|
try:
|
|
parent.mkdir(mode=0o700)
|
|
except FileExistsError:
|
|
pass
|
|
with private.custody(directory, ('openbao',)) as fds:
|
|
fd = fds['openbao']
|
|
before = status(namespace, pod)
|
|
if not before['initialized']:
|
|
# Refuse prior custody material before sending init. An exclusive
|
|
# owner window is necessary; Kubernetes exec cannot CAS pod UID.
|
|
try:
|
|
os.stat('init.json', dir_fd=fd, follow_symlinks=False)
|
|
except FileNotFoundError:
|
|
pass
|
|
else:
|
|
raise Refused()
|
|
pinned = json.loads(run(['-n', namespace, 'get', 'pod', pod, '-o', 'json']))
|
|
if pinned.get('metadata', {}).get('uid') != pod_uid:
|
|
raise Refused()
|
|
raw = run(['-n', namespace, 'exec', pod, '--', 'bao', 'operator', 'init',
|
|
'-key-shares=' + str(shares), '-key-threshold=' + str(threshold), '-format=json'])
|
|
# Preserve even a malformed successful response privately: the
|
|
# remote initialization cannot be undone or blindly retried.
|
|
output = os.open('init.json', os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
|
|
0o600, dir_fd=fd)
|
|
with os.fdopen(output, 'wb') as dest:
|
|
dest.write(raw); dest.flush(); os.fsync(dest.fileno())
|
|
os.fsync(fd)
|
|
init_material(raw)
|
|
did_init = True
|
|
observed = status(namespace, pod)
|
|
if observed['sealed']:
|
|
material = init_material(private.read_private(fd, 'init.json'))
|
|
for share in material:
|
|
pinned = json.loads(run(['-n', namespace, 'get', 'pod', pod, '-o', 'json']))
|
|
if pinned.get('metadata', {}).get('uid') != pod_uid:
|
|
raise Refused()
|
|
run(['-n', namespace, 'exec', '-i', pod, '--', 'bao', 'write', 'sys/unseal', 'key=-'],
|
|
(share + '\n').encode())
|
|
observed = status(namespace, pod)
|
|
if not observed['sealed']:
|
|
break
|
|
if not observed['initialized'] or observed['sealed']:
|
|
raise Refused()
|
|
print('EVIDENCE ' + json.dumps({'custody_model': 'sops-held-automation', 'namespace': namespace,
|
|
'pod': pod, 'cluster_uid': expected, 'pod_uid': pod_uid, 'openbao_initialized': True,
|
|
'openbao_did_init_this_run': did_init, 'openbao_post_unseal_verified': True,
|
|
'recovery_custody_verified': False, 'dry_run': False}, sort_keys=True))
|
|
print('Retain init material in protected custody; encrypt and independently verify recovery. Platform configuration remains a separate owner operation.')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
main(sys.argv[1:])
|
|
except (Refused, private.Refused, OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError):
|
|
print('ERROR: lab init/unseal refused or failed; diagnostics suppressed. Remote outcome may be unknown. Preserve private init material and reconcile through the owner before retry.', file=sys.stderr)
|
|
raise SystemExit(1)
|