Add service accounts and operator mode
Service accounts (SERVICE-ACCOUNTS.md) are a scoped, non-human credential: not a users row, so they never touch OIDC sync, login or the is_admin flag. Instance scope can create a team and mint a team-scoped account for it; team scope is owner-equivalent for that one team and nothing else. This is what unblocks terdut-operator's DESIGN.md §6 — no more impersonating a human admin, and a real rotation story instead of the unworkable delete-and-re-bootstrap /api/bootstrap can't actually do. - migration 014: service_accounts + service_account_keys - POST /api/service-accounts, POST/DELETE .../keys, GET ?name= self-lookup - AuthMiddleware resolves a tdsa_-prefixed key to a distinct principal; a team-scoped account gets a synthetic single membership so requireTeamMember/requireTeamOwner work on it unmodified - handleCreateTeam accepts an instance-scoped caller; the team it creates has no human owner, which is the expected shape for one an operator is about to hand a team-scoped credential to Operator mode (TERDUT_OPERATOR_MODE / values.operatorMode) declares an install gitops-managed: session and user-API-key writes to teams, escalation policies, dead man's switches and integrations get 403 reason=operator_managed, while a service account's writes still go through. Team membership/invites and the schedule are deliberately left out — never gitops-managed by design, and still human day-to-day work. /api/auth/config reports operator_mode so the web UI can grey these sections out from the start rather than only after a write fails. Also: GET /api/version (both terdut-tui and terdut-operator currently detect server capability by route-probing; this gives them a real answer), and a PUT for dead man's switches so a reconciler can update one in place instead of deleting and recreating it.
This commit is contained in:
+10
-1
@@ -66,9 +66,18 @@ func handleAuthConfig(cfg config.Config) http.HandlerFunc {
|
||||
// DeviceLogin is whether a client that cannot open a browser (the TUI)
|
||||
// can sign in by showing a code, through /api/oidc/device.
|
||||
DeviceLogin bool `json:"device_login"`
|
||||
|
||||
// OperatorMode is whether this install is gitops-managed: writes to
|
||||
// teams, escalation policies, dead man's switches and integrations
|
||||
// from a session or a user's own API key are refused (OperatorModeBlock),
|
||||
// though a service account's are not. The web UI reads this before
|
||||
// anybody signs in, the same way it reads PasswordLogin/OIDC, so it can
|
||||
// show those sections read-only from the start rather than only after
|
||||
// a write fails.
|
||||
OperatorMode bool `json:"operator_mode"`
|
||||
}
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
resp := response{PasswordLogin: !cfg.DisablePasswordLogin}
|
||||
resp := response{PasswordLogin: !cfg.DisablePasswordLogin, OperatorMode: cfg.OperatorMode}
|
||||
if cfg.OIDC.Enabled() {
|
||||
resp.OIDC = oidcInfo{Enabled: true, Name: cfg.OIDC.Name}
|
||||
resp.DeviceLogin = true
|
||||
|
||||
Reference in New Issue
Block a user