Authenticate with a seeded operator key; fold escalation and switches into TerdutTeam

Credentials: the TerdutServer controller generates <name>-operator-key in
the server's own namespace (owned by it) and hands it to the pods as
TERDUT_OPERATOR_KEY; the server creates its instance-scoped account from it
at every start. A replaced Secret rolls the pods. The bootstrap handshake,
the checkpoint Secret, per-team service accounts and credentials Secrets,
BootstrapStateLost and credentials.deletionPolicy are gone.

CRDs: TerdutServer, TerdutTeam and TerdutAlertSource. TerdutEscalationRule
and TerdutDeadmanSwitch become spec.escalation and spec.deadmanSwitches[]
on the team (matched by name, extras removed); team invites are removed.
A team is created under the identity <namespace>/<name> (external_id), so a
retry, a lost status or a deleted team heal by repeating the same call, and
a display name owned by another team is TeamNameTaken instead of an
adoption. The server resolves escalation usernames (UnknownUser condition).
OIDC claim names and trustEmail are spec fields.

Fixes: query values are URL-escaped; every delete treats 404 as success;
deleting a team no longer depends on allowedTeams consent; a switch or
integration deleted on the server is recreated; unnamed switches take the
CR's name.

Cleanup: scaffold e2e test, AGENTS.md, devcontainer, unused config/ pieces
and Client.Version() removed; DESIGN.md, README, ROADMAP and the demo
(run-demo.sh, manifests) rewritten for the new design. Secret RBAC stays
cluster-wide, now stated in DESIGN.md section 9.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
This commit is contained in:
Niklas Ye
2026-10-09 14:56:22 +02:00
parent b0a431f2a4
commit e1103f2b7d
92 changed files with 2082 additions and 7561 deletions
@@ -125,24 +125,6 @@ spec:
x-kubernetes-map-type: atomic
type: object
type: object
credentials:
description: |-
credentials: what happens to the instance credential this operator
generates for the server.
properties:
deletionPolicy:
default: Retain
description: |-
deletionPolicy: whether the instance credential Secret is kept
(Retain, the default) or removed (Delete) when this TerdutServer is
deleted. A kept Secret is only ever adopted after the server accepts
its token, so one left over from a database that has since been reset
is ignored and replaced.
enum:
- Retain
- Delete
type: string
type: object
database:
description: |-
DatabaseSpec is the Postgres connection this TerdutServer uses. Exactly
@@ -193,19 +175,6 @@ spec:
- message: exactly one of dsn or postgresClusterRef must be set
rule: '(has(self.dsn) ? 1 : 0) + (has(self.postgresClusterRef) ?
1 : 0) == 1'
deadman:
description: |-
DeadmanSpec controls dead man's switch alerts. Matchers/Timeout/Severity
map straight to TERDUT_DEADMAN_MATCHERS/TERDUT_DEADMAN_TIMEOUT/
TERDUT_DEADMAN_SEVERITY.
properties:
matchers:
type: string
severity:
type: string
timeout:
type: string
type: object
image:
description: ImageSpec is the terdut-server image to run.
properties:
@@ -277,12 +246,7 @@ spec:
type: object
type: object
oidc:
description: |-
OIDCSpec controls single sign-on. Fields the chart also exposes but
DESIGN.md's spec doesn't (usernameClaim, emailClaim, groupsClaim,
trustEmail) use terdut-server's own defaults
(preferred_username/email/groups/false) rather than being added here
speculatively.
description: OIDCSpec controls single sign-on.
properties:
adminGroup:
type: string
@@ -294,12 +258,11 @@ spec:
type: string
clientSecretRef:
description: |-
SecretKeyRef names one data key inside a Secret. Every use of this type in
TerdutServerSpec resolves in the TerdutServer's own namespace (it's wired
straight into the Deployment's pod spec as a secretKeyRef env source,
which Kubernetes itself only allows same-namespace) -- unlike the
generated credentials Secret (DESIGN.md §6), which always lives in the
operator's own namespace and is never referenced through this type.
SecretKeyRef names one data key inside a Secret in the TerdutServer's own
namespace. Every use of this type is wired into the Deployment's pod spec as
a secretKeyRef env source, which Kubernetes only allows same-namespace --
including status.credentialsSecretRef, the operator key the controller
generates there.
properties:
key:
description: key is the data key inside the Secret holding
@@ -314,8 +277,14 @@ spec:
- key
- name
type: object
emailClaim:
default: email
type: string
enabled:
type: boolean
groupsClaim:
default: groups
type: string
issuer:
type: string
name:
@@ -327,6 +296,17 @@ spec:
sessionMaxAge:
default: 12h
type: string
trustEmail:
description: |-
trustEmail links a sign-in to an existing local user by email even when
the provider does not vouch the address is verified (Authentik reports
email_verified false unless told otherwise).
type: boolean
usernameClaim:
default: preferred_username
description: usernameClaim, emailClaim and groupsClaim name the
ID token claims read.
type: string
type: object
passwordLogin:
default: true
@@ -4414,10 +4394,9 @@ spec:
x-kubernetes-list-type: map
credentialsSecretRef:
description: |-
credentialsSecretRef is the generated instance-scoped credential
(DESIGN.md §6) -- pure output, always in the operator's own
namespace, under a fixed data key ("token"). Set only once
Bootstrapped is True.
credentialsSecretRef is the operator key this controller generated for
the server (TERDUT_OPERATOR_KEY): pure output, in the TerdutServer's own
namespace and owned by it, under the data key "token".
properties:
key:
description: key is the data key inside the Secret holding the
@@ -4439,12 +4418,6 @@ spec:
tells "applied" from "seen" (DESIGN.md §7).
format: int64
type: integer
serviceName:
description: |-
serviceName is the Service this controller created for the
Deployment, so other objects can reference it without recomputing the
naming convention.
type: string
type: object
required:
- spec