a4dd60f6b8
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.
38 lines
1.5 KiB
Go
38 lines
1.5 KiB
Go
package models
|
|
|
|
import "time"
|
|
|
|
// Service account scopes. Instance acts with the same reach system
|
|
// administration has over teams: create one, list them, mint a team-scoped
|
|
// account against any of them. Team acts as that one team's owner, and
|
|
// nothing else.
|
|
const (
|
|
ServiceAccountScopeInstance = "instance"
|
|
ServiceAccountScopeTeam = "team"
|
|
)
|
|
|
|
// ServiceAccount is a non-human credential: not a users row, so it never
|
|
// touches OIDC group sync, login, or the is_admin flag, and is never mistaken
|
|
// for a human in an audit trail (see api_keys' user_id, which every service
|
|
// account key deliberately does not have).
|
|
type ServiceAccount struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Scope string `json:"scope"`
|
|
TeamID *int64 `json:"team_id,omitempty"`
|
|
CreatedBy *int64 `json:"created_by,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// ServiceAccountKey is one bearer credential on a ServiceAccount. Multiple
|
|
// keys per account, the same shape as APIKey, are what let rotation mint a
|
|
// new one and revoke the old without recreating the account.
|
|
type ServiceAccountKey struct {
|
|
ID int64 `json:"id"`
|
|
ServiceAccountID int64 `json:"service_account_id"`
|
|
Name string `json:"name"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
|
|
Key string `json:"key,omitempty"` // populated only on creation, never stored
|
|
}
|