Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
64 lines
4.6 KiB
Python
64 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Deploy the reviewed provider enrollment hook while preserving runtime secrets."""
|
|
import argparse
|
|
import copy
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
|
|
CLUSTER='a553c742-0115-43d4-99a4-a5ca56fe0786'
|
|
PROVIDER='58c7f96d-61cb-4dd4-bca2-54661c0ac375'
|
|
CONFIG='423d857b-6b10-40ad-aba8-2cda2d3645ef'
|
|
SUFFIX='\n# P06: fail startup if the enrollment guard is unavailable.\nfrom keycape_onboarding_guard import check as _p06_onboarding_check\nPI_INIT_CHECK_HOOK = "keycape_onboarding_guard.check"\n'
|
|
|
|
def kube(args,payload=None):
|
|
result=subprocess.run(['kubectl','--request-timeout=20s',*args],input=json.dumps(payload) if payload is not None else None,capture_output=True,text=True,timeout=30)
|
|
if result.returncode:raise RuntimeError('kubernetes_operation_failed')
|
|
return json.loads(result.stdout)
|
|
|
|
def plan(source,config,deployment):
|
|
code=(source/'scripts/keycape_onboarding_guard.py').read_text()
|
|
digest=hashlib.sha256(code.encode()).hexdigest();name='keycape-onboarding-'+digest[:16]
|
|
cm={'apiVersion':'v1','kind':'ConfigMap','metadata':{'name':name,'namespace':'mfa'},'immutable':True,'data':{'keycape_onboarding_guard.py':code}}
|
|
original=config['data']['pi.cfg']
|
|
if 'PI_INIT_CHECK_HOOK' in original and not original.endswith(SUFFIX):raise RuntimeError('existing_hook_requires_review')
|
|
updated=original if original.endswith(SUFFIX) else original+SUFFIX
|
|
spec=copy.deepcopy(deployment['spec']['template']['spec'])
|
|
for container in spec['containers']:
|
|
if container['name'] not in {'privacyidea','factor-recovery'}:continue
|
|
env=container.setdefault('env',[])
|
|
path=next((e for e in env if e['name']=='PYTHONPATH'),None)
|
|
if path and path.get('value') not in {'/opt/keycape-onboarding'}:raise RuntimeError('existing_python_path_requires_review')
|
|
if not path:env.append({'name':'PYTHONPATH','value':'/opt/keycape-onboarding'})
|
|
mounts=container.setdefault('volumeMounts',[])
|
|
existing=next((m for m in mounts if m['name']=='onboarding-guard'),None)
|
|
desired={'name':'onboarding-guard','mountPath':'/opt/keycape-onboarding','readOnly':True}
|
|
if existing and existing!=desired:raise RuntimeError('existing_guard_mount_differs')
|
|
if not existing:mounts.append(desired)
|
|
spec['volumes']=[v for v in spec['volumes'] if v['name']!='onboarding-guard']+[{'name':'onboarding-guard','configMap':{'name':name}}]
|
|
return cm,updated,spec
|
|
|
|
def main():
|
|
parser=argparse.ArgumentParser();parser.add_argument('--source',type=Path,required=True);parser.add_argument('--apply',action='store_true');args=parser.parse_args()
|
|
if kube(['get','namespace','kube-system','-o','json'])['metadata']['uid']!=CLUSTER:raise RuntimeError('cluster_changed')
|
|
config=kube(['-n','mfa','get','configmap','privacyidea-cfg','-o','json']);deployment=kube(['-n','mfa','get','deployment','privacyidea','-o','json'])
|
|
if config['metadata']['uid']!=CONFIG or deployment['metadata']['uid']!=PROVIDER:raise RuntimeError('target_identity_changed')
|
|
cm,updated,spec=plan(args.source,config,deployment)
|
|
print(json.dumps({'apply':args.apply,'guard_config':cm['metadata']['name'],'target':'mfa/privacyidea','config_uid':CONFIG,'provider_uid':PROVIDER,'secret_values_emitted':False}),flush=True)
|
|
if not args.apply:return
|
|
committed=subprocess.run(['git','show','HEAD:scripts/keycape_onboarding_guard.py'],cwd=args.source,capture_output=True,text=True)
|
|
if committed.returncode or committed.stdout!=cm['data']['keycape_onboarding_guard.py']:raise RuntimeError('guard_source_must_be_committed')
|
|
kube(['apply','-f','-','-o','json'],cm)
|
|
if updated!=config['data']['pi.cfg']:
|
|
kube(['-n','mfa','patch','configmap','privacyidea-cfg','--type=json','--patch-file=/dev/stdin','-o','json'],[
|
|
{'op':'test','path':'/metadata/uid','value':CONFIG},{'op':'test','path':'/metadata/resourceVersion','value':config['metadata']['resourceVersion']},
|
|
{'op':'replace','path':'/data/pi.cfg','value':updated}])
|
|
kube(['-n','mfa','patch','deployment','privacyidea','--type=json','--patch-file=/dev/stdin','-o','json'],[
|
|
{'op':'test','path':'/metadata/uid','value':PROVIDER},{'op':'test','path':'/metadata/resourceVersion','value':deployment['metadata']['resourceVersion']},
|
|
{'op':'replace','path':'/spec/template/spec','value':spec}])
|
|
after=kube(['-n','mfa','get','configmap','privacyidea-cfg','-o','json'])
|
|
if after['data']!=dict(config['data'],**{'pi.cfg':updated}):raise RuntimeError('configuration_readback_changed')
|
|
print(json.dumps({'guard_deployed':True,'wait_for_rollout_and_policy_activation':True}))
|
|
|
|
if __name__=='__main__':main()
|