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
267
manifests/keycape-factor-custody.yaml
Normal file
267
manifests/keycape-factor-custody.yaml
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: keycape-factor-eso
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: keycape-factor-renewer
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: openbao-keycape-factor-read
|
||||
spec:
|
||||
conditions:
|
||||
- namespaces:
|
||||
- sso
|
||||
provider:
|
||||
vault:
|
||||
server: http://openbao.openbao.svc.cluster.local:8200
|
||||
path: platform
|
||||
version: v2
|
||||
auth:
|
||||
kubernetes:
|
||||
mountPath: kubernetes
|
||||
role: keycape-factor-workload-kv-read
|
||||
serviceAccountRef:
|
||||
name: keycape-factor-eso
|
||||
namespace: sso
|
||||
audiences:
|
||||
- openbao
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: keycape-factor-read
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
spec:
|
||||
refreshInterval: 1m
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: openbao-keycape-factor-read
|
||||
target:
|
||||
name: keycape-factor-read
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: admin-token
|
||||
remoteRef:
|
||||
key: workloads/net-kingdom/keycape-factor-read
|
||||
property: TOKEN
|
||||
- secretKey: expires-at
|
||||
remoteRef:
|
||||
key: workloads/net-kingdom/keycape-factor-read
|
||||
property: EXPIRES_AT
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: keycape-factor-renewer-2cf770000e
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
immutable: true
|
||||
data:
|
||||
renew.py: "\"\"\"Dedicated Kubernetes renewal worker. Only sanitized outcome metadata\
|
||||
\ is emitted.\"\"\"\nimport base64,json,sys,time,urllib.request,urllib.error,urllib.parse\n\
|
||||
from pathlib import Path\nBAO=\"http://openbao.openbao.svc.cluster.local:8200/v1\"\
|
||||
\nPI=\"http://privacyidea.mfa.svc.cluster.local:8080\"\nISSUER=\"platform/data/workloads/net-kingdom/keycape-factor-issuer\"\
|
||||
\nTARGET=\"platform/data/workloads/net-kingdom/keycape-factor-read\"\nUSER=\"\
|
||||
keycape-factor-reader\"\nPROVENANCE=\"RPF-WP-0040\"\nclass Failure(Exception):pass\n\
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):\n def redirect_request(self,*args,**kwargs):return\
|
||||
\ None\n\ndef http(url,body=None,headers=None,method=None,form=False):\n data=None\n\
|
||||
\ headers=dict(headers or {})\n if body is not None:\n data=(urllib.parse.urlencode(body)\
|
||||
\ if form else json.dumps(body)).encode()\n headers[\"Content-Type\"]=\"application/x-www-form-urlencoded\"\
|
||||
\ if form else \"application/json\"\n req=urllib.request.Request(url,data=data,headers=headers,method=method)\n\
|
||||
\ try:\n with urllib.request.build_opener(NoRedirect()).open(req,timeout=15)\
|
||||
\ as r:\n raw=r.read(1048577)\n if len(raw)>1048576:raise Failure()\n return\
|
||||
\ r.status,json.loads(raw) if raw else {}\n except urllib.error.HTTPError as e:return\
|
||||
\ e.code,{}\n\ndef success(code,data):\n if code!=200 or not data.get(\"result\"\
|
||||
,{}).get(\"status\"):raise Failure()\n return data[\"result\"][\"value\"]\n\n\
|
||||
def run():\n session=None;receipt={\"success\":False,\"phase\":\"workload_login\"\
|
||||
}\n try:\n jwt=Path(\"/var/run/keycape-factor/token\").read_text().strip()\n\
|
||||
\ code,body=http(BAO+\"/auth/kubernetes/login\",{\"role\":\"keycape-factor-renewer\"\
|
||||
,\"jwt\":jwt})\n if code!=200:raise Failure()\n session=body[\"auth\"][\"client_token\"\
|
||||
];headers={\"X-Vault-Token\":session}\n receipt[\"phase\"]=\"issuer_custody\"\
|
||||
\n code,body=http(BAO+\"/\"+ISSUER,headers=headers)\n if code!=200:raise Failure()\n\
|
||||
\ issuer=body[\"data\"][\"data\"]\n if issuer.get(\"REQUEST\")!=PROVENANCE or\
|
||||
\ issuer.get(\"USERNAME\")!=USER:raise Failure()\n receipt[\"phase\"]=\"provider_authentication\"\
|
||||
\n value=success(*http(PI+\"/auth\",{\"username\":USER,\"password\":issuer[\"\
|
||||
PASSWORD\"]},form=True))\n if value.get(\"role\")!=\"admin\" or value.get(\"\
|
||||
username\")!=USER:raise Failure()\n token=value[\"token\"];claims=json.loads(base64.urlsafe_b64decode(token.split('.')[1]+'==='));expiry=int(claims[\"\
|
||||
exp\"])\n if not 300<expiry-time.time()<=7200:raise Failure()\n receipt[\"phase\"\
|
||||
]=\"provider_scope\"\n provider_headers={\"Authorization\":token}\n listing=success(*http(PI+\"\
|
||||
/token/?tokenrealm=coulomb&active=True&pagesize=1\",headers=provider_headers))\n\
|
||||
\ if not isinstance(listing.get(\"tokens\"),list) or not isinstance(listing.get(\"\
|
||||
count\"),int):raise Failure()\n code,_=http(PI+\"/policy/\",headers=provider_headers)\n\
|
||||
\ if code not in (401,403):raise Failure()\n receipt[\"phase\"]=\"publish\"\n\
|
||||
\ code,body=http(BAO+\"/\"+TARGET,headers=headers)\n if code!=200 or body[\"\
|
||||
data\"][\"data\"].get(\"REQUEST\")!=PROVENANCE:raise Failure()\n version=body[\"\
|
||||
data\"][\"metadata\"][\"version\"]\n code,_=http(BAO+\"/\"+TARGET,{\"options\"\
|
||||
:{\"cas\":version},\"data\":{\"TOKEN\":token,\"EXPIRES_AT\":expiry,\"REQUEST\"\
|
||||
:PROVENANCE}},headers=headers)\n if code!=200:raise Failure()\n code,body=http(BAO+\"\
|
||||
/\"+TARGET,headers=headers)\n if code!=200 or body[\"data\"][\"data\"][\"TOKEN\"\
|
||||
]!=token:raise Failure()\n receipt.update(success=True,phase=\"complete\",expires_at=expiry,kv_version=body[\"\
|
||||
data\"][\"metadata\"][\"version\"],cross_user_factor_visible=listing[\"count\"\
|
||||
]>0)\n except Exception:pass\n finally:\n if session:\n try:\n code,_=http(BAO+\"\
|
||||
/auth/token/revoke-self\",headers={\"X-Vault-Token\":session},method=\"POST\"\
|
||||
)\n receipt[\"session_revoked\"]=code in (200,204)\n except Exception:receipt[\"\
|
||||
session_revoked\"]=False\n if not receipt[\"session_revoked\"]:receipt[\"success\"\
|
||||
]=False;receipt[\"phase\"]=\"session_cleanup\"\n return receipt\nif __name__==\"\
|
||||
__main__\":\n receipt=run();print(json.dumps(receipt));raise SystemExit(0 if receipt[\"\
|
||||
success\"] else 1)\n"
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: keycape-factor-renewer
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
spec:
|
||||
schedule: '*/10 * * * *'
|
||||
suspend: false
|
||||
concurrencyPolicy: Forbid
|
||||
startingDeadlineSeconds: 120
|
||||
successfulJobsHistoryLimit: 2
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
activeDeadlineSeconds: 120
|
||||
backoffLimit: 1
|
||||
ttlSecondsAfterFinished: 3600
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: keycape-factor-renewer
|
||||
spec:
|
||||
serviceAccountName: keycape-factor-renewer
|
||||
automountServiceAccountToken: false
|
||||
restartPolicy: Never
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65534
|
||||
runAsGroup: 65534
|
||||
fsGroup: 65534
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: renew
|
||||
image: python:3.12-slim@sha256:d764629ce0ddd8c71fd371e9901efb324a95789d2315a47db7e4d27e78f1b0e9
|
||||
command:
|
||||
- python3
|
||||
- /worker/renew.py
|
||||
env:
|
||||
- name: PYTHONDONTWRITEBYTECODE
|
||||
value: '1'
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 24Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 64Mi
|
||||
volumeMounts:
|
||||
- name: worker
|
||||
mountPath: /worker
|
||||
readOnly: true
|
||||
- name: identity
|
||||
mountPath: /var/run/keycape-factor
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: worker
|
||||
configMap:
|
||||
name: keycape-factor-renewer-2cf770000e
|
||||
- name: identity
|
||||
projected:
|
||||
defaultMode: 288
|
||||
sources:
|
||||
- serviceAccountToken:
|
||||
path: token
|
||||
audience: openbao
|
||||
expirationSeconds: 600
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: keycape-factor-renewer
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: keycape-factor-renewer
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress: []
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: openbao
|
||||
ports:
|
||||
- port: 8200
|
||||
protocol: TCP
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: mfa
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: privacyidea
|
||||
ports:
|
||||
- port: 8080
|
||||
protocol: TCP
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-keycape-factor-renewal
|
||||
namespace: mfa
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: privacyidea
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: sso
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: keycape-factor-renewer
|
||||
ports:
|
||||
- port: 8080
|
||||
protocol: TCP
|
||||
Loading…
Add table
Add a link
Reference in a new issue