Documentation
¶
Index ¶
- Constants
- Variables
- func AllPermissions() []string
- func AuthorizeIdentity(cfg OIDCConfig, id *Identity) error
- func BuiltinRoles() map[string][]string
- func CheckGlobalOnly(perms []string, sel db.Selector) error
- func ClearLoginStateCookie(w http.ResponseWriter, opts CookieOptions)
- func ClearSessionCookie(w http.ResponseWriter, opts CookieOptions)
- func ClientIP(r *http.Request) string
- func GenerateSecureToken(length int) (rawToken string, tokenHash string, err error)
- func HasGlobalOnly(perms []string) bool
- func HashToken(rawToken string) string
- func IsAPIKey(value string) bool
- func KnownPermission(p string) bool
- func LookupAPIKey(ctx context.Context, store APIKeyStore, raw string) (*db.APIKey, error)
- func Narrower(p *Principal, perms []string, sel db.Selector) error
- func NewAPIKey(name, createdBy string, expiresAt *time.Time) (raw string, key *db.APIKey, err error)
- func NewAuditEntry(r *http.Request, action, kind, id, name string, labels db.Labels, details any) *db.AuditEntry
- func NewRandomToken() (string, error)
- func RequireAny(next http.Handler) http.Handler
- func RequireAnyPerm(perms ...string) func(http.Handler) http.Handler
- func RequirePerm(perm string) func(http.Handler) http.Handler
- func SetLoginStateCookie(w http.ResponseWriter, token string, opts CookieOptions)
- func SetSessionCookie(w http.ResponseWriter, token string, expires time.Time, opts CookieOptions)
- func WithAgent(ctx context.Context, c *AgentClaims) context.Context
- func WithPrincipal(ctx context.Context, p *Principal) context.Context
- type APIKeyStore
- type AdminAuth
- type AgentAuth
- type AgentClaims
- type AgentStateStore
- type AgentTokenClaims
- type AgentVerifier
- type BackupTicketClaims
- type Binding
- type BindingStore
- type CookieOptions
- type Identity
- type LoginStateClaims
- type OIDC
- type OIDCConfig
- type PermissionGroup
- type Principal
- type PrincipalKind
- type Resolver
- type SessionClaims
- type TokenManager
- func (tm *TokenManager) CreateAgentToken(agentID uuid.UUID, taskQueue string, version int, ttl time.Duration) (string, time.Time, error)
- func (tm *TokenManager) CreateBackupTicket(claims BackupTicketClaims, ttl time.Duration) (string, error)
- func (tm *TokenManager) CreateLoginState(state, nonce, next string) (string, error)
- func (tm *TokenManager) CreateSession(id *Identity, ttl time.Duration) (string, time.Time, error)
- func (tm *TokenManager) CreateWorkerToken(agentID uuid.UUID, taskQueue string, version int, ttl time.Duration) (string, time.Time, error)
- func (tm *TokenManager) ValidateAgentToken(tokenString string) (*AgentTokenClaims, error)
- func (tm *TokenManager) ValidateBackupTicket(ticketString string) (*BackupTicketClaims, error)
- func (tm *TokenManager) ValidateLoginState(tokenString string) (*LoginStateClaims, error)
- func (tm *TokenManager) ValidateSession(tokenString string) (*SessionClaims, error)
Constants ¶
const ( CSRFHeader = "X-Requested-With" CSRFHeaderValue = "fallbck" )
CSRFHeader must be present on cookie-authenticated state-changing requests. Browsers cannot add custom headers cross-site, so its presence proves same-origin JS.
const ( PermAgentsRead = "agents:read" PermAgentsWrite = "agents:write" // debug, auto-update, update, inventory, logs, merge, labels PermAgentsDelete = "agents:delete" PermAgentsEnroll = "agents:enroll" // enrollment tokens PermJobsRead = "jobs:read" // the job and all of its runs PermJobsWrite = "jobs:write" PermJobsRun = "jobs:run" // run, cancel PermJobsDelete = "jobs:delete" PermTargetsRead = "targets:read" PermTargetsWrite = "targets:write" PermTargetsDelete = "targets:delete" PermTargetsCredentials = "targets:credentials" // reveal the PBS token PermKeyringsRead = "keyrings:read" PermKeyringsWrite = "keyrings:write" // create, rename, master key, rotate, import PermKeyringsExport = "keyrings:export" PermKeyringsDelete = "keyrings:delete" PermTenantsRead = "tenants:read" PermTenantsWrite = "tenants:write" PermTenantsDelete = "tenants:delete" PermAPIKeysManage = "apikeys:manage" PermRBACManage = "rbac:manage" PermAuditRead = "audit:read" PermTemporalUI = "temporal:ui" )
Permissions are fixed resource:action strings. This table is the only list: the router, the role editor and /api/me all read it.
const ( RoleAdmin = "admin" RoleOperator = "operator" RoleViewer = "viewer" )
Built-in role names
const ( // SessionCookieName carries the admin browser session JWT SessionCookieName = "fallbck_session" // LoginStateCookieName carries the OIDC state/nonce between login redirect and callback LoginStateCookieName = "fallbck_login" DefaultSessionTTL = 12 * time.Hour )
const ( AudienceBackupTicket = "fallbck-ticket" AudienceAgent = "fallbck-agent" AudienceSession = "fallbck-session" AudienceLoginState = "fallbck-login" )
Audiences separate the JWT types that share the server signing secret so that a token of one kind can never be presented as another.
const APIKeyPrefix = "fbk_"
APIKeyPrefix makes keys recognisable in logs and secret scanners
const DefaultAgentTokenTTL = 365 * 24 * time.Hour
DefaultAgentTokenTTL is the lifetime of an agent bearer token unless configured otherwise
const DefaultResolverTTL = 30 * time.Second
DefaultResolverTTL bounds how long a removed binding keeps working
const Issuer = "fallbck-server"
Issuer is the `iss` claim on every JWT minted by the server
Variables ¶
var ( ErrInvalidTicket = errors.New("invalid or expired backup ticket") ErrInvalidToken = errors.New("invalid or expired token") )
var BuiltinDescriptions = map[string]string{ RoleAdmin: "Everything, including access management and API keys", RoleOperator: "Read everything; create, run and delete jobs; manage agents, targets and tenants (no deletes of those, no credentials or key export)", RoleViewer: "Read everything", }
BuiltinDescriptions are shown in the Access page
var Catalog = []PermissionGroup{ {"agents", []string{PermAgentsRead, PermAgentsWrite, PermAgentsDelete, PermAgentsEnroll}}, {"jobs", []string{PermJobsRead, PermJobsWrite, PermJobsRun, PermJobsDelete}}, {"targets", []string{PermTargetsRead, PermTargetsWrite, PermTargetsDelete, PermTargetsCredentials}}, {"keyrings", []string{PermKeyringsRead, PermKeyringsWrite, PermKeyringsExport, PermKeyringsDelete}}, {"tenants", []string{PermTenantsRead, PermTenantsWrite, PermTenantsDelete}}, {"apikeys", []string{PermAPIKeysManage}}, {"rbac", []string{PermRBACManage}}, {"audit", []string{PermAuditRead}}, {"temporal", []string{PermTemporalUI}}, }
Catalog lists every permission grouped by resource, in display order
var ErrAgentRevoked = errors.New("agent has been revoked")
ErrAgentRevoked is returned when an agent has been deleted or revoked
var ErrInvalidAPIKey = errors.New("invalid, expired or revoked API key")
ErrInvalidAPIKey is returned for unknown, expired or revoked API keys
var ErrNotAuthorized = errors.New("identity is not authorized to access this server")
ErrNotAuthorized is returned when an authenticated identity is not permitted to administer
var GlobalOnly = []string{PermRBACManage, PermAPIKeysManage, PermTemporalUI}
GlobalOnly permissions can only be held on everything: a binding or API key whose role holds one must have an empty selector. rbac:manage and apikeys:manage administer access itself (a scoped administrator could otherwise grant themselves a global role); temporal:ui opens the Temporal UI, which is not label-scoped and shows every workflow.
Functions ¶
func AllPermissions ¶ added in v1.0.0
func AllPermissions() []string
AllPermissions is every permission in catalog order
func AuthorizeIdentity ¶
func AuthorizeIdentity(cfg OIDCConfig, id *Identity) error
AuthorizeIdentity applies the allow-lists of cfg to id
func BuiltinRoles ¶ added in v1.0.0
BuiltinRoles are rewritten into the roles table at every start
func CheckGlobalOnly ¶ added in v1.0.0
CheckGlobalOnly refuses a selector on a role holding a global-only permission
func ClearLoginStateCookie ¶
func ClearLoginStateCookie(w http.ResponseWriter, opts CookieOptions)
ClearLoginStateCookie removes the OIDC state cookie
func ClearSessionCookie ¶
func ClearSessionCookie(w http.ResponseWriter, opts CookieOptions)
ClearSessionCookie expires the session cookie
func ClientIP ¶ added in v1.0.0
ClientIP is the address set by the trusted-proxies middleware, else the peer
func GenerateSecureToken ¶
GenerateSecureToken creates a cryptographically secure random token string and its SHA256 hash
func HasGlobalOnly ¶ added in v1.0.0
HasGlobalOnly reports whether a permission set contains a global-only permission
func KnownPermission ¶ added in v1.0.0
KnownPermission reports whether p is in the catalog
func LookupAPIKey ¶
LookupAPIKey resolves a raw key to its record, enforcing expiry and revocation
func Narrower ¶ added in v1.0.0
Narrower checks that a grant of perms on sel stays within what p holds: every permission must be covered on a selector sel narrows, and global-only permissions need an empty selector. Used for API keys (a key never reaches further than its creator) and for bindings created by a principal.
func NewAPIKey ¶
func NewAPIKey(name, createdBy string, expiresAt *time.Time) (raw string, key *db.APIKey, err error)
NewAPIKey generates a new raw key and its persisted record. The raw key is returned exactly once; only its hash is stored.
func NewAuditEntry ¶ added in v1.0.0
func NewAuditEntry(r *http.Request, action, kind, id, name string, labels db.Labels, details any) *db.AuditEntry
NewAuditEntry describes an action of the request's principal for the audit log
func NewRandomToken ¶
NewRandomToken returns a URL-safe random string for OIDC state and nonce values
func RequireAny ¶ added in v1.0.0
RequireAny refuses principals without any binding
func RequireAnyPerm ¶ added in v1.0.0
RequireAnyPerm is RequirePerm for a route that serves holders of any of perms
func RequirePerm ¶ added in v1.0.0
RequirePerm is the route gate: refused unless some binding grants perm on some selector. Object-level checks (labels) happen in the handler.
func SetLoginStateCookie ¶
func SetLoginStateCookie(w http.ResponseWriter, token string, opts CookieOptions)
SetLoginStateCookie writes the short-lived OIDC state cookie
func SetSessionCookie ¶
func SetSessionCookie(w http.ResponseWriter, token string, expires time.Time, opts CookieOptions)
SetSessionCookie writes the session cookie
Types ¶
type APIKeyStore ¶
type APIKeyStore interface {
CreateAPIKey(ctx context.Context, key *db.APIKey) error
GetAPIKeyByHash(ctx context.Context, hash string) (*db.APIKey, error)
TouchAPIKey(ctx context.Context, id uuid.UUID) error
}
APIKeyStore provides API key persistence (implemented by *db.DB)
type AdminAuth ¶
type AdminAuth struct {
TM *TokenManager
Keys APIKeyStore
// RBAC resolves a session's or key's bindings; nil grants nothing (fail closed)
RBAC *Resolver
// Insecure disables authentication entirely (development only)
Insecure bool
// LoginPath is where unauthenticated browser requests are redirected
LoginPath string
}
AdminAuth authenticates admin requests via session cookie or API key
func NewAdminAuth ¶
func NewAdminAuth(tm *TokenManager, keys APIKeyStore, insecure bool) *AdminAuth
NewAdminAuth constructs the admin middleware
func (*AdminAuth) Authenticate ¶
Authenticate resolves the principal for a request without enforcing anything
type AgentAuth ¶
type AgentAuth struct {
Verifier *AgentVerifier
}
AgentAuth authenticates agent requests with the agent bearer token
type AgentClaims ¶
type AgentClaims struct {
AgentID uuid.UUID
TaskQueue string
Version int
Worker bool
ExpiresAt time.Time
}
AgentClaims is the authenticated identity of an agent, attached to request contexts
func AgentFrom ¶
func AgentFrom(ctx context.Context) *AgentClaims
AgentFrom returns the authenticated agent on the context, if any
type AgentStateStore ¶
type AgentStateStore interface {
GetAgentAuthState(ctx context.Context, id uuid.UUID) (*db.AgentAuthState, error)
}
AgentStateStore provides the persisted revocation state of agents (implemented by *db.DB)
type AgentTokenClaims ¶
type AgentTokenClaims struct {
TaskQueue string `json:"tq"`
Version int `json:"ver"`
// Worker marks the short-lived secondary token of a kubernetes run's worker pods;
// it cannot be rotated into a long-lived one
Worker bool `json:"wrk,omitempty"`
jwt.RegisteredClaims
}
AgentTokenClaims is the JWT payload of an agent bearer token. `sub` is the agent ID.
type AgentVerifier ¶
type AgentVerifier struct {
// contains filtered or unexported fields
}
AgentVerifier validates agent bearer tokens against both the signature and the database state (agent exists, not revoked, token version current). Database lookups are cached per agent for a short time so every RPC does not hit Postgres.
func NewAgentVerifier ¶
func NewAgentVerifier(tm *TokenManager, store AgentStateStore, cacheTTL time.Duration) *AgentVerifier
NewAgentVerifier creates a verifier. cacheTTL bounds how long a revocation takes to propagate.
func (*AgentVerifier) Invalidate ¶
func (v *AgentVerifier) Invalidate(agentID uuid.UUID)
Invalidate drops the cached state for an agent (call after revocation for immediate effect)
func (*AgentVerifier) Verify ¶
func (v *AgentVerifier) Verify(ctx context.Context, bearer string) (*AgentClaims, error)
Verify validates a bearer token value (with or without the "Bearer " prefix)
type BackupTicketClaims ¶
type BackupTicketClaims struct {
JobID uuid.UUID `json:"job_id"`
AgentID uuid.UUID `json:"agent_id"`
TargetID uuid.UUID `json:"target_id"`
Datastore string `json:"datastore"`
Namespace string `json:"namespace"`
BackupType string `json:"backup_type"` // e.g. "host"
BackupID string `json:"backup_id"` // e.g. "agent-123"
BackupTime int64 `json:"backup_time"` // unix timestamp
// Issued is when the run's first ticket was minted; refreshes and sub-tickets carry
// it over so a run cannot keep itself alive forever
Issued int64 `json:"issued,omitempty"`
jwt.RegisteredClaims
}
BackupTicketClaims defines the claims embedded in a short-lived backup session ticket
type Binding ¶ added in v1.0.0
type Binding struct {
Role string `json:"role"`
Permissions []string `json:"permissions"`
Selector db.Selector `json:"selector"`
}
Binding is one grant held by a principal: the permissions of a role on the objects Selector matches (empty: everything)
func AdminBindings ¶ added in v1.0.0
func AdminBindings() []Binding
AdminBindings is the global admin grant (insecure dev mode)
type BindingStore ¶ added in v1.0.0
type BindingStore interface {
BindingsFor(ctx context.Context, email string, groups, claims []string) ([]*db.RoleBinding, error)
GetRole(ctx context.Context, name string) (*db.Role, error)
}
BindingStore loads bindings and roles (implemented by *db.DB)
type CookieOptions ¶
type CookieOptions struct {
// Secure marks cookies as HTTPS-only; enable whenever PublicURL is https
Secure bool
}
CookieOptions controls the attributes of cookies set by the server
type Identity ¶
type Identity struct {
Subject string
Email string
Name string
Groups []string
// EmailUnverified is set when the IdP says email_verified=false; allowed_emails
// never matches such an identity (a missing claim is taken as verified)
EmailUnverified bool
// Claims are the values of the configured binding claims as "<claim>=<value>",
// which "claim" role bindings match
Claims []string
}
Identity is an authenticated admin resolved from an OIDC ID token
type LoginStateClaims ¶
type LoginStateClaims struct {
State string `json:"state"`
Nonce string `json:"nonce"`
Next string `json:"next,omitempty"`
jwt.RegisteredClaims
}
LoginStateClaims protects the OIDC round trip: state must match the callback query, nonce must match the ID token, next is where to send the user afterwards.
type OIDC ¶
type OIDC struct {
// contains filtered or unexported fields
}
OIDC performs the authorization-code login flow against the configured provider
func NewOIDC ¶
NewOIDC discovers the provider and prepares the OAuth2 client. redirectURL must be PublicURL + "/auth/callback" and registered with the provider.
func (*OIDC) AuthCodeURL ¶
AuthCodeURL builds the provider login URL for a state/nonce pair
type OIDCConfig ¶
type OIDCConfig = config.OIDCConfig
OIDCConfig is the OpenID Connect configuration (defined in pkg/config)
type PermissionGroup ¶ added in v1.0.0
type PermissionGroup struct {
Resource string `json:"resource"`
Permissions []string `json:"permissions"`
}
PermissionGroup is one resource and its actions, for the role editor
type Principal ¶
type Principal struct {
Kind PrincipalKind
Subject string
Email string
Name string
Bindings []Binding
}
Principal is the authenticated admin identity attached to the request context. Bindings are what it may do (see Scope); none means no access.
func PrincipalFrom ¶
PrincipalFrom returns the admin principal on the context, if any
func (*Principal) Allowed ¶ added in v1.0.0
Allowed reports whether perm reaches an object with these labels
func (*Principal) Can ¶ added in v1.0.0
Can reports whether any binding grants perm at all (the route gate)
func (*Principal) Covers ¶ added in v1.0.0
Covers reports whether one binding grants perm on a selector that sel narrows, so everything sel reaches is already reachable
func (*Principal) Permissions ¶ added in v1.0.0
Permissions is the union of the permissions of all bindings, sorted
type PrincipalKind ¶
type PrincipalKind string
PrincipalKind distinguishes how an admin request was authenticated
const ( PrincipalSession PrincipalKind = "session" PrincipalAPIKey PrincipalKind = "apikey" PrincipalInsecure PrincipalKind = "insecure" )
type Resolver ¶ added in v1.0.0
type Resolver struct {
// contains filtered or unexported fields
}
Resolver turns an identity into its bindings. Results are cached for a short TTL so a removed binding takes effect within that window, not at session expiry; Flush drops the cache at once (called after every access change on this server).
func NewResolver ¶ added in v1.0.0
func NewResolver(store BindingStore, ttl time.Duration) *Resolver
func (*Resolver) APIKey ¶ added in v1.0.0
APIKey resolves a key's single binding (its role on its selector)
func (*Resolver) Flush ¶ added in v1.0.0
func (r *Resolver) Flush()
Flush forgets every cached resolution
func (*Resolver) Session ¶ added in v1.0.0
func (r *Resolver) Session(ctx context.Context, email string, emailUnverified bool, groups, claims []string) ([]Binding, error)
Session resolves an OIDC identity: user bindings on its email (only when the IdP did not mark it unverified), group bindings on its groups claim and claim bindings on its binding claims ("<claim>=<value>")
type SessionClaims ¶
type SessionClaims struct {
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
Groups []string `json:"groups,omitempty"`
// EmailUnverified: the IdP said email_verified=false, so user bindings on the
// email never match
EmailUnverified bool `json:"email_unverified,omitempty"`
// BindingClaims are the identity's "<claim>=<value>" pairs for claim bindings
BindingClaims []string `json:"binding_claims,omitempty"`
jwt.RegisteredClaims
}
SessionClaims is the payload of an admin browser session. `sub` is the OIDC subject.
type TokenManager ¶
type TokenManager struct {
// contains filtered or unexported fields
}
TokenManager signs and validates every JWT the server issues (backup tickets, agent tokens, admin sessions, login state) with a single HS256 secret, separated by audience.
func NewTokenManager ¶
func NewTokenManager(secret string) *TokenManager
NewTokenManager creates a new TokenManager with a shared signing secret
func (*TokenManager) CreateAgentToken ¶
func (tm *TokenManager) CreateAgentToken(agentID uuid.UUID, taskQueue string, version int, ttl time.Duration) (string, time.Time, error)
CreateAgentToken mints a long-lived bearer token for an enrolled agent
func (*TokenManager) CreateBackupTicket ¶
func (tm *TokenManager) CreateBackupTicket(claims BackupTicketClaims, ttl time.Duration) (string, error)
CreateBackupTicket generates a signed JWT ticket valid for the specified duration (e.g. 2 hours)
func (*TokenManager) CreateLoginState ¶
func (tm *TokenManager) CreateLoginState(state, nonce, next string) (string, error)
CreateLoginState signs the OIDC state cookie payload
func (*TokenManager) CreateSession ¶
CreateSession mints a session token for an authenticated admin identity
func (*TokenManager) CreateWorkerToken ¶ added in v0.14.12
func (tm *TokenManager) CreateWorkerToken(agentID uuid.UUID, taskQueue string, version int, ttl time.Duration) (string, time.Time, error)
CreateWorkerToken mints a short-lived worker token (same agent identity, not rotatable)
func (*TokenManager) ValidateAgentToken ¶
func (tm *TokenManager) ValidateAgentToken(tokenString string) (*AgentTokenClaims, error)
ValidateAgentToken verifies the signature and claims of an agent token. It does NOT consult revocation state; use AgentVerifier for that.
func (*TokenManager) ValidateBackupTicket ¶
func (tm *TokenManager) ValidateBackupTicket(ticketString string) (*BackupTicketClaims, error)
ValidateBackupTicket parses and verifies a short-lived backup ticket
func (*TokenManager) ValidateLoginState ¶
func (tm *TokenManager) ValidateLoginState(tokenString string) (*LoginStateClaims, error)
ValidateLoginState verifies the OIDC state cookie payload
func (*TokenManager) ValidateSession ¶
func (tm *TokenManager) ValidateSession(tokenString string) (*SessionClaims, error)
ValidateSession verifies a session token