Documentation
¶
Overview ¶
Package mode builds the authorization provider a deployment configured (ADR-008): role-based ("rbac") or relationship-based ("rebac") with an engine, from an authz.ModeConfig.
It is a separate package from authz because both providers import authz, and so that loading a configuration file links neither provider. An application reads the `authz` key of its SystemAuth configuration and builds the provider once:
res, err := mode.New(ctx, cfg.Authz, mode.Deps{DB: db, Registry: registry})
if err != nil { ... }
defer res.Close()
orgs := organization.NewService(db, organization.WithAuthzSyncer(res.Syncer))
invites := invite.NewService(db, baseURL, invite.WithAuthzSyncer(res.Syncer), invite.WithResourceGrants(res.Grants))
Both modes implement the same platform contract (organization roles, application roles, resource grants, ListResources) with the same semantics, so switching modes is a configuration change; the membership, role and grant tables remain the record in both.
In mode rebac the services remove a relationship from the graph before the change that revokes it commits, and write granted relationships after their change commits. The Result's Reconciler repairs what a failed write leaves behind; run it on the configured schedule beside the application:
go func() {
if err := res.RunReconciler(ctx); err != nil { ... }
}()
Index ¶
Constants ¶
const DefaultReconcilePageSize = 500
DefaultReconcilePageSize is how many rows or relationships the reconciler reads per request when the configuration does not say.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Deps ¶
type Deps struct {
// DB is the identity database: the membership and grant tables both
// modes keep. Required.
DB *ent.Client
// Registry holds the application vocabularies. It restricts grants to
// registered applications' resource types and, in mode rebac, supplies
// the schema fragments composed into the engine's schema. Optional.
Registry *authz.VocabularyRegistry
// SyncMode decides what a failed write of granted access to the
// relationship engine does: authz.SyncModeStrict returns it,
// SyncModeEventual (the default) logs it. Revocations remove the
// relationship before their transaction commits and fail when the
// engine refuses, whatever the mode; the reconciler (Result.Reconciler)
// repairs what a failed write leaves behind. Mode rbac writes nothing.
// See authz.SyncMode.
SyncMode authz.SyncMode
// AccessChangeSink announces the access changes grants record
// (ADR-010). Optional.
AccessChangeSink accesschange.Sink
// Logger records sync failures and startup. Default: slog.Default().
Logger *slog.Logger
// PlatformAdmin and ManagerRoles feed the role-based provider's
// platform-admin bypass and manager-role checks (rbac.WithPlatformAdminChecker and rbac.WithManagerRoleGetter). Mode rebac reads
// both from the graph. Optional.
PlatformAdmin func(ctx context.Context, principalID uuid.UUID) (bool, error)
ManagerRoles authz.ManagerRoleGetter
// RBACOptions are further options for the role-based provider, such as
// WithPermissions and WithRoleHierarchy. They apply after the ones
// above.
RBACOptions []rbac.Option
}
Deps are the collaborators a mode needs.
type Graph ¶ added in v0.21.0
type Graph interface {
// ReadRelationships calls fn for every relationship matching filter,
// as one consistent snapshot read pageSize at a time.
ReadRelationships(ctx context.Context, filter *spicedb.RelationshipFilter, pageSize int, fn func(*spicedb.Relationship) error) error
// WriteRelationships writes relationships; writing one that exists
// succeeds.
WriteRelationships(ctx context.Context, rels []*spicedb.Relationship) error
// DeleteRelationshipsExact deletes relationships; deleting one that
// is absent succeeds.
DeleteRelationshipsExact(ctx context.Context, rels []*spicedb.Relationship) error
}
Graph is the relationship store a Reconciler repairs. *spicedb.Client implements it.
type NamespaceResult ¶ added in v0.21.0
type NamespaceResult struct {
Namespace string
Checked int
Desired int
Added int
Removed int
Restored int
Skipped int
}
NamespaceResult is the part of a ReconcileResult for one definition.
type ReconcileResult ¶ added in v0.21.0
type ReconcileResult struct {
Started time.Time
Duration time.Duration
// Checked is the number of relationships read from the graph that the
// reconciler manages, and Desired the number the identity tables call
// for.
Checked int
Desired int
// Added is the number of missing relationships written, and Removed
// the number of stale relationships deleted. Restored counts deleted
// relationships written back because a grant committed while the
// reconciliation ran; Skipped counts missing relationships not
// written because a revocation removed their rows meanwhile.
Added int
Removed int
Restored int
Skipped int
// Namespaces breaks the counts down by the graph definition
// reconciled ("organization", "platform", "principal", an
// application's "{app}_org", or a resource type).
Namespaces []NamespaceResult
}
ReconcileResult reports one reconciliation.
type ReconcileSchedule ¶ added in v0.21.0
type ReconcileSchedule struct {
// OnStartup reconciles as soon as Run starts.
OnStartup bool
// Interval is the time between periodic reconciliations; zero or
// less disables them.
Interval time.Duration
// Jitter adds a random delay of up to Jitter to every interval, so
// several instances do not reconcile in step.
Jitter time.Duration
}
ReconcileSchedule says when Run reconciles.
type ReconcileStats ¶ added in v0.21.0
type ReconcileStats struct {
Runs int64
Failures int64
Added int64
Removed int64
Restored int64
Skipped int64
// Last is the result of the last successful run, zero before one.
Last ReconcileResult
// LastError is the error of the last run, nil when it succeeded.
LastError error
}
ReconcileStats are a Reconciler's totals since it was created, for metrics.
type Reconciler ¶ added in v0.21.0
type Reconciler struct {
// contains filtered or unexported fields
}
Reconciler repairs the authorization graph from the identity tables, which are the record: it writes the relationships the tables call for that the graph lacks, and deletes those it holds that the tables no longer call for. It manages the relationships the identity services write — organization memberships, platform administration, manager relations, application roles on "{app}_org" facets and resource grants on collaborator resource types — and never touches others, such as the "org" relation an application writes on its own resources.
The identity services remove a relationship before the change that revokes it commits, so the graph never holds more than the tables record for long; the reconciler repairs what is left behind when a commit fails after the removal, when a grant could not be written, or when a grant and a revocation of the same access race.
A reconciliation is safe while the services run. For each definition it reads the graph first and the tables second, so a grant committed after the graph was read is in the tables and is not deleted. It writes a missing relationship only in a transaction holding the graph locks of its principal and organization (package identity/graphlock), after reading its rows again, so a revocation in progress is never undone. It reads the rows of every relationship it deleted again afterwards and restores one a grant committed in the meantime. The locks need PostgreSQL; on SQLite the reconciler does not serialize with revocations.
What the tables call for follows package identity/graphstate, which the services also consult before writing a grant: in particular, a deactivated principal and a collaborator's application roles call for nothing.
It holds one definition's relationships in memory at a time.
func NewReconciler ¶ added in v0.21.0
func NewReconciler(db *ent.Client, graph Graph, opts ...ReconcilerOption) *Reconciler
NewReconciler returns a Reconciler of graph from db.
func (*Reconciler) Reconcile ¶ added in v0.21.0
func (r *Reconciler) Reconcile(ctx context.Context) (ReconcileResult, error)
Reconcile repairs the graph once and reports what it changed. Calls are serialized. On an error the repairs made before it stand and are counted in the result.
func (*Reconciler) Run ¶ added in v0.21.0
func (r *Reconciler) Run(ctx context.Context, s ReconcileSchedule) error
Run reconciles on schedule until ctx is cancelled, then returns nil. A failed reconciliation is logged and counted in Stats, and the schedule goes on. With no interval it returns after the startup run.
func (*Reconciler) Stats ¶ added in v0.21.0
func (r *Reconciler) Stats() ReconcileStats
Stats returns the reconciler's totals.
type ReconcilerOption ¶ added in v0.21.0
type ReconcilerOption func(*Reconciler)
ReconcilerOption configures a Reconciler.
func WithReconcileLogger ¶ added in v0.21.0
func WithReconcileLogger(l *slog.Logger) ReconcilerOption
WithReconcileLogger sets the logger for summaries and failures. The summary is logged at info level and carries counts only; the relationships changed, with their identifiers, are logged at debug.
func WithReconcilePageSize ¶ added in v0.21.0
func WithReconcilePageSize(n int) ReconcilerOption
WithReconcilePageSize sets how many rows or relationships are read per request. Default DefaultReconcilePageSize.
func WithReconcileRegistry ¶ added in v0.21.0
func WithReconcileRegistry(r *authz.VocabularyRegistry) ReconcilerOption
WithReconcileRegistry names the application vocabularies whose application roles and resource grants the reconciler manages. Without it, it manages the resource types found in the grants table and no application roles.
type Result ¶
type Result struct {
// Mode is the configured mode.
Mode authz.Mode
// Provider answers authorization checks and implements the resource
// grant contract. For mode rbac it is a *rbac.Provider, which also
// implements authz.DecisionAuthorizer; for rebac, a *spicedb.Provider.
Provider authz.Provider
// Syncer is the relationship syncer to pass to the identity services
// (organization, invite, approle: their WithAuthzSyncer options). Mode
// rbac returns the no-op syncer, since it reads the tables at check
// time; mode rebac returns the engine's, which also implements
// authz.AppRoleSyncer and authz.ResourceGrantSyncer.
Syncer authz.RelationshipSyncer
// SyncMode is Deps.SyncMode with its default applied
// (authz.SyncModeEventual), to pass to the services with Syncer.
SyncMode authz.SyncMode
// Grants is the resource-grant store both modes keep, for services that
// grant inside their own transactions (invite.WithResourceGrants).
Grants *resourcegrant.Service
// Reconciler repairs the relationship graph from the identity tables
// in mode rebac; nil in mode rbac, which has no graph. Call its
// Reconcile for an on-demand run, and RunReconciler to run it on the
// configured schedule.
Reconciler *Reconciler
// Schedule is when RunReconciler reconciles, from the configuration's
// reconcile settings. Disabled reconciler: the zero schedule.
Schedule ReconcileSchedule
// contains filtered or unexported fields
}
Result is a configured mode.
func New ¶
New builds the provider cfg selects. It applies the configuration's defaults and validates it, so a zero authz.ModeConfig is mode rbac.
For mode rebac with an embedded engine it starts the instance and writes the composed schema; with a remote engine it connects, and writes the schema only when the configuration says so (authz.SpiceDBConfig. WriteSchema).
func (*Result) Close ¶
Close releases what the mode holds: the engine's connection, and, for an embedded SpiceDB, the instance. It is safe to call on any Result.
func (*Result) RunReconciler ¶ added in v0.21.0
RunReconciler runs the reconciler on Schedule until ctx is cancelled and returns nil then. It returns at once in mode rbac, when the reconciler is disabled, and after the startup run when no interval is configured.