diff --git a/scripts/state_hub_signing_acceptance.py b/scripts/state_hub_signing_acceptance.py new file mode 100644 index 0000000..f13ffcc --- /dev/null +++ b/scripts/state_hub_signing_acceptance.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Attended API-only signing acceptance; values stay in captured memory.""" +import argparse +import base64 +import json +import os +from pathlib import Path +import subprocess +import sys + +from state_hub_preflight_lane import CLUSTER_UID, FIELD + +POD_PROBE = ''' +import hmac,json,sys,urllib.request +from api.config import settings +from api.services.repository_rename import _verify_preflight_token, RenamePreconditionFailed +x=json.load(sys.stdin) +with urllib.request.urlopen('http://127.0.0.1:8000/state/health',timeout=20) as r: + health=json.load(r) +req=urllib.request.Request('http://127.0.0.1:8000/repos/fda8ad85-a7d7-4055-8f21-902a533e59df/rename/preflight',data=b'{"new_slug":"access-engine"}',headers={'Content-Type':'application/json'},method='POST') +with urllib.request.urlopen(req,timeout=60) as r: report=json.load(r) +token=report.get('preflight_token') +valid=False +if token: + _verify_preflight_token(token) + valid=True +old_rejected=None +if x.get('old_token'): + try: _verify_preflight_token(x['old_token']) + except RenamePreconditionFailed: old_rejected=True + else: old_rejected=False +print(json.dumps({'key_matches':hmac.compare_digest(settings.repository_rename_preflight_secret or '',x['key']), 'signed_preflight_valid':valid, 'old_token_rejected':old_rejected, 'blockers':[b.get('code') for b in report['blockers']], 'token':token, 'health':health.get('instance_role')=='primary' and health.get('instance_label')=='railiance01'})) +''' + + +def main(): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('--kubeconfig', required=True) + p.add_argument('--receipt', required=True) + p.add_argument('--fixture', required=True) + p.add_argument('--after-rotation', action='store_true') + args = p.parse_args() + k = ['kubectl', '--kubeconfig', args.kubeconfig] + def command(argv, payload=None): + r = subprocess.run(argv, input=payload, capture_output=True, timeout=90) + if r.returncode: + raise RuntimeError('captured_command_failed') + return json.loads(r.stdout) + receipt = {'schema': 'platform.statehub-preflight-api-acceptance.v1', 'status': 'failed'} + fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) + try: + assert command(k + ['get', 'namespace', 'kube-system', '-o', 'json'])['metadata']['uid'] == CLUSTER_UID + def get(kind, name): + return command(k + ['-n', 'state-hub', 'get', kind, name, '-o', 'json']) + secret = get('secret', 'state-hub-rename-preflight') + key = base64.b64decode(secret['data'][FIELD]).decode() + assert len(key) == 64 + assert FIELD not in get('secret', 'state-hub-env').get('data', {}) + dep = get('deployment', 'state-hub') + refs = [e for c in dep['spec']['template']['spec']['containers'] for e in c.get('env', []) if e['name'] == FIELD] + assert refs == [{'name': FIELD, 'valueFrom': {'secretKeyRef': {'name': 'state-hub-rename-preflight', 'key': FIELD, 'optional': False}}}] + mcp = get('deployment', 'state-hub-mcp') + assert 'state-hub-rename-preflight' not in json.dumps(mcp['spec']['template']['spec']) + pods = command(k + ['-n', 'state-hub', 'get', 'pods', '-l', 'app=state-hub', '-o', 'json'])['items'] + assert len(pods) == dep['spec']['replicas'] and pods + old = Path(args.fixture).read_text() if args.after_rotation else None + results = [] + token = None + for pod in pods: + assert not pod['metadata'].get('deletionTimestamp') + result = command(k + ['-n', 'state-hub', 'exec', '-i', pod['metadata']['name'], '-c', 'state-hub', '--', 'python', '-c', POD_PROBE], json.dumps({'key': key, 'old_token': old}).encode()) + token = result.pop('token') + assert result['key_matches'] and result['signed_preflight_valid'] and result['health'] + if args.after_rotation: + assert result['old_token_rejected'] + results.append(result) + if not args.after_rotation: + fixture_fd = os.open(args.fixture, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) + with os.fdopen(fixture_fd, 'w') as out: + out.write(token) + receipt.update(status='passed', api_only=True, replicas=len(pods), secret_resource_version=secret['metadata']['resourceVersion'], results=results) + except Exception: + receipt['error'] = 'acceptance_failed_details_suppressed' + finally: + with os.fdopen(fd, 'w') as out: + json.dump(receipt, out, indent=2) + out.write('\n') + return 0 if receipt['status'] == 'passed' else 1 + + +if __name__ == '__main__': + sys.exit(main())