Establish scoped KeyCape factor custody and verified automatic renewal
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
KeyCape factor custody acceptance / acceptance (push) Successful in 7s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
This commit is contained in:
codex 2026-09-13 16:25:33 +02:00
parent b75729b799
commit 2e2c31d237
22 changed files with 1169 additions and 1 deletions

View file

@ -0,0 +1,39 @@
"""Run native delivery/scope/rotation acceptance; no attended admin or secret output."""
import copy,json,subprocess,time,uuid
from pathlib import Path
from keycape_factor_activate import CLUSTER,kubectl
ROOT=Path(__file__).resolve().parents[1]
def run():
if kubectl("get","ns","kube-system","-o","json")["metadata"]["uid"]!=CLUSTER:raise ValueError()
cron=kubectl("-n","sso","get","cronjob","keycape-factor-renewer","-o","json")
spec=copy.deepcopy(cron["spec"]["jobTemplate"]["spec"])
spec.update(activeDeadlineSeconds=300,backoffLimit=0)
p=spec["template"]["spec"];p["serviceAccountName"]="keycape-factor-eso"
p["containers"][0]["command"]=["python3","-c",(ROOT/"scripts/keycape_factor_delivery_probe.py").read_text()]
p["volumes"].append({"name":"factor","secret":{"secretName":"keycape-factor-read","defaultMode":288}})
p["containers"][0]["volumeMounts"].append({"name":"factor","mountPath":"/factor","readOnly":True})
suffix=uuid.uuid4().hex[:8];name="keycape-factor-proof-"+suffix
kubectl("create","-f","-","-o","json",payload={"apiVersion":"batch/v1","kind":"Job","metadata":{"name":name,"namespace":"sso"},"spec":spec})
deadline=time.monotonic()+310;rotating=False
while time.monotonic()<deadline:
r=subprocess.run(["kubectl","-n","sso","logs","job/"+name],capture_output=True,text=True,timeout=15)
results=[]
if r.returncode==0:
for line in r.stdout.splitlines():
try:results.append(json.loads(line))
except ValueError:pass
if results and not rotating:
if results[0].get("phase")!="awaiting_rotation":raise ValueError()
renewal={"apiVersion":"batch/v1","kind":"Job","metadata":{"name":"keycape-factor-rotate-"+suffix,"namespace":"sso"},"spec":cron["spec"]["jobTemplate"]["spec"]}
kubectl("create","-f","-","-o","json",payload=renewal);rotating=True
print(json.dumps({"phase":"waiting_for_mounted_rotation","job":name}),flush=True)
if results and "phase" not in results[-1]:
result={k:v for k,v in results[-1].items() if isinstance(v,bool)}
result["job"]=name
return result
time.sleep(5)
raise TimeoutError()
if __name__=="__main__":
try:result=run()
except Exception:result={"success":False,"failure":"native acceptance failed or timed out"}
print(json.dumps(result),flush=True);raise SystemExit(0 if result.get("success") else 1)

View file

@ -0,0 +1,57 @@
"""Guarded activation; credentials remain in memory and subprocess stdin."""
import argparse,base64,copy,json,re,subprocess,time
CLUSTER="a553c742-0115-43d4-99a4-a5ca56fe0786"
DEPLOYMENT="99ddd83c-cb3f-4847-bcf8-35f1aa87627f"
IMAGE="forgejo.coulomb.social/coulomb/key-cape@sha256:c9eb584d60efecfe00e1745a7e8cd3ebb4ae0f94faa715ef9594cd58a5dcebb6"
MOUNT={"name":"factor-token","mountPath":"/etc/keycape-factor","readOnly":True}
VOLUME={"name":"factor-token","secret":{"secretName":"keycape-factor-read","defaultMode":288,"items":[{"key":"admin-token","path":"admin-token"}]}}
def kubectl(*args,payload=None):
r=subprocess.run(["kubectl",*args],input=json.dumps(payload).encode() if payload is not None else None,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=40)
if r.returncode:raise RuntimeError("Kubernetes operation failed")
return json.loads(r.stdout) if r.stdout else {}
def rewrite_config(raw):
text=raw.decode();match=re.search(r"(?m)^privacyidea:[^\n]*\n(?:(?:[ \t]+[^\n]*|)[\n]|[ \t]+[^\n]*$)*",text)
if not match:raise ValueError("privacyidea section missing")
section=match.group()
if re.search(r"(?m)^ adminTokenFile:",section):
if not re.search(r"(?m)^ adminTokenFile: /etc/keycape-factor/admin-token$",section) or re.search(r"(?m)^ adminToken:",section):raise ValueError("unexpected credential configuration")
return raw
section,n=re.subn(r"(?m)^ adminToken:[^\n]*$"," adminTokenFile: /etc/keycape-factor/admin-token",section)
if n!=1:raise ValueError("expected exactly one inline token")
return (text[:match.start()]+section+text[match.end():]).encode()
def deployment_patch(obj):
if obj["metadata"]["uid"]!=DEPLOYMENT:raise ValueError("deployment identity mismatch")
spec=obj["spec"]["template"]["spec"];containers=spec["containers"]
i=next(i for i,c in enumerate(containers) if c["name"]=="keycape")
if containers[i]["image"]!=IMAGE:raise ValueError("deployment image mismatch")
mounts=copy.deepcopy(containers[i].get("volumeMounts",[]));volumes=copy.deepcopy(spec.get("volumes",[]))
for entries,expected in ((mounts,MOUNT),(volumes,VOLUME)):
existing=[x for x in entries if x["name"]==expected["name"]]
if existing and existing!=[expected]:raise ValueError("existing factor mount differs")
if not existing:entries.append(expected)
return [{"op":"test","path":"/metadata/uid","value":DEPLOYMENT},{"op":"test","path":"/metadata/resourceVersion","value":obj["metadata"]["resourceVersion"]},{"op":"add","path":f"/spec/template/spec/containers/{i}/volumeMounts","value":mounts},{"op":"add","path":"/spec/template/spec/volumes","value":volumes}]
def run(apply):
if kubectl("get","ns","kube-system","-o","json")["metadata"]["uid"]!=CLUSTER:raise ValueError("cluster identity mismatch")
es=kubectl("-n","sso","get","externalsecret","keycape-factor-read","-o","json")
if not any(x["type"]=="Ready" and x["status"]=="True" for x in es.get("status",{}).get("conditions",[])):raise ValueError("delivery not ready")
delivered=kubectl("-n","sso","get","secret","keycape-factor-read","-o","json")
expiry=int(base64.b64decode(delivered["data"]["expires-at"]))
if expiry-time.time()<600:raise ValueError("delivered token too near expiry")
secret=kubectl("-n","sso","get","secret","keycape-config","-o","json")
old=secret["data"]["config.yaml"];new=base64.b64encode(rewrite_config(base64.b64decode(old))).decode()
patch=[{"op":"test","path":"/metadata/uid","value":secret["metadata"]["uid"]},{"op":"test","path":"/metadata/resourceVersion","value":secret["metadata"]["resourceVersion"]},{"op":"replace","path":"/data/config.yaml","value":new}]
deployment=kubectl("-n","sso","get","deployment","keycape","-o","json");dp=deployment_patch(deployment)
for kind,name,ops in (("secret","keycape-config",patch),("deployment","keycape",dp)):
kubectl("-n","sso","patch",kind,name,"--type=json","--patch-file=/dev/stdin","--dry-run=server","-o","json",payload=ops)
if apply:
kubectl("-n","sso","patch","secret","keycape-config","--type=json","--patch-file=/dev/stdin","-o","json",payload=patch)
kubectl("-n","sso","patch","deployment","keycape","--type=json","--patch-file=/dev/stdin","-o","json",payload=dp)
current=kubectl("-n","sso","get","secret","keycape-config","-o","json")
expected=dict(secret["data"],**{"config.yaml":new})
if current["data"]!=expected:raise ValueError("config readback mismatch")
return {"success":True,"applied":apply,"unrelated_config_preserved":True,"credential_source":"mounted_file","expires_at":expiry}
if __name__=="__main__":
p=argparse.ArgumentParser();p.add_argument("--apply",action="store_true");a=p.parse_args()
try:result=run(a.apply)
except Exception:result={"success":False,"failure":"activation failed; inspect sanitized resource status"}
print(json.dumps(result));raise SystemExit(0 if result["success"] else 1)

View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Silent attended custody and dedicated provider bootstrap; no personal passwords."""
import argparse,json,os,secrets,subprocess,time,re
from pathlib import Path
from keycape_factor_metadata import call
ROOT=Path(__file__).resolve().parents[1]
ISSUER="platform/data/workloads/net-kingdom/keycape-factor-issuer"
TOKEN="platform/data/workloads/net-kingdom/keycape-factor-read"
REQUEST="RPF-WP-0040"
USER="keycape-factor-reader"
def read_optional(path):
p=subprocess.run(["bao","read","-format=json",path],capture_output=True,text=True,timeout=25)
if p.returncode:
# Only explicit 404 permits creation. Permission or transport failures never mean absent.
if "Code: 404" in p.stderr or "No value found" in p.stderr:return None
raise RuntimeError("custody read failed")
return json.loads(p.stdout)["data"]
def execute():
cluster=json.loads(subprocess.check_output(["kubectl","get","ns","kube-system","-o","json"]))
if cluster["metadata"]["uid"]!="a553c742-0115-43d4-99a4-a5ca56fe0786":raise RuntimeError("wrong cluster")
issuer=read_optional(ISSUER)
if issuer is None:
secret={"USERNAME":USER,"PASSWORD":secrets.token_urlsafe(48),"REQUEST":REQUEST}
call("write","-format=json",ISSUER,"-",payload={"options":{"cas":0},"data":secret})
issuer=read_optional(ISSUER)
secret=issuer["data"]
if set(secret)!={"USERNAME","PASSWORD","REQUEST"} or secret["USERNAME"]!=USER or secret["REQUEST"]!=REQUEST:raise RuntimeError("issuer custody mismatch")
source=(ROOT/"scripts/keycape_factor_provider.py").read_text()
p=subprocess.run(["kubectl","-n","mfa","exec","-i","deployment/privacyidea","-c","privacyidea","--","python3","-c",source],input=json.dumps({"operation":"bootstrap","username":USER,"password":secret["PASSWORD"]}),text=True,capture_output=True,timeout=60)
if p.returncode:
failure=json.loads(p.stdout)
permitted={"admin policy baseline changed","existing managed policy differs","service name already belongs to another setup","service authentication failed","unsupported service token lifetime","factor lookup failed","service has unexpected administration rights","provider response too large"}
reason=failure.get("failure")
raise RuntimeError(reason if reason in permitted or (isinstance(reason,str) and re.fullmatch(r"factor lookup failed [0-9]{3} (True|False) (True|False) (True|False)",reason)) else "provider setup failed without secret-bearing detail")
result=json.loads(p.stdout)
if not result.get("policy_read_denied") or result["expires_at"]<=time.time()+300:raise RuntimeError("provider verification incomplete")
current=read_optional(TOKEN)
if current and current["data"].get("REQUEST")!=REQUEST:raise RuntimeError("token custody belongs to another request")
cas=current["metadata"]["version"] if current else 0
call("write","-format=json",TOKEN,"-",payload={"options":{"cas":cas},"data":{"TOKEN":result["token"],"EXPIRES_AT":result["expires_at"],"REQUEST":REQUEST}})
stored=read_optional(TOKEN)
if stored["data"]["TOKEN"]!=result["token"]:raise RuntimeError("token custody readback failed")
return {"issuer_path":ISSUER,"token_path":TOKEN,"token_version":stored["metadata"]["version"],"expires_at":result["expires_at"],"provider_policy_read_denied":True,"cross_user_factor_visible":result["cross_user_factor_visible"],"values_emitted":False}
def main():
p=argparse.ArgumentParser();p.add_argument("--receipt",type=Path,required=True);args=p.parse_args()
if any(os.environ.get(k) for k in ["OPENBAO_TOKEN","BAO_TOKEN","VAULT_TOKEN"]):return 2
fd=os.open(args.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);result={"success":False}
try:result.update(execute());result["success"]=True;return 0
except RuntimeError as e:result["failure"]=str(e);return 1
except Exception as e:result["failure_type"]=type(e).__name__;return 1
finally:os.write(fd,json.dumps(result,indent=2).encode());os.close(fd)
if __name__=="__main__":raise SystemExit(main())

View file

@ -0,0 +1,44 @@
"""Native proof job: only boolean scope and projection observations leave the pod."""
import json,time,sys,urllib.parse
from pathlib import Path
sys.path.insert(0,"/worker")
from renew import http,BAO,PI,TARGET,ISSUER,success
result={"success":False};session=None
try:
jwt=Path("/var/run/keycape-factor/token").read_text().strip()
code,body=http(BAO+"/auth/kubernetes/login",{"role":"keycape-factor-renewer","jwt":jwt})
result["wrong_service_account_denied"]=code in (400,403)
code,body=http(BAO+"/auth/kubernetes/login",{"role":"keycape-factor-workload-kv-read","jwt":jwt})
if code!=200:raise ValueError()
session=body["auth"]["client_token"];headers={"X-Vault-Token":session}
code,_=http(BAO+"/"+ISSUER,headers=headers);result["issuer_password_denied"]=code==403
code,_=http(BAO+"/platform/data/workloads/net-kingdom/unrelated",headers=headers);result["sibling_secret_denied"]=code==403
code,data=http(BAO+"/"+TARGET,headers=headers)
if code!=200:raise ValueError()
initial=Path("/factor/admin-token").read_text().strip()
result["mounted_token_matches_custody"]=initial==data["data"]["data"]["TOKEN"]
listing=success(*http(PI+"/token/?tokenrealm=coulomb&active=True&pagesize=1",headers={"Authorization":initial}))
user=listing["tokens"][0]["username"]
query=urllib.parse.urlencode({"user":user,"realm":"coulomb","active":"True"})
listing=success(*http(PI+"/token/?"+query,headers={"Authorization":initial}))
result["keycape_user_lookup_accepted"]=listing["count"]>0 and any(x.get("active") is True for x in listing["tokens"])
code,_=http(PI+"/policy/",headers={"Authorization":initial});result["provider_administration_denied"]=code in (401,403)
print(json.dumps({"phase":"awaiting_rotation",**result}),flush=True)
for _ in range(48):
time.sleep(5)
current=Path("/factor/admin-token").read_text().strip()
if current!=initial:
listing=success(*http(PI+"/token/?"+query,headers={"Authorization":current}))
result["rotated_projection_accepted"]=listing["count"]>0
break
result["success"]=all(v for k,v in result.items() if k!="success") and result.get("rotated_projection_accepted",False)
except Exception:pass
finally:
if session:
try:
code,_=http(BAO+"/auth/token/revoke-self",headers={"X-Vault-Token":session},method="POST")
result["session_revoked"]=code in (200,204)
except Exception:result["session_revoked"]=False
if not result["session_revoked"]:result["success"]=False
print(json.dumps(result),flush=True)
raise SystemExit(0 if result["success"] else 1)

View file

@ -0,0 +1,16 @@
import json,os,sys
from pathlib import Path
from keycape_factor_metadata import call,normalized,policy_text
NAME="workload-kv-read-keycape-factor-read"
OLD='# Exact-path JWT delivery; no issuer password or sibling paths.\npath "platform/data/workloads/net-kingdom/keycape-factor-read" {\n capabilities = ["read"]\n}\npath "platform/metadata/workloads/net-kingdom/keycape-factor-read" {\n capabilities = ["read"]\n}\n\n# ESO validates its own short-lived workload session.\npath "auth/token/lookup-self" {\n capabilities = ["read"]\n}\n'
NEW=Path(__file__).resolve().parents[1].joinpath("openbao/policies/"+NAME+".hcl").read_text()
fd=os.open(sys.argv[1],os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
result={"success":False}
try:
current=policy_text(call("policy","read","-format=json",NAME))
if normalized(current) not in (normalized(OLD),normalized(NEW)):raise RuntimeError()
call("write","-format=json","sys/policies/acl/"+NAME,"-",payload={"policy":NEW})
if normalized(policy_text(call("policy","read","-format=json",NAME)))!=normalized(NEW):raise RuntimeError()
result={"success":True,"change":"ESO self-session lookup and revocation only","secret_values_read":False}
finally:
os.write(fd,json.dumps(result).encode());os.close(fd)

View file

@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Reviewed silent metadata apply inside the attended OpenBao envelope."""
import argparse,json,os,subprocess
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
POLICY="workload-kv-read-keycape-factor-read"
ROLE="keycape-factor-workload-kv-read"
ROLE_PATH="auth/kubernetes/role/"+ROLE
POLICY_PATH="sys/policies/acl/"+POLICY
KV="platform/workloads/net-kingdom/keycape-factor-read"
ROLE_CONFIG={"bound_service_account_names":["keycape-factor-eso"],"bound_service_account_namespaces":["sso"],"audience":"openbao","token_policies":[POLICY],"token_ttl":900,"token_max_ttl":900,"token_no_default_policy":True}
def call(*args,payload=None):
p=subprocess.run(["bao",*args],input=json.dumps(payload) if payload is not None else None,capture_output=True,text=True,timeout=25)
if p.returncode:raise RuntimeError("OpenBao metadata command rejected: "+args[0]+" "+args[1])
return json.loads(p.stdout) if p.stdout.strip() else {}
def normalized(value):return "".join(value.split())
def policy_text(value):return value.get("data",value).get("policy","")
def verify_role(data):
return all(data.get(k)==v for k,v in ROLE_CONFIG.items())
def execute(apply=False):
mounts=call("secrets","list","-format=json")
if mounts.get("platform/",{}).get("type")!="kv" or str(mounts["platform/"].get("options",{}).get("version"))!="2":raise RuntimeError("Expected existing KV v2 mount")
auth=call("auth","list","-format=json")
if auth.get("kubernetes/",{}).get("type")!="kubernetes":raise RuntimeError("Expected existing Kubernetes auth")
# Capability checks are individual: the CLI interprets multiple arguments as TOKEN PATH.
capabilities={p:call("token","capabilities","-format=json",p) for p in [POLICY_PATH,ROLE_PATH]}
if any(not {"create","update"}.issubset(set(c)) and "root" not in c for c in capabilities.values()):raise RuntimeError("Missing metadata administration capabilities")
policy=(ROOT/"openbao/policies"/(POLICY+".hcl")).read_text()
if apply:
# New dedicated names only. Existing objects must match; never overwrite foreign metadata.
policies=call("policy","list","-format=json")
if POLICY in policies:
current=call("policy","read","-format=json",POLICY)
if normalized(current.get("data",current).get("policy",""))!=normalized(policy):raise RuntimeError("Existing policy differs")
else:call("write","-format=json",POLICY_PATH,"-",payload={"policy":policy})
roles=call("list","-format=json","auth/kubernetes/role")
role_names=roles if isinstance(roles,list) else roles.get("data",{}).get("keys",[])
if ROLE in role_names:
if not verify_role(call("read","-format=json",ROLE_PATH).get("data",{})):raise RuntimeError("Existing role differs")
else:call("write","-format=json",ROLE_PATH,"-",payload=ROLE_CONFIG)
if not verify_role(call("read","-format=json",ROLE_PATH).get("data",{})):raise RuntimeError("Role readback differs")
if normalized(policy_text(call("policy","read","-format=json",POLICY)))!=normalized(policy):raise RuntimeError("Policy readback differs")
return {"operation":"apply" if apply else "preflight","policy":POLICY,"role":ROLE_PATH,"kv_path":KV,"field":"TOKEN","metadata_verified":apply,"secret_values_read":False,"secret_values_written":False}
def main():
parser=argparse.ArgumentParser();parser.add_argument("--apply",action="store_true");parser.add_argument("--receipt",type=Path,required=True);args=parser.parse_args()
if any(os.environ.get(k) for k in ["OPENBAO_TOKEN","BAO_TOKEN","VAULT_TOKEN"]):return 2
fd=os.open(args.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
result={"success":False,"phase":"metadata"}
try:
result.update(execute(args.apply));result["success"]=True;return 0
except RuntimeError as exc:
result["failure"]=str(exc);return 1
except Exception as exc:
result["failure_type"]=type(exc).__name__;return 1
finally:
os.write(fd,json.dumps(result,indent=2).encode());os.close(fd)
if __name__=="__main__":raise SystemExit(main())

View file

@ -0,0 +1,72 @@
"""Run only inside the provider; secret-bearing stdin/stdout stay in owner pipes."""
import contextlib,io,json,logging,sys,time,base64,urllib.request,urllib.error,urllib.parse
USER="keycape-factor-reader"
BASELINE="keycape-preserve-existing-admins"
READER="keycape-factor-reader-coulomb"
REALM="coulomb"
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self,*args,**kwargs):return None
def request(path,payload=None,token=None):
headers={}
if token:headers["Authorization"]=token
data=urllib.parse.urlencode(payload).encode() if payload is not None else None
req=urllib.request.Request("http://127.0.0.1:8080"+path,data=data,headers=headers)
try:
with urllib.request.build_opener(NoRedirect()).open(req,timeout=15) as r:
raw=r.read(1048577)
if len(raw)>1048576:raise RuntimeError("provider response too large")
return r.status,json.loads(raw)
except urllib.error.HTTPError as e:return e.code,{}
def token_result(password):
code,auth=request("/auth",{"username":USER,"password":password})
value=auth.get("result",{}).get("value",{})
if code!=200 or not auth.get("result",{}).get("status") or value.get("role")!="admin" or value.get("username")!=USER:raise RuntimeError("service authentication failed")
token=value.get("token","")
claims=json.loads(base64.urlsafe_b64decode(token.split('.')[1]+'==='))
expiry=int(claims['exp'])
if not 300<expiry-time.time()<=7200:raise RuntimeError("unsupported service token lifetime")
code,listing=request("/token/?tokenrealm=coulomb&active=True&page=1&pagesize=1",token=token)
lv=listing.get("result",{}).get("value",{})
if code!=200 or not listing.get("result",{}).get("status") or not isinstance(lv.get("tokens"),list) or not isinstance(lv.get("count"),int):raise RuntimeError("factor lookup failed "+str(code)+" "+str(bool(listing.get("result",{}).get("status")))+" "+str(isinstance(lv.get("tokens"),list))+" "+str(isinstance(lv.get("count"),int)))
code,_=request("/policy/",token=token)
if code not in (401,403):raise RuntimeError("service has unexpected administration rights")
return {"token":token,"expires_at":expiry,"cross_user_factor_visible":lv["count"]>0,"policy_read_denied":True}
def bootstrap(password):
logging.disable(logging.CRITICAL)
from privacyidea.app import create_app
from privacyidea.lib.policy import PolicyClass,set_policy
from privacyidea.lib.auth import get_db_admins,create_db_admin
app=create_app(config_name="production")
with app.app_context():
policies=PolicyClass().list_policies(scope="admin",active=True)
# Source preflight found no policies. Retry accepts only our exact two policies.
if any(p['name'] not in [BASELINE,READER] for p in policies):raise RuntimeError("admin policy baseline changed")
for p in policies:
expected=({"adminuser":["*","!"+USER],"realm":[],"action":{"*":True}} if p['name']==BASELINE else {"adminuser":[USER],"realm":[REALM],"action":{"tokenlist":True}})
if any(p.get(k)!=v for k,v in expected.items()):raise RuntimeError("existing managed policy differs")
existing={a.username for a in get_db_admins()}
if USER in existing and not policies:raise RuntimeError("service name already belongs to another setup")
if BASELINE not in {p['name'] for p in policies}:
set_policy(name=BASELINE,scope="admin",action="*",adminuser=["*","!"+USER],description="Preserve default rights for existing administrative identities; exclude factor reader")
if READER not in {p['name'] for p in policies}:
set_policy(name=READER,scope="admin",action="tokenlist",adminuser=USER,realm=REALM,description="KeyCape factor listing in coulomb only")
if USER not in existing:create_db_admin(USER,password=password)
return token_result(password)
def main():
data=json.load(sys.stdin)
if data.get('username')!=USER or not isinstance(data.get('password'),str) or len(data['password'])<32:return 2
with contextlib.redirect_stdout(io.StringIO()),contextlib.redirect_stderr(io.StringIO()):
result=bootstrap(data['password']) if data.get('operation')=='bootstrap' else token_result(data['password'])
sys.stdout.write(json.dumps(result));return 0
if __name__=='__main__':
try:code=main()
except RuntimeError as exc:
sys.stdout.write(json.dumps({"failure":str(exc)}));code=1
except Exception as exc:
sys.stdout.write(json.dumps({"failure_type":type(exc).__name__}));code=1
raise SystemExit(code)

View file

@ -0,0 +1,74 @@
"""Dedicated Kubernetes renewal worker. Only sanitized outcome metadata is emitted."""
import base64,json,sys,time,urllib.request,urllib.error,urllib.parse
from pathlib import Path
BAO="http://openbao.openbao.svc.cluster.local:8200/v1"
PI="http://privacyidea.mfa.svc.cluster.local:8080"
ISSUER="platform/data/workloads/net-kingdom/keycape-factor-issuer"
TARGET="platform/data/workloads/net-kingdom/keycape-factor-read"
USER="keycape-factor-reader"
PROVENANCE="RPF-WP-0040"
class Failure(Exception):pass
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self,*args,**kwargs):return None
def http(url,body=None,headers=None,method=None,form=False):
data=None
headers=dict(headers or {})
if body is not None:
data=(urllib.parse.urlencode(body) if form else json.dumps(body)).encode()
headers["Content-Type"]="application/x-www-form-urlencoded" if form else "application/json"
req=urllib.request.Request(url,data=data,headers=headers,method=method)
try:
with urllib.request.build_opener(NoRedirect()).open(req,timeout=15) as r:
raw=r.read(1048577)
if len(raw)>1048576:raise Failure()
return r.status,json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:return e.code,{}
def success(code,data):
if code!=200 or not data.get("result",{}).get("status"):raise Failure()
return data["result"]["value"]
def run():
session=None;receipt={"success":False,"phase":"workload_login"}
try:
jwt=Path("/var/run/keycape-factor/token").read_text().strip()
code,body=http(BAO+"/auth/kubernetes/login",{"role":"keycape-factor-renewer","jwt":jwt})
if code!=200:raise Failure()
session=body["auth"]["client_token"];headers={"X-Vault-Token":session}
receipt["phase"]="issuer_custody"
code,body=http(BAO+"/"+ISSUER,headers=headers)
if code!=200:raise Failure()
issuer=body["data"]["data"]
if issuer.get("REQUEST")!=PROVENANCE or issuer.get("USERNAME")!=USER:raise Failure()
receipt["phase"]="provider_authentication"
value=success(*http(PI+"/auth",{"username":USER,"password":issuer["PASSWORD"]},form=True))
if value.get("role")!="admin" or value.get("username")!=USER:raise Failure()
token=value["token"];claims=json.loads(base64.urlsafe_b64decode(token.split('.')[1]+'==='));expiry=int(claims["exp"])
if not 300<expiry-time.time()<=7200:raise Failure()
receipt["phase"]="provider_scope"
provider_headers={"Authorization":token}
listing=success(*http(PI+"/token/?tokenrealm=coulomb&active=True&pagesize=1",headers=provider_headers))
if not isinstance(listing.get("tokens"),list) or not isinstance(listing.get("count"),int):raise Failure()
code,_=http(PI+"/policy/",headers=provider_headers)
if code not in (401,403):raise Failure()
receipt["phase"]="publish"
code,body=http(BAO+"/"+TARGET,headers=headers)
if code!=200 or body["data"]["data"].get("REQUEST")!=PROVENANCE:raise Failure()
version=body["data"]["metadata"]["version"]
code,_=http(BAO+"/"+TARGET,{"options":{"cas":version},"data":{"TOKEN":token,"EXPIRES_AT":expiry,"REQUEST":PROVENANCE}},headers=headers)
if code!=200:raise Failure()
code,body=http(BAO+"/"+TARGET,headers=headers)
if code!=200 or body["data"]["data"]["TOKEN"]!=token:raise Failure()
receipt.update(success=True,phase="complete",expires_at=expiry,kv_version=body["data"]["metadata"]["version"],cross_user_factor_visible=listing["count"]>0)
except Exception:pass
finally:
if session:
try:
code,_=http(BAO+"/auth/token/revoke-self",headers={"X-Vault-Token":session},method="POST")
receipt["session_revoked"]=code in (200,204)
except Exception:receipt["session_revoked"]=False
if not receipt["session_revoked"]:receipt["success"]=False;receipt["phase"]="session_cleanup"
return receipt
if __name__=="__main__":
receipt=run();print(json.dumps(receipt));raise SystemExit(0 if receipt["success"] else 1)

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""Exact reviewed renewal metadata; owner-admin operation, not generic KV-read delegation."""
import argparse,json,os
from pathlib import Path
from keycape_factor_metadata import call,normalized,policy_text
ROOT=Path(__file__).resolve().parents[1]
NAME="keycape-factor-renewer"
ROLE={"bound_service_account_names":[NAME],"bound_service_account_namespaces":["sso"],"audience":"openbao","token_policies":[NAME],"token_ttl":300,"token_max_ttl":300,"token_no_default_policy":True}
def execute():
policy=(ROOT/"openbao/policies"/(NAME+".hcl")).read_text()
existing=call("policy","list","-format=json")
if NAME in existing:
if normalized(policy_text(call("policy","read","-format=json",NAME)))!=normalized(policy):raise RuntimeError("existing renewal policy differs")
else:call("write","-format=json","sys/policies/acl/"+NAME,"-",payload={"policy":policy})
roles=call("list","-format=json","auth/kubernetes/role")
names=roles if isinstance(roles,list) else roles.get("data",{}).get("keys",[])
path="auth/kubernetes/role/"+NAME
if NAME in names:
data=call("read","-format=json",path)["data"]
if any(data.get(k)!=v for k,v in ROLE.items()):raise RuntimeError("existing renewal role differs")
else:call("write","-format=json",path,"-",payload=ROLE)
data=call("read","-format=json",path)["data"]
if any(data.get(k)!=v for k,v in ROLE.items()):raise RuntimeError("renewal role readback failed")
if normalized(policy_text(call("policy","read","-format=json",NAME)))!=normalized(policy):raise RuntimeError("renewal policy readback failed")
return {"success":True,"policy":NAME,"role":path,"token_max_ttl":300,"secret_values_read":False,"secret_values_written":False}
def main():
p=argparse.ArgumentParser();p.add_argument("--receipt",type=Path,required=True);a=p.parse_args()
if any(os.environ.get(k) for k in ["VAULT_TOKEN","BAO_TOKEN","OPENBAO_TOKEN"]):return 2
fd=os.open(a.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600);result={"success":False}
try:result=execute();return 0
except RuntimeError as e:result["failure"]=str(e);return 1
except Exception as e:result["failure_type"]=type(e).__name__;return 1
finally:os.write(fd,json.dumps(result,indent=2).encode());os.close(fd)
if __name__=="__main__":raise SystemExit(main())