Deploy KeyCape-backed portal login edge
This commit is contained in:
parent
eac9d655b0
commit
9a486c3531
5 changed files with 145 additions and 2 deletions
|
|
@ -54,7 +54,7 @@ spec:
|
||||||
# 2026-05-24: direct-imported into railiance01 k3s for the
|
# 2026-05-24: direct-imported into railiance01 k3s for the
|
||||||
# bootstrap-console OIDC/MFA rollout. Use IfNotPresent while the
|
# bootstrap-console OIDC/MFA rollout. Use IfNotPresent while the
|
||||||
# HTTP registry push/pull path is being cleaned up.
|
# HTTP registry push/pull path is being cleaned up.
|
||||||
image: 92.205.130.254:32166/coulomb/key-cape:main-e877d27-2
|
image: key-cape:e8b4ede
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
|
|
|
||||||
52
sso-mfa/k8s/keycape/register-user-engine-portal.py
Normal file
52
sso-mfa/k8s/keycape/register-user-engine-portal.py
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Idempotently register the user-engine portal in the live KeyCape Secret.
|
||||||
|
|
||||||
|
The complete Secret travels over stdin/stdout between kubectl and this
|
||||||
|
process. Secret values are never printed to the terminal or written to disk.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
CLIENT_ID = "user-engine-portal"
|
||||||
|
CLIENT = {
|
||||||
|
"clientId": CLIENT_ID,
|
||||||
|
"displayName": "User Engine Portal",
|
||||||
|
"redirectUris": ["https://users.92-205-62-239.nip.io/oidc/callback"],
|
||||||
|
"allowedScopes": ["openid", "profile", "email", "groups"],
|
||||||
|
"grantTypes": ["authorization_code"],
|
||||||
|
"clientType": "public",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
secret = json.load(sys.stdin)
|
||||||
|
encoded = secret.get("data", {}).get("config.yaml")
|
||||||
|
if not encoded:
|
||||||
|
raise SystemExit("keycape-config does not contain config.yaml")
|
||||||
|
config = yaml.safe_load(base64.b64decode(encoded))
|
||||||
|
clients = config.setdefault("clients", [])
|
||||||
|
existing = next(
|
||||||
|
(index for index, client in enumerate(clients) if client.get("clientId") == CLIENT_ID),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if existing is None:
|
||||||
|
clients.append(CLIENT)
|
||||||
|
else:
|
||||||
|
clients[existing] = CLIENT
|
||||||
|
rendered = yaml.safe_dump(config, sort_keys=False).encode()
|
||||||
|
secret["data"]["config.yaml"] = base64.b64encode(rendered).decode()
|
||||||
|
secret.pop("status", None)
|
||||||
|
metadata = secret.get("metadata", {})
|
||||||
|
for key in ("creationTimestamp", "managedFields", "resourceVersion", "uid"):
|
||||||
|
metadata.pop(key, None)
|
||||||
|
json.dump(secret, sys.stdout, separators=(",", ":"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
74
sso-mfa/k8s/user-engine/ingress.yaml
Normal file
74
sso-mfa/k8s/user-engine/ingress.yaml
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: user-engine-security
|
||||||
|
namespace: user-engine
|
||||||
|
spec:
|
||||||
|
chain:
|
||||||
|
middlewares:
|
||||||
|
- name: user-engine-rate-limit
|
||||||
|
- name: user-engine-headers
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: user-engine-rate-limit
|
||||||
|
namespace: user-engine
|
||||||
|
spec:
|
||||||
|
rateLimit:
|
||||||
|
average: 60
|
||||||
|
period: 1m
|
||||||
|
burst: 20
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: user-engine-headers
|
||||||
|
namespace: user-engine
|
||||||
|
spec:
|
||||||
|
headers:
|
||||||
|
stsSeconds: 31536000
|
||||||
|
stsIncludeSubdomains: true
|
||||||
|
contentTypeNosniff: true
|
||||||
|
frameDeny: true
|
||||||
|
referrerPolicy: no-referrer
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: user-engine
|
||||||
|
namespace: user-engine
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
traefik.ingress.kubernetes.io/router.middlewares: user-engine-user-engine-security@kubernetescrd
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: users.92-205-62-239.nip.io
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: user-engine
|
||||||
|
port: {number: 8080}
|
||||||
|
tls:
|
||||||
|
- secretName: user-engine-tls
|
||||||
|
hosts: [users.92-205-62-239.nip.io]
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: allow-acme-http01
|
||||||
|
namespace: user-engine
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
acme.cert-manager.io/http01-solver: "true"
|
||||||
|
policyTypes: [Ingress]
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: kube-system}
|
||||||
|
ports: [{protocol: TCP, port: 8089}]
|
||||||
|
|
@ -45,7 +45,7 @@ spec:
|
||||||
seccompProfile: {type: RuntimeDefault}
|
seccompProfile: {type: RuntimeDefault}
|
||||||
containers:
|
containers:
|
||||||
- name: portal
|
- name: portal
|
||||||
image: user-engine:portal-c27012a
|
image: user-engine:portal-268b315
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports: [{name: http, containerPort: 8080}]
|
ports: [{name: http, containerPort: 8080}]
|
||||||
env:
|
env:
|
||||||
|
|
@ -58,6 +58,9 @@ spec:
|
||||||
- {name: USER_ENGINE_LOGIN_URL, value: "https://kc.coulomb.social/"}
|
- {name: USER_ENGINE_LOGIN_URL, value: "https://kc.coulomb.social/"}
|
||||||
- {name: USER_ENGINE_OIDC_ISSUER, value: "https://kc.coulomb.social"}
|
- {name: USER_ENGINE_OIDC_ISSUER, value: "https://kc.coulomb.social"}
|
||||||
- {name: USER_ENGINE_OIDC_AUDIENCE, value: user-engine-portal}
|
- {name: USER_ENGINE_OIDC_AUDIENCE, value: user-engine-portal}
|
||||||
|
- {name: USER_ENGINE_OIDC_CLIENT_ID, value: user-engine-portal}
|
||||||
|
- {name: USER_ENGINE_OIDC_REDIRECT_URI, value: "https://users.92-205-62-239.nip.io/oidc/callback"}
|
||||||
|
- {name: USER_ENGINE_OIDC_BACKEND_URL, value: "http://keycape.sso.svc.cluster.local:8080"}
|
||||||
- {name: USER_ENGINE_PUBLIC_REGISTRATION, value: "false"}
|
- {name: USER_ENGINE_PUBLIC_REGISTRATION, value: "false"}
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
|
|
@ -108,6 +111,12 @@ spec:
|
||||||
- podSelector:
|
- podSelector:
|
||||||
matchLabels: {cnpg.io/cluster: user-engine-pg}
|
matchLabels: {cnpg.io/cluster: user-engine-pg}
|
||||||
ports: [{protocol: TCP, port: 5432}]
|
ports: [{protocol: TCP, port: 5432}]
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: sso}
|
||||||
|
podSelector:
|
||||||
|
matchLabels: {app.kubernetes.io/name: keycape}
|
||||||
|
ports: [{protocol: TCP, port: 8080}]
|
||||||
- to:
|
- to:
|
||||||
- namespaceSelector:
|
- namespaceSelector:
|
||||||
matchLabels: {kubernetes.io/metadata.name: kube-system}
|
matchLabels: {kubernetes.io/metadata.name: kube-system}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,14 @@ provide safe password and MFA enrollment/recovery handoffs. Preserve
|
||||||
platform-root separation and ensure tenant administration never implies
|
platform-root separation and ensure tenant administration never implies
|
||||||
platform authority.
|
platform authority.
|
||||||
|
|
||||||
|
2026-07-27 implementation evidence: `user-engine-portal` is registered as a
|
||||||
|
public static KeyCape client with an exact callback and S256 PKCE. The portal
|
||||||
|
is live at `https://users.92-205-62-239.nip.io`, has a trusted ACME
|
||||||
|
certificate, begins the KeyCape/Authelia flow, and rejects an unregistered
|
||||||
|
callback. KeyCape image `key-cape:e8b4ede` maps an unambiguous
|
||||||
|
`tenant:<kind>:<slug>:users|admins` directory group envelope into the tenant
|
||||||
|
and coarse tenant-admin claims while refusing ambiguous multi-tenant mapping.
|
||||||
|
|
||||||
## T04 - Integrate authorization, email, audit, and events
|
## T04 - Integrate authorization, email, audit, and events
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue