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
-8
View File
@@ -1,8 +0,0 @@
## Append samples of your project ##
resources:
- terdut_v1alpha1_terdutserver.yaml
- terdut_v1alpha1_terdutteam.yaml
- terdut_v1alpha1_terdutescalationrule.yaml
- terdut_v1alpha1_terdutdeadmanswitch.yaml
- terdut_v1alpha1_terdutalertsource.yaml
# +kubebuilder:scaffold:manifestskustomizesamples
@@ -1,19 +0,0 @@
apiVersion: terdut.ryuvia.com/v1alpha1
kind: TerdutAlertSource
metadata:
labels:
app.kubernetes.io/name: terdut-operator
app.kubernetes.io/managed-by: kustomize
name: terdutalertsource-sample
spec:
teamRef:
name: terdutteam-sample
# kind defaults to "alertmanager" -- the only value terdut-server
# supports today. Changing it after this object exists rotates the
# webhook key (DESIGN.md §5): the old integration is deleted and a new
# one created, which breaks whatever still sends to the old URL.
kind: alertmanager
# name is this source's own display name server-side, distinct from this
# object's own metadata.name above -- renaming it is safe and never
# rotates the key.
name: prod-alertmanager
@@ -1,15 +0,0 @@
apiVersion: terdut.ryuvia.com/v1alpha1
kind: TerdutDeadmanSwitch
metadata:
labels:
app.kubernetes.io/name: terdut-operator
app.kubernetes.io/managed-by: kustomize
name: terdutdeadmanswitch-sample
spec:
teamRef:
name: terdutteam-sample
# name is optional -- left empty, terdut-server derives it from matcher's
# own canonical form (DESIGN.md §4.4).
matcher: "alertname=Watchdog"
timeout: 15m
severity: critical
@@ -1,25 +0,0 @@
apiVersion: terdut.ryuvia.com/v1alpha1
kind: TerdutEscalationRule
metadata:
labels:
app.kubernetes.io/name: terdut-operator
app.kubernetes.io/managed-by: kustomize
name: terdutescalationrule-sample
spec:
# One per team (DESIGN.md §4.3) -- a second TerdutEscalationRule naming
# the same teamRef would simply clobber this one every reconcile, since
# there's no admission-time check for it in v1.
teamRef:
name: terdutteam-sample
repeatCount: 2
fallbackTopic: platform-fallback
levels:
# username is required iff kind is "user", and rejected otherwise --
# enforced at apply time via CEL (api/v1alpha1/terdutescalationrule_types.go).
- timeout: 5m
targets:
- kind: user
username: alice
- timeout: 10m
targets:
- kind: oncall
@@ -1,42 +0,0 @@
apiVersion: terdut.ryuvia.com/v1alpha1
kind: TerdutServer
metadata:
labels:
app.kubernetes.io/name: terdut-operator
app.kubernetes.io/managed-by: kustomize
name: terdutserver-sample
spec:
image:
repository: git.ryuvia.com/niklas/terdut-server
tag: v0.20.0
replicas: 1
networking:
hostname: terdut.example.com
servicePort: 8080
# Bring-your-own DSN (simplest path, no external CRD dependency). For the
# Zalando postgres-operator path instead, use:
# database:
# postgresClusterRef:
# name: terdut-postgres
database:
dsn: "postgres://terdut@terdut-postgres:5432/terdut?sslmode=require"
passwordSecretRef:
name: terdut-postgres-password
key: password
sweeper:
staleAfter: 6h
archiveAfter: 168h
deadman:
matchers: "alertname=Watchdog"
timeout: 15m
severity: critical
passwordLogin: true
# Pod-level customization, all optional -- see PodSpec in
# api/v1alpha1/terdutserver_types.go for the full shape (tolerations,
# affinity, topologySpreadConstraints, securityContext,
# serviceAccountName, extraEnv/extraVolumes, imagePullSecrets,
# disruptionBudget, ...). Example:
# pod:
# resources:
# requests: {cpu: 100m, memory: 128Mi}
# limits: {memory: 256Mi}
@@ -1,20 +0,0 @@
apiVersion: terdut.ryuvia.com/v1alpha1
kind: TerdutTeam
metadata:
labels:
app.kubernetes.io/name: terdut-operator
app.kubernetes.io/managed-by: kustomize
name: terdutteam-sample
spec:
# serverRef.namespace is optional, defaulting to this TerdutTeam's own
# namespace (the common case). Set it only to reference a TerdutServer in
# a different namespace -- which needs that TerdutServer's own
# spec.allowedTeams to admit this namespace (DESIGN.md §4.6), otherwise
# this reports Ready: False, reason: RefNotPermitted.
serverRef:
name: terdutserver-sample
displayName: Platform
# oidc is optional -- omit entirely for a password-login-only install.
# oidc:
# memberGroup: terdut-platform-members
# ownerGroup: terdut-platform-owners