adminauth

package
v0.9.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package adminauth authenticates administrative principals and evaluates Cedar authorization policies.

Design

Principals have roles and root authority; API tokens are checksummed, stored as digests, and revocable. Route actions and request context are evaluated under default-deny Cedar policies. Root administration, credential-issuance restrictions and last-root protection are enforced outside policy grants. Only root may mutate principals or credentials, including self-rotation; role subsets do not establish equivalent authority under arbitrary Cedar. Store.ApplyPrincipal atomically preserves an active root under concurrent credential changes. Natural expiry and policy lockout remain operator concerns.

The reference server separately accepts DM_ADMIN_TOKEN as a root credential that bypasses policy, including when a principal store is configured. Its use is audited as break-glass; removal requires unsetting it and restarting. Use it to bootstrap principals, then verify their access and remove it. This package does not implement administrative users, passwords, sessions or federation; applications can supply another authorizer.

References

Index

Constants

View Source
const (
	// EntityPrincipal is an admin caller, `MDM::Principal::"<name>"`.
	EntityPrincipal types.EntityType = "MDM::Principal"
	// EntityRole is a named group a principal belongs to,
	// `MDM::Role::"<name>"`, expressed as a Cedar entity parent so a policy
	// can say `principal in MDM::Role::"reader"`.
	EntityRole types.EntityType = "MDM::Role"
	// EntityAction is an operation a route declares, `MDM::Action::"<id>"`.
	EntityAction types.EntityType = "MDM::Action"
	// EntitySystem is the resource for routes that act on the deployment
	// rather than on one object: `MDM::System::"any"`.
	EntitySystem types.EntityType = "MDM::System"
	// EntityEnrollment is one enrollment as a resource.
	EntityEnrollment types.EntityType = "MDM::Enrollment"
	// EntityDeclaration is one declaration as a resource.
	EntityDeclaration types.EntityType = "MDM::Declaration"
	// EntityDEPAccount is one device enrollment service account as a resource.
	EntityDEPAccount types.EntityType = "MDM::DEPAccount"
)

Cedar entity types. Every admin authorization decision is a Cedar request over these: a principal (which may be a member of roles), an action, and a resource. Namespacing them under MDM keeps action ids unique, which Cedar requires globally.

View Source
const (
	// Prefix precedes every admin token.
	Prefix = "mdmt_"

	// TokenLen is the total length of a well-formed token.
	TokenLen = len(Prefix) + bodyLen + checksumLen
)

Token format: a type prefix, a random body and a checksum.

mdmt_<30 base62 characters><6 base62 checksum characters>

The prefix supports secret scanning. The checksum rejects malformed values before storage lookup. The random body provides approximately 178 bits of entropy; storage holds its digest rather than the bearer value. See decision 0034.

View Source
const DefaultPageSize = 100

DefaultPageSize applies when Page.Limit is not positive.

Variables

View Source
var (
	// ErrNotFound is an unknown principal or policy, or a token matching none.
	ErrNotFound = errors.New("adminauth: not found")
	// ErrConflict is a name that already exists.
	ErrConflict = errors.New("adminauth: conflict")
	// ErrInvalid is a malformed name, token, or policy.
	ErrInvalid = errors.New("adminauth: invalid")
	// ErrRevoked is a principal whose token has been revoked.
	ErrRevoked = errors.New("adminauth: token revoked")
	// ErrExpired is a principal whose token has passed its expiry.
	ErrExpired = errors.New("adminauth: token expired")
	// ErrDenied is an authorization decision of deny.
	ErrDenied = errors.New("adminauth: denied")
	// ErrLastRoot guards the last active root credential against deletion,
	// demotion, revocation or immediate expiry. Natural expiry still requires
	// operational rotation before all root credentials expire.
	ErrLastRoot = errors.New("adminauth: last root principal")
	// ErrEscalation is an attempt to issue a credential for a principal whose
	// authority the caller does not already hold.
	ErrEscalation = errors.New("adminauth: cannot issue credentials for a more privileged principal")
	// ErrUnknownAction indicates a policy reference to an action absent from the
	// route registry. This additional validation follows Cedar syntax parsing.
	ErrUnknownAction = errors.New("adminauth: unknown action")
)

Errors this package and its stores return.

View Source
var Root = Principal{Name: "root", Root: true}

Root is the implicit actor for bootstrap, before any principal exists.

View Source
var SystemResource = types.NewEntityUID(EntitySystem, "any")

SystemResource is the resource for deployment-wide routes.

Functions

func ActionUID

func ActionUID(id string) types.EntityUID

ActionUID is the Cedar entity for an action id.

func Digest

func Digest(t Token) string

Digest is the SHA-256 hex digest a store persists in place of the token. The token is high-entropy random, so a plain hash is the right primitive; a password KDF would only add cost.

func ParseRoles

func ParseRoles(s string) ([]string, error)

ParseRoles validates a comma-separated role list, sorted and deduplicated.

func PrincipalUID

func PrincipalUID(name string) types.EntityUID

PrincipalUID is the Cedar entity for a principal name.

func Redact

func Redact(t Token) string

Redact renders a token for a log line or an error: the prefix, the first four body characters, and an ellipsis. Never log a whole token.

func RoleUID

func RoleUID(name string) types.EntityUID

RoleUID is the Cedar entity for a role name.

func Valid

func Valid(t Token) bool

Valid reports whether t is well formed: the right prefix, the right length, only alphabet characters, and a checksum that matches the body. It says nothing about whether the token was ever issued.

Checking this before a store lookup means a malformed value never becomes a database round trip, which is the point of carrying a checksum at all.

func ValidName

func ValidName(name string) bool

ValidName reports whether a principal or role name is usable: 1 to 64 characters of letters, digits, hyphen, underscore, or dot. Names appear in policies and audit lines, so they stay boring on purpose, and the character set keeps them safe to render inside a Cedar entity literal.

func Validate

func Validate(reg *Registry, doc Policy) error

Validate parses one document and checks its action ids without adding it to a set, for the write path.

Types

type Action

type Action struct {
	// ID is the Cedar action id, `MDM::Action::"<ID>"`.
	ID string
	// Help is operator-facing prose naming the consequence, shown by
	// `dmctl policy actions`. Zentral writes the same for its secret-reveal
	// actions, and it is the difference between an operator granting an
	// action knowingly and granting it by name.
	Help string
	// Resource is the entity type this action acts on, EntitySystem when the
	// action is deployment-wide.
	Resource types.EntityType
}

Action describes an operation from the route registry. Policy validation rejects references outside this registry.

type Decision

type Decision struct {
	Allowed bool
	// Policy is the document and statement that decided, empty when nothing
	// matched and the default deny applied.
	Policy string
	// Errors are per-policy evaluation errors. A policy that errors neither
	// permits nor forbids, so these are surfaced rather than swallowed.
	Errors []string
}

Decision is the outcome of an authorization check, carrying the policy that decided so an audit line can name it.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager authenticates admin callers, answers authorization decisions from the stored Cedar policies, and administers principals and policies.

Principal, credential and policy mutations require Principal.Root independently of Cedar. Callers authenticate the actor and authorize the route action before invoking these methods. A role-subset comparison cannot bound arbitrary Cedar authority. Stores atomically protect the last active root from removal, demotion, revocation and immediate expiry; later expiry and policy lockout remain operational responsibilities.

func New

func New(store Store, reg *Registry, opts ...Option) (*Manager, error)

New returns a Manager over store, serving the actions in reg.

func (*Manager) Authenticate

func (m *Manager) Authenticate(ctx context.Context, t Token) (Principal, error)

Authenticate resolves a plaintext token to its principal.

A malformed token is rejected on its checksum before any query runs, so a scanner spraying the endpoint costs no database round trips. The principal carries TokenID, so an audit line names the credential that acted without naming the secret.

func (*Manager) Authorize

func (m *Manager) Authorize(ctx context.Context, p Principal, action string, resource types.EntityUID, reqCtx map[string]types.Value) (Decision, error)

Authorize answers one request against the current policies, recompiling them when the store's policy version has moved.

The default is deny: an unknown action, an empty policy set, and a policy that errors all produce a denial rather than an allow.

func (*Manager) CreatePrincipal

func (m *Manager) CreatePrincipal(ctx context.Context, actor Principal, p Principal, expires time.Time) (Principal, Token, error)

CreatePrincipal adds a principal and mints its first token.

actor must be Root. The caller additionally authorizes its administrative action before entering this manager.

func (*Manager) DeletePolicy

func (m *Manager) DeletePolicy(ctx context.Context, actor Principal, name string) error

DeletePolicy removes a policy document.

func (*Manager) DeletePrincipal

func (m *Manager) DeletePrincipal(ctx context.Context, actor Principal, name string) error

DeletePrincipal removes a principal.

func (*Manager) GetPolicy

func (m *Manager) GetPolicy(ctx context.Context, actor Principal, name string) (Policy, error)

GetPolicy returns one policy document.

func (*Manager) Policies

func (m *Manager) Policies(ctx context.Context, actor Principal) ([]Policy, error)

Policies returns every policy document, ordered by name.

func (*Manager) Principal

func (m *Manager) Principal(ctx context.Context, name string) (Principal, error)

Principal returns one principal.

func (*Manager) Principals

func (m *Manager) Principals(ctx context.Context, p Page) (Result[Principal], error)

Principals pages principals by name.

func (*Manager) PutPolicy

func (m *Manager) PutPolicy(ctx context.Context, actor Principal, doc Policy) (Policy, error)

PutPolicy parses and validates the policy, rejects unknown action references, and stores it only after validation succeeds.

func (*Manager) Registry

func (m *Manager) Registry() *Registry

Registry returns the action registry this manager serves.

func (*Manager) Revoke

func (m *Manager) Revoke(ctx context.Context, actor Principal, name string) error

Revoke clears a principal's token, leaving the principal in place so its name still resolves in audit history.

func (*Manager) Rotate

func (m *Manager) Rotate(ctx context.Context, actor Principal, name string, expires time.Time) (Principal, Token, error)

Rotate mints a replacement token, invalidating the previous one at once.

func (*Manager) UpdatePrincipal

func (m *Manager) UpdatePrincipal(ctx context.Context, actor Principal, name string, roles []string, root bool) (Principal, error)

UpdatePrincipal replaces a principal's roles and root flag.

type Option

type Option func(*Manager)

Option configures a Manager.

func WithClock

func WithClock(c clock.Clock) Option

WithClock injects a clock, so token expiry is deterministic in tests.

type Page

type Page struct {
	Cursor string
	Limit  int
}

Page requests one page of principals or policies.

type Policy

type Policy struct {
	// Name identifies the document for editing and for audit lines.
	Name string
	// Source is the Cedar text, stored and served exactly as written so an
	// operator sees back what they wrote.
	Source string
	// Description is operator prose, never interpreted.
	Description string
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

Policy is a stored Cedar policy document. One record may hold several statements; the whole document is parsed and validated as a unit.

type PolicySet

type PolicySet struct {
	// contains filtered or unexported fields
}

PolicySet is a compiled, immutable set of policies ready to answer decisions. Compile once per policy version and share it across requests.

func Compile

func Compile(reg *Registry, version int64, docs []Policy) (*PolicySet, error)

Compile parses policy documents with Cedar and checks referenced actions against the supplied registry. It returns a compiled set only when both checks succeed. Action validation uses the stable API without the experimental schema package.

func (*PolicySet) Authorize

func (p *PolicySet) Authorize(principal Principal, action string, resource types.EntityUID, ctx map[string]types.Value) Decision

Authorize evaluates one request. The default is deny: with no policy set, no matching policy, or an unknown action, the answer is no.

func (*PolicySet) Version

func (p *PolicySet) Version() int64

Version is the store version this set was compiled from.

type Principal

type Principal struct {
	Name  string
	Roles []string
	// Root may administer principals and policies. It confers no other
	// authority: a root principal still needs a policy to enqueue a command.
	Root      bool
	CreatedAt time.Time
	UpdatedAt time.Time
	// TokenID names the current credential in audit lines, empty once
	// revoked. It is a fragment of the token, never enough to use.
	TokenID string
	// TokenAt is when the current token was minted (zero when revoked).
	TokenAt time.Time
	// ExpiresAt is when the current token stops being accepted; zero means no expiry.
	ExpiresAt time.Time
}

Principal is an administrative caller authorized by policies naming the principal or its roles. Root is checked outside policy evaluation for privileged administration, so policy grants cannot authorize editing their own authority.

func (Principal) Active

func (p Principal) Active(now time.Time) error

Active reports whether p has a credential that is neither revoked nor expired at now.

func (Principal) Covers

func (p Principal) Covers(other Principal) bool

Covers compares role membership and the root flag. It does not compare Cedar authority: policies can grant permissions directly to a named principal or distinguish otherwise identical roles using context and forbid clauses. Credential administration therefore requires root independently of Covers.

func (Principal) Entity

func (p Principal) Entity() types.Entity

Entity renders the principal as a Cedar entity whose parents are its roles, so `principal in MDM::Role::"reader"` resolves.

func (Principal) UID

func (p Principal) UID() types.EntityUID

UID is the principal's Cedar entity.

type PrincipalChange

type PrincipalChange struct {
	// Op is update, rotate, revoke or delete.
	Op              string
	Roles           []string
	Root            bool
	Digest, TokenID string
	ExpiresAt       time.Time
}

PrincipalChange describes a credential mutation committed with the store's last-active-root check. It contains a token digest, never the bearer token.

func (PrincipalChange) Apply

func (c PrincipalChange) Apply(p Principal, now time.Time) (Principal, error)

Apply computes the new record. Stores must atomically check whether this removes the last active root before committing it. Delete returns an inactive record for that check; the store then removes the row.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the set of actions the server serves. It is built once from the route table and then read-only, so there is no global mutable state and no init-order dependence.

func NewRegistry

func NewRegistry(actions ...Action) (*Registry, error)

NewRegistry returns a registry over actions, refusing duplicates and malformed ids.

func (*Registry) Actions

func (r *Registry) Actions() []Action

Actions returns every action, sorted by id.

func (*Registry) IDs

func (r *Registry) IDs() []string

IDs returns every action id, sorted.

func (*Registry) Lookup

func (r *Registry) Lookup(id string) (Action, bool)

Lookup returns the action with id.

type Result

type Result[T any] struct {
	Items      []T
	NextCursor string
}

Result is one page with the cursor for the next ("" at the end).

type Store

type Store interface {
	// ApplyPrincipal serializes mutations across instances and refuses removal,
	// revocation, expiry or demotion of the last active root credential. Older
	// low-level write methods below are intended for import/storage tooling;
	// authenticated administration must use this method.
	ApplyPrincipal(ctx context.Context, name string, change PrincipalChange, now time.Time) (Principal, error)
	// CreatePrincipal adds a principal with its first token digest. An
	// existing name is ErrConflict.
	CreatePrincipal(ctx context.Context, p Principal, digest string, now time.Time) (Principal, error)
	// Principal returns one principal by name.
	Principal(ctx context.Context, name string) (Principal, error)
	// PrincipalByDigest returns the principal whose current token digest
	// matches. This is the authentication path and must be one indexed lookup.
	PrincipalByDigest(ctx context.Context, digest string) (Principal, error)
	// Principals pages principals by name.
	Principals(ctx context.Context, p Page) (Result[Principal], error)
	// UpdatePrincipal replaces the roles and root flag of a principal.
	UpdatePrincipal(ctx context.Context, name string, roles []string, root bool, now time.Time) (Principal, error)
	// SetToken replaces the current token digest, invalidating the previous
	// one immediately.
	SetToken(ctx context.Context, name, digest, tokenID string, expires, now time.Time) (Principal, error)
	// RevokeToken clears the current token, leaving the principal in place.
	RevokeToken(ctx context.Context, name string, now time.Time) error
	// DeletePrincipal removes a principal entirely.
	DeletePrincipal(ctx context.Context, name string) error
	// CountRoot returns how many principals carry Root, for the anti-lockout
	// invariant.
	CountRoot(ctx context.Context) (int, error)

	// PutPolicy stores or replaces a policy document.
	PutPolicy(ctx context.Context, p Policy, now time.Time) (Policy, error)
	// GetPolicy returns one policy document by name.
	GetPolicy(ctx context.Context, name string) (Policy, error)
	// Policies returns every stored policy, ordered by name. The whole set is
	// compiled together, so this is not paged.
	Policies(ctx context.Context) ([]Policy, error)
	// DeletePolicy removes a policy document.
	DeletePolicy(ctx context.Context, name string) error
	// PolicyVersion changes whenever any policy changes, so a cached
	// compilation can tell it is stale without recompiling.
	PolicyVersion(ctx context.Context) (int64, error)
}

Store persists principals, the digests of their tokens, and the policy documents that grant them authority. Implementations are contract-tested by adminauth/adminauthtest, so the in-memory and the three SQL backends behave identically.

A store never sees a plaintext token: the caller mints one, hands the store its digest, and shows the value to the operator once.

type Token

type Token string

Token is a plaintext admin API token. It exists only between minting and handing it to the operator: the store keeps Digest(t) and never the value.

func Mint

func Mint() (Token, error)

Mint returns a new token. The caller stores Digest(token) and shows the token to the operator once.

Directories

Path Synopsis
Package adminauthtest defines adminauth.Store contract tests and a failing-store wrapper.
Package adminauthtest defines adminauth.Store contract tests and a failing-store wrapper.
Package inmem implements an in-memory adminauth.Store for tests and development.
Package inmem implements an in-memory adminauth.Store for tests and development.
Package sqlstore persists administrative principals, token digests and policies in SQL.
Package sqlstore persists administrative principals, token digests and policies in SQL.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL