coulomb-social/scripts/verify-backup-alert-email.py
tegwick c49c6b39ce
Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 6m35s
Record backup alert destinations and prepare bounded email verification
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 09:24:57 +01:00

116 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""One founder-attended SMTP proof using the existing telemetry credential projection."""
from __future__ import annotations
import argparse
import hashlib
import json
import shlex
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
PACKET = ROOT / "docs/release/backup-alert-email-2026-10-31"
NODE = r'''
import json,os,socket,subprocess,sys
p=json.load(sys.stdin);plan=p['plan'];job=p['job'];mode=p['mode']
def command(args,payload=None,allowed=(0,),timeout=30):
r=subprocess.run(args,input=payload,capture_output=True,timeout=timeout,env=dict(os.environ,KUBECONFIG='/etc/rancher/k3s/k3s.yaml'))
if r.returncode not in allowed:raise RuntimeError('bounded_command_failed')
return r.stdout
def doc(*args):return json.loads(command(['kubectl',*args,'-o','json']))
result={'schema':'coulomb.backup-email-proof-result.v1','event_id':plan['event_id'],'proposal_digest':p['digest'],'email_sent':False,'recipient_confirmed':False,'automatic_monitoring_active':False}
try:
assert mode in ('preflight','execute')
assert doc('get','namespace','kube-system')['metadata']['uid']==plan['cluster_uid']
assert doc('get','namespace',plan['namespace'])['metadata']['uid']==plan['namespace_uid']
policy=doc('get','networkpolicy','telemetry-smtp-egress','-n',plan['namespace'])
assert policy['spec']==plan['smtp_policy_spec']
resolved=socket.getaddrinfo('smtp.ionos.de',587,socket.AF_INET,socket.SOCK_STREAM)
addresses={r[4][0] for r in resolved}
assert addresses==set(plan['smtp_ipv4'])
es=doc('get','externalsecret',plan['secret_reference'],'-n',plan['namespace'])
assert es['spec']['target']['name']==plan['secret_reference']
conditions=es.get('status',{}).get('conditions',[])
assert any(c['type']=='Ready' and c['status']=='True' for c in conditions)
existing=command(['kubectl','get','job',plan['job_name'],'-n',plan['namespace'],'--ignore-not-found','-o','json'])
assert not existing.strip(), 'existing_job_requires_inspection_not_replay'
raw=json.dumps(job).encode()
command(['kubectl','create','--dry-run=server','-f','-','-o','json'],raw)
result.update(status='preflight_verified',namespace_identity_verified=True,smtp_projection_ready=True,smtp_egress_matches_current_dns=True,server_dry_run=True)
if mode=='execute':
# Never replace a Job or retry an uncertain create/send. Kubernetes refuses a duplicate name.
result['status']='creation_outcome_unconfirmed'
created=json.loads(command(['kubectl','create','-f','-','-o','json'],raw))
result['job_uid']=created['metadata']['uid'];result['job_name']=plan['job_name']
command(['kubectl','wait','--for=condition=complete','--timeout=130s','job/'+plan['job_name'],'-n',plan['namespace']],timeout=145)
current=doc('get','job',plan['job_name'],'-n',plan['namespace'])
assert current['metadata']['uid']==result['job_uid'] and current['status'].get('succeeded')==1
raw_log=command(['kubectl','logs','job/'+plan['job_name'],'-n',plan['namespace'],'--container=send'])
assert len(raw_log)<=8192
receipt=json.loads(raw_log)
assert receipt['status']=='smtp-accepted' and receipt['message_id']==plan['message_id']
result.update(status='smtp_provider_accepted',email_sent=True,provider_receipt=receipt)
except Exception as e:
result.update(
status='execution_requires_inspection' if mode=='execute' else 'preflight_refused',
failure_type=type(e).__name__,email_sent=None if mode=='execute' else False)
print(json.dumps(result));sys.exit(1)
print(json.dumps(result))
'''
def canonical(value):
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
def prepare(packet=PACKET):
plan = json.loads((packet / "plan.json").read_bytes())
job = json.loads((packet / "job.json").read_bytes())
digest = "sha256:" + hashlib.sha256(canonical({
"plan": plan, "job": job,
"launcher_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
})).hexdigest()
return plan, job, digest
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
action = parser.add_mutually_exclusive_group()
action.add_argument("--preflight", action="store_true")
action.add_argument("--execute", action="store_true")
parser.add_argument("--expect-digest")
args = parser.parse_args(argv)
plan, job, digest = prepare()
if args.expect_digest and args.expect_digest != digest:
raise SystemExit("Reviewed email proposal changed; no action")
if not (args.preflight or args.execute):
print(json.dumps({"status": "prepared", "proposal_digest": digest,
"recipient": plan["recipient"], "email_sent": False}))
return 0
if args.execute:
if not args.expect_digest or not sys.stdin.isatty():
raise SystemExit("Founder terminal and reviewed digest required")
print("Send one backup-alert verification email to " + plan["recipient"])
if input("Type SEND followed by the full digest: ").strip() != "SEND " + digest:
raise SystemExit("Not confirmed; no action")
payload = canonical({"plan": plan, "job": job, "digest": digest,
"mode": "execute" if args.execute else "preflight"})
try:
response = subprocess.run(
["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "railiance01",
"sudo -n python3 -c " + shlex.quote(NODE)],
input=payload, capture_output=True, timeout=240,
)
result = json.loads(response.stdout)
except (subprocess.TimeoutExpired, ValueError):
print(json.dumps({"status": "remote_outcome_unconfirmed", "job_name": plan["job_name"],
"proposal_digest": digest, "retry_automatically": False}))
return 1
print(json.dumps(result))
return int(response.returncode != 0)
if __name__ == "__main__":
raise SystemExit(main())