# Deployment + Service — KeyCape (namespace: sso) # # KeyCape orchestrates OIDC. Pending logins and authorization codes are process-local; # use one replica with Recreate during replacement. Persistent identity state remains # in Authelia, LLDAP and privacyIDEA. No PVC is required. # # Configuration is stored entirely in the keycape-config Secret, which holds # a complete config.yaml and the RSA private key used to sign OIDC tokens # issued to downstream applications. # # Prerequisites (apply in order): # 1. keycape-config Secret — run keycape/create-secrets.sh # 2. keycape-pi-token Secret — run keycape/create-pi-token.sh (after T04 bootstrap) # 3. This file # 4. middleware.yaml + ingress.yaml # # Container image: # KeyCape publishes immutable main- tags through Forgejo Actions. # K3s pulls through the HTTPS Forgejo registry origin. apiVersion: apps/v1 kind: Deployment metadata: name: keycape namespace: sso labels: app.kubernetes.io/name: keycape app.kubernetes.io/part-of: net-kingdom-sso-mfa net-kingdom/component: sso spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: keycape strategy: type: Recreate # one issuer instance; process-local login/code state template: metadata: labels: app.kubernetes.io/name: keycape app.kubernetes.io/part-of: net-kingdom-sso-mfa net-kingdom/component: sso spec: securityContext: runAsNonRoot: true runAsUser: 65534 # nobody — matches distroless static image fsGroup: 65534 containers: - name: keycape # Image published to the self-hosted Forgejo OCI registry (KEY-WP-0002). # KEY-WP-0012: canonical OIDC subject resolution for /userinfo. image: forgejo.coulomb.social/coulomb/key-cape@sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8080 protocol: TCP env: - name: KEYCAPE_CONFIG value: /etc/keycape/config.yaml - name: KEYCAPE_RAPP_QONTO_CLIENT_SECRET valueFrom: secretKeyRef: name: keycape-rapp-qonto-client key: client-secret - name: KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET valueFrom: secretKeyRef: name: keycape-secrets-engine-approval-client key: client-secret - name: KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET valueFrom: secretKeyRef: name: keycape-approval-engine-operator-client key: client-secret volumeMounts: # keycape-config Secret provides config.yaml and key.pem - name: config-secret mountPath: /etc/keycape readOnly: true startupProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 3 periodSeconds: 3 failureThreshold: 10 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 0 periodSeconds: 15 failureThreshold: 3 readinessProbe: httpGet: path: /readyz port: 8080 initialDelaySeconds: 0 periodSeconds: 10 failureThreshold: 3 resources: requests: cpu: "25m" memory: "32Mi" limits: cpu: "200m" memory: "128Mi" volumes: - name: config-secret secret: secretName: keycape-config # Secret must contain two keys: config.yaml and key.pem items: - key: config.yaml path: config.yaml - key: key.pem path: key.pem mode: 0400 # key.pem is sensitive; restrict to owner read only --- # Service — ClusterIP; Traefik reaches KeyCape via port 8080. apiVersion: v1 kind: Service metadata: name: keycape namespace: sso labels: app.kubernetes.io/name: keycape app.kubernetes.io/part-of: net-kingdom-sso-mfa net-kingdom/component: sso spec: type: ClusterIP selector: app.kubernetes.io/name: keycape ports: - name: http port: 8080 targetPort: 8080 protocol: TCP