Deploy KeyCape-backed portal login edge
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

This commit is contained in:
tegwick 2026-07-28 00:39:22 +02:00
parent eac9d655b0
commit 9a486c3531
5 changed files with 145 additions and 2 deletions

View file

@ -54,7 +54,7 @@ spec:
# 2026-05-24: direct-imported into railiance01 k3s for the
# bootstrap-console OIDC/MFA rollout. Use IfNotPresent while the
# 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
ports:

View 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()

View 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}]

View file

@ -45,7 +45,7 @@ spec:
seccompProfile: {type: RuntimeDefault}
containers:
- name: portal
image: user-engine:portal-c27012a
image: user-engine:portal-268b315
imagePullPolicy: Never
ports: [{name: http, containerPort: 8080}]
env:
@ -58,6 +58,9 @@ spec:
- {name: USER_ENGINE_LOGIN_URL, 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_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"}
securityContext:
allowPrivilegeEscalation: false
@ -108,6 +111,12 @@ spec:
- podSelector:
matchLabels: {cnpg.io/cluster: user-engine-pg}
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:
- namespaceSelector:
matchLabels: {kubernetes.io/metadata.name: kube-system}