Establish scoped KeyCape factor custody and verified automatic renewal
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
This commit is contained in:
parent
b75729b799
commit
2e2c31d237
22 changed files with 1169 additions and 1 deletions
74
scripts/keycape_factor_renew.py
Normal file
74
scripts/keycape_factor_renew.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue