Documentation
¶
Overview ¶
Package warden provides composable permissions and authorization for Go.
Warden supports RBAC (role-based), ABAC (attribute-based), and ReBAC (relationship-based) authorization models individually or combined. It is tenant-scoped by default via forge.Scope and integrates with the Forge ecosystem for audit logging, feature flags, and async jobs.
eng, err := warden.NewEngine(
warden.WithStore(memStore),
)
result, err := eng.Check(ctx, &warden.CheckRequest{
Subject: warden.Subject{Kind: warden.SubjectUser, ID: "user_123"},
Action: warden.Action{Name: "read"},
Resource: warden.Resource{Type: "document", ID: "doc_456"},
})
Index ¶
- Constants
- Variables
- func AncestorNamespaces(path string) []string
- func IsAncestorOrSelf(ancestor, path string) bool
- func JoinNamespace(parent, child string) string
- func NamespaceFromContext(ctx context.Context) string
- func PolicySelectsSubject(pol *policy.Policy, kind SubjectKind, subjectID string, roleSlugs []string) bool
- func ScopeFromContext(ctx context.Context) (appID, tenantID string)
- func ValidateNamespacePath(path string, maxDepth int) error
- func WithActor(ctx context.Context, a Actor) context.Context
- func WithNamespace(ctx context.Context, namespacePath string) context.Context
- func WithRequestIP(ctx context.Context, ip string) context.Context
- func WithTenant(ctx context.Context, appID, tenantID string) context.Context
- type Action
- type Actor
- type Cache
- type CallOption
- type CheckLogLoss
- type CheckRequest
- type CheckResult
- type Config
- type Decision
- type Engine
- func (e *Engine) CanI(ctx context.Context, subjectKind SubjectKind, ...) (bool, error)
- func (e *Engine) Check(ctx context.Context, req *CheckRequest, opts ...CallOption) (*CheckResult, error)
- func (e *Engine) CheckLogLoss() (CheckLogLoss, bool)
- func (e *Engine) Config() Config
- func (e *Engine) Enforce(ctx context.Context, req *CheckRequest, opts ...CallOption) error
- func (e *Engine) ExpandRelation(ctx context.Context, objectType, objectID, rel string, opts ...CallOption) (*Expansion, error)
- func (e *Engine) Explain(ctx context.Context, req *CheckRequest, opts ...CallOption) (*Explanation, error)
- func (e *Engine) Health(ctx context.Context) error
- func (e *Engine) InvalidateSubject(ctx context.Context, tenantID string, subjectKind SubjectKind, ...)
- func (e *Engine) InvalidateTenant(ctx context.Context, tenantID string)
- func (e *Engine) Plugins() *plugin.Registry
- func (e *Engine) RunMaintenance(ctx context.Context) (MaintenanceReport, error)
- func (e *Engine) RunTenantMaintenance(ctx context.Context, tenantID string) (MaintenanceReport, error)
- func (e *Engine) SetExpressionEvaluator(ev ExpressionEvaluator)
- func (e *Engine) Start(_ context.Context) error
- func (e *Engine) StartMaintenance(ctx context.Context)
- func (e *Engine) Stop(ctx context.Context) error
- func (e *Engine) Store() store.Store
- func (e *Engine) SubjectRoles(ctx context.Context, kind SubjectKind, subjectID string, opts ...CallOption) (direct, all []*role.Role, err error)
- type Evaluator
- type ExpandEdge
- type ExpandNode
- type ExpandStop
- type Expansion
- type Explanation
- type ExpressionEvaluator
- type GraphWalker
- type ID
- type Lane
- type LaneState
- type MaintenanceReport
- type MatchInfo
- type MemoryCache
- func (m *MemoryCache) Clear(_ context.Context)
- func (m *MemoryCache) Get(_ context.Context, tenantID, namespacePath string, req *CheckRequest) (*CheckResult, bool)
- func (m *MemoryCache) InvalidateSubject(_ context.Context, tenantID string, subjectKind SubjectKind, subjectID string)
- func (m *MemoryCache) InvalidateTenant(_ context.Context, tenantID string)
- func (m *MemoryCache) Set(_ context.Context, tenantID, namespacePath string, req *CheckRequest, ...)
- type MemoryCacheOption
- type Metrics
- type NoopMetrics
- func (NoopMetrics) AssignmentsPurged(int64)
- func (NoopMetrics) CacheInvalidated(string)
- func (NoopMetrics) CheckEvaluated(Decision, string, time.Duration, bool)
- func (NoopMetrics) CheckLogDropped()
- func (NoopMetrics) CheckLogWritten(int)
- func (NoopMetrics) CheckLogsPurged(int64)
- func (NoopMetrics) GraphBudgetExceeded()
- func (NoopMetrics) GraphNodesVisited(int)
- func (NoopMetrics) HookError(string, string)
- func (NoopMetrics) StoreError(string)
- type Option
- func WithCache(c Cache) Option
- func WithConfig(c Config) Option
- func WithEvaluator(ev Evaluator) Option
- func WithExpressionEvaluator(ev ExpressionEvaluator) Option
- func WithGraphWalker(gw GraphWalker) Option
- func WithLogger(l log.Logger) Option
- func WithMetrics(m Metrics) Option
- func WithPlugin(x plugin.Plugin) Option
- func WithStore(s store.Store) Option
- type Prefix
- type Resource
- type Subject
- type SubjectKind
Constants ¶
const MaxNamespaceDepth = 8
MaxNamespaceDepth is the default cap on namespace nesting. Practical organizations don't go deeper than this; the cap keeps walking-cost predictable. Configurable via Config.MaxNamespaceDepth.
Variables ¶
var ( // ErrAccessDenied is returned when an authorization check fails. ErrAccessDenied = errors.New("warden: access denied") // ErrNotFound is the common base error for the missing-entity errors // below. Use errors.Is(err, ErrNotFound) to match any of them. // // Defined in wardenerr so that low-level subpackages (e.g. store/memory) // can return typed not-found errors without an import cycle. ErrNotFound = wardenerr.ErrNotFound // ErrRoleNotFound is returned when a role cannot be found in the tenant. // Every store returns this for a by-ID lookup whose tenant does not own // the row, so a caller cannot distinguish "no such role" from "that role // belongs to someone else". ErrRoleNotFound = wardenerr.ErrRoleNotFound // ErrPermissionNotFound is returned when a permission cannot be found in the tenant. ErrPermissionNotFound = wardenerr.ErrPermissionNotFound // ErrAssignmentNotFound is returned when an assignment cannot be found in the tenant. ErrAssignmentNotFound = wardenerr.ErrAssignmentNotFound // ErrPolicyNotFound is returned when a policy cannot be found in the tenant. ErrPolicyNotFound = wardenerr.ErrPolicyNotFound // ErrStaleWrite is the common base error for a conditional write that // found the record changed since the caller read it. Use // errors.Is(err, ErrStaleWrite) to match any of the specialized // conflict errors, such as ErrPolicyVersionConflict. ErrStaleWrite = wardenerr.ErrStaleWrite // ErrPolicyVersionConflict is returned by UpdatePolicyIfVersion when the // stored policy's version is not the one the caller expected. Nothing is // written. Wraps ErrStaleWrite. ErrPolicyVersionConflict = wardenerr.ErrPolicyVersionConflict // ErrRelationNotFound is returned when a relation tuple cannot be found in the tenant. ErrRelationNotFound = wardenerr.ErrRelationNotFound // ErrResourceTypeNotFound is returned when a resource type cannot be found in the tenant. ErrResourceTypeNotFound = wardenerr.ErrResourceTypeNotFound // ErrCheckLogNotFound is returned when a check log entry cannot be found in the tenant. ErrCheckLogNotFound = wardenerr.ErrCheckLogNotFound // ErrSystemRoleImmutable is returned when a write would change or // delete a system role. role.CheckWritable returns an error that wraps // it. Defined in wardenerr so the role package can wrap it. ErrSystemRoleImmutable = wardenerr.ErrSystemRoleImmutable // ErrSystemPermissionImmutable is returned when a write would change or // delete a system permission. permission.CheckWritable returns an error // that wraps it. Defined in wardenerr so the permission package can wrap // it. ErrSystemPermissionImmutable = wardenerr.ErrSystemPermissionImmutable // ErrAlreadyExists is the common base error for entity-uniqueness // violations. Use errors.Is(err, ErrAlreadyExists) to match any of the // specialized ErrDuplicate* errors below. // // Defined in wardenerr so that low-level subpackages (e.g. store/memory) // can return typed duplicate errors without an import cycle. ErrAlreadyExists = wardenerr.ErrAlreadyExists // ErrDuplicateRole is returned when a role would violate the // (tenant_id, namespace_path, slug) uniqueness constraint. ErrDuplicateRole = wardenerr.ErrDuplicateRole // ErrDuplicatePermission is returned when a permission would violate the // (tenant_id, namespace_path, name) uniqueness constraint. ErrDuplicatePermission = wardenerr.ErrDuplicatePermission // ErrDuplicatePolicy is returned when a policy would violate the // (tenant_id, namespace_path, name) uniqueness constraint. ErrDuplicatePolicy = wardenerr.ErrDuplicatePolicy // ErrDuplicateResourceType is returned when a resource type would violate // the (tenant_id, namespace_path, name) uniqueness constraint. ErrDuplicateResourceType = wardenerr.ErrDuplicateResourceType // ErrDuplicateAssignment is returned when a role is already assigned to a // subject within the same scope. Wraps ErrAlreadyExists. ErrDuplicateAssignment = wardenerr.ErrDuplicateAssignment // ErrDuplicateRelation is returned when a relation tuple already exists. // Wraps ErrAlreadyExists. ErrDuplicateRelation = wardenerr.ErrDuplicateRelation // ErrCyclicRoleInheritance is returned when a role's parent would make // the role its own ancestor. role.CheckParent returns an error that // wraps it. Defined in wardenerr so the role package can wrap it. ErrCyclicRoleInheritance = wardenerr.ErrCyclicRoleInheritance // ErrMaxMembersExceeded is returned when a role's member limit is reached. ErrMaxMembersExceeded = errors.New("warden: role max members exceeded") // ErrInvalidCondition is returned when a policy condition is malformed. ErrInvalidCondition = errors.New("warden: invalid policy condition") // ErrGraphDepthExceeded is returned when the relation graph walk exceeds max depth. ErrGraphDepthExceeded = errors.New("warden: relation graph depth exceeded") // ErrTenantRequired is returned by Check when the resolved scope has no // tenant ID and Config.RequireTenant is true (the default), and by // RunTenantMaintenance when it is given no tenant. ErrTenantRequired = wardenerr.ErrTenantRequired // ErrGraphBudgetExceeded is returned when a ReBAC graph traversal // exceeds its configured visited-node or fan-out budget. ErrGraphBudgetExceeded = errors.New("warden: graph traversal budget exceeded") // ErrUnauthenticated is returned when a check cannot proceed because no // subject/actor identity is available. ErrUnauthenticated = errors.New("warden: unauthenticated") )
var SystemActor = Actor{Kind: "system", ID: "warden", Via: "system"}
SystemActor identifies warden itself as the actor, used for internal mutations that are not attributable to a caller (maintenance jobs, etc).
Functions ¶
func AncestorNamespaces ¶
AncestorNamespaces returns the path itself and every ancestor up to the tenant root, ordered from most-specific to most-general. The tenant root (empty string) is always the last element.
Examples:
AncestorNamespaces("") → [""]
AncestorNamespaces("eng") → ["eng", ""]
AncestorNamespaces("eng/platform") → ["eng/platform", "eng", ""]
AncestorNamespaces("eng/platform/sre") → ["eng/platform/sre", "eng/platform", "eng", ""]
Used by the engine to resolve roles, permissions, policies, and resource types across the namespace ancestor chain (cascading scope inheritance).
func IsAncestorOrSelf ¶
IsAncestorOrSelf reports whether ancestor is path itself or a strict ancestor of path. Both must be valid namespace paths (or empty string).
func JoinNamespace ¶
JoinNamespace concatenates two namespace paths with a "/" separator, dropping empties on either side. Used by the DSL parser to build absolute paths from nested namespace blocks.
func NamespaceFromContext ¶
NamespaceFromContext extracts the namespace path from the given context. Returns the empty string (tenant root) if not set.
func PolicySelectsSubject ¶ added in v1.7.0
func PolicySelectsSubject(pol *policy.Policy, kind SubjectKind, subjectID string, roleSlugs []string) bool
PolicySelectsSubject reports whether a policy's subject matchers select a subject holding roleSlugs. It is the evaluator's own test: an empty Subjects list selects everyone; otherwise any matcher whose non-empty Kind, ID and Role all agree. Selecting is not applying: the policy's actions, resources, window and conditions still decide each check.
func ScopeFromContext ¶
ScopeFromContext extracts the tenant scope from the given context. Returns appID and tenantID using the standard resolution chain: explicit WithTenant > forge.Scope > empty.
func ValidateNamespacePath ¶
ValidateNamespacePath checks that a path is well-formed. Empty string (the tenant root) is always valid. Otherwise the path is "/"-separated, each segment matches namespaceSegmentRegex, no reserved segment appears, and depth ≤ maxDepth (or MaxNamespaceDepth if 0).
func WithActor ¶ added in v1.7.0
WithActor returns a context carrying an explicit Actor. Takes priority over any actor inferred from ActorFromContext's fallback chain.
func WithNamespace ¶
WithNamespace returns a context with the given namespace path overlaid on top of the existing tenant scope. Pass empty string for the tenant root.
Combine with WithTenant when needed:
ctx := warden.WithTenant(context.Background(), "app1", "t1") ctx = warden.WithNamespace(ctx, "engineering/platform")
func WithRequestIP ¶ added in v1.7.0
WithRequestIP returns a context carrying the caller's IP address, used to populate CheckLog entries (checklog.Entry.RequestIP). Typically set once per inbound request by the HTTP/gRPC middleware layer.
Types ¶
type Action ¶
type Action struct {
Name string `json:"name"`
}
Action represents what the subject wants to do.
type Actor ¶ added in v1.7.0
type Actor struct {
Kind string `json:"kind"` // "user", "api_key", "service", "system"
ID string `json:"id"` //
Via string `json:"via,omitempty"` // "forge", "declarative", "cli", ""
}
Actor identifies who or what caused a mutation, for audit trails and the plugin Audit hook. It is deliberately small and serializable so it can travel through plugin.Event.Actor (typed as `any` there to avoid an import cycle).
func ActorFromContext ¶ added in v1.7.0
ActorFromContext returns the Actor for the current request.
Resolution order:
- An explicit Actor set via WithActor.
- log.UserIDFromContext(ctx), wrapped as {Kind: "user", ID: uid, Via: "forge"}.
- Not found: the zero Actor and false.
type Cache ¶
type Cache interface {
// Get returns a cached check result, if available.
Get(ctx context.Context, tenantID, namespacePath string, req *CheckRequest) (*CheckResult, bool)
// Set stores a check result in the cache.
Set(ctx context.Context, tenantID, namespacePath string, req *CheckRequest, result *CheckResult)
// InvalidateTenant removes all cached results for a tenant.
InvalidateTenant(ctx context.Context, tenantID string)
// InvalidateSubject removes all cached results for a specific subject.
InvalidateSubject(ctx context.Context, tenantID string, subjectKind SubjectKind, subjectID string)
// Clear removes every cached entry across every tenant. Used by
// maintenance and by invalidation paths that cannot be scoped to a
// single tenant (e.g. a delete hook that only carries a bare ID).
Clear(ctx context.Context)
}
Cache provides caching for authorization check results.
Get and Set take the resolved tenant ID and namespace path explicitly (rather than trusting CheckRequest.NamespacePath, which callers may leave unset when they rely on context-derived scope) so the cache key always reflects the namespace the check actually ran against.
type CallOption ¶
type CallOption func(*callOptions)
CallOption is a functional option applied per-call on Check, Enforce, and CanI. It is distinct from Option, which configures the Engine at construction time.
func WithCallAppID ¶
func WithCallAppID(appID string) CallOption
WithCallAppID overrides the app ID for this single call.
func WithCallDryRun ¶ added in v1.7.0
func WithCallDryRun() CallOption
WithCallDryRun evaluates the check without its externally visible side effects: the result cache is neither read nor written, no check log entry is enqueued, and no plugin hooks fire (neither OnBeforeCheck nor OnAfterCheck, on any path including a failed evaluation).
It exists for callers that ask "what would this decide" rather than "decide this", the dashboard playground above all. Without it, pressing a playground button writes an audit entry indistinguishable from production traffic, fires PolicyObligationFired at whatever is listening, and serves the second press from cache, which skips evaluation entirely and reports a cache lookup as the evaluation time.
Internal metrics are not suppressed: CheckEvaluated still records the decision and latency, and a failed evaluation still counts as a store error. Those are operational counters, not externally observable side effects, and a dry run should still be visible to whoever watches the engine's own health.
The decision itself is identical. Only the side effects above are suppressed.
func WithCallNamespacePath ¶
func WithCallNamespacePath(namespacePath string) CallOption
WithCallNamespacePath overrides the namespace path for this single call. Pass empty string to scope the call to the tenant root.
func WithCallTenantID ¶
func WithCallTenantID(tenantID string) CallOption
WithCallTenantID overrides the tenant ID for this single call. It takes precedence over context-derived scope and CheckRequest.TenantID.
type CheckLogLoss ¶ added in v1.7.0
type CheckLogLoss struct {
// QueueFull counts entries dropped because the writer's bounded queue
// was full, or because the writer had already stopped.
QueueFull uint64
// WriteFailed counts entries the store refused to write.
WriteFailed uint64
// Since is when the writer started counting: engine construction.
Since time.Time
}
CheckLogLoss counts checks the engine ran whose entries may not have been recorded. Failed checks count too, because a failed check's entry goes through the same writer. A write that times out after the store committed also counts as failed.
type CheckRequest ¶
type CheckRequest struct {
Subject Subject `json:"subject"`
Action Action `json:"action"`
Resource Resource `json:"resource"`
Context map[string]any `json:"context,omitempty"`
TenantID string `json:"tenant_id,omitempty"` // Optional: overrides context-derived tenant.
NamespacePath string `json:"namespace_path,omitempty"` // Optional: overrides context-derived namespace.
}
CheckRequest is the input to an authorization check.
type CheckResult ¶
type CheckResult struct {
Allowed bool `json:"allowed"`
Decision Decision `json:"decision"`
Reason string `json:"reason,omitempty"`
MatchedBy []MatchInfo `json:"matched_by,omitempty"`
Obligations []string `json:"obligations,omitempty"`
EvalTimeNs int64 `json:"eval_time_ns"`
// contains filtered or unexported fields
}
CheckResult is the outcome of an authorization check.
Obligations is the list of obligation names from PBAC policies that matched this check (allow OR deny). Obligations are side-effect signals — names of actions the calling system should perform (audit-log, require-mfa, notify-security, etc.). They don't change the Allowed/Decision outcome. The engine also fires the PolicyObligationFired plugin hook per obligation, so existing plugin pipelines (Chronicle audit, dispatchers) get them automatically.
type Config ¶
type Config struct {
// MaxGraphDepth is the maximum depth for ReBAC graph traversal.
// Defaults to 10.
MaxGraphDepth int `json:"max_graph_depth,omitempty"`
// CacheTTL is the time-to-live for cached check results. When >0 and no
// Cache was supplied via WithCache, the engine builds a memory cache
// (NewMemoryCache) from CacheTTL and CacheMaxSize. Zero means no
// caching unless a Cache was set explicitly.
CacheTTL time.Duration `json:"cache_ttl,omitempty"`
// CacheMaxSize bounds the number of entries the auto-built memory cache
// holds per shard set. Defaults to 10000. Ignored when a Cache is
// supplied explicitly via WithCache.
CacheMaxSize int `json:"cache_max_size,omitempty"`
// EnableRBAC enables role-based access control evaluation.
// Defaults to true.
EnableRBAC *bool `json:"enable_rbac,omitempty"`
// EnableABAC enables attribute-based access control evaluation.
// Defaults to true.
EnableABAC *bool `json:"enable_abac,omitempty"`
// EnableReBAC enables relationship-based access control evaluation.
// Defaults to true.
EnableReBAC *bool `json:"enable_rebac,omitempty"`
// EnableCheckLog enables writing authorization check results to the
// check log store. Defaults to true.
EnableCheckLog *bool `json:"enable_check_log,omitempty"`
// RequireTenant, when true (the default), makes Check return
// ErrTenantRequired whenever the resolved scope has no tenant ID.
// Set to false for single-tenant / standalone deployments that never
// call WithTenant.
RequireTenant *bool `json:"require_tenant,omitempty"`
// MaxGraphVisited caps the number of distinct nodes a ReBAC graph walk
// may visit before it aborts with ErrGraphBudgetExceeded. Defaults to
// 5000.
MaxGraphVisited int `json:"max_graph_visited,omitempty"`
// MaxGraphFanout caps the number of subjects fetched per hop during a
// ReBAC graph walk (passed through to ListRelationSubjects). Defaults
// to 1000.
MaxGraphFanout int `json:"max_graph_fanout,omitempty"`
// EvaluateAllModels, when true, always evaluates RBAC, ReBAC and ABAC
// even after an earlier model already allowed the request. Defaults to
// false: ReBAC is skipped once RBAC already allowed.
EvaluateAllModels bool `json:"evaluate_all_models,omitempty"`
// CheckLogQueueSize bounds the check-log writer's internal channel.
// Defaults to 4096. When full, new entries are dropped rather than
// blocking Check.
CheckLogQueueSize int `json:"check_log_queue_size,omitempty"`
// CheckLogRetention is how long check log entries are kept before
// RunMaintenance purges them. Defaults to 90 days. The engine reads 0 or
// any negative value as no purging. The Forge extension reads 0 as unset
// and fills in a default, so there only a negative value turns it off.
CheckLogRetention time.Duration `json:"check_log_retention,omitempty"`
// MaintenanceInterval is how often Engine.Start runs RunMaintenance in
// the background. Defaults to 1 hour. The engine reads 0 or any negative
// value as no loop. The Forge extension reads 0 as unset and fills in a
// default, so there only a negative value turns it off.
MaintenanceInterval time.Duration `json:"maintenance_interval,omitempty"`
// MaxBatchChecks bounds the number of checks a batch-check API call may
// request in one call. Defaults to 100.
MaxBatchChecks int `json:"max_batch_checks,omitempty"`
}
Config holds configuration for the Warden engine.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with sensible defaults.
func (Config) Validate ¶ added in v1.7.0
Validate checks the configuration for internally inconsistent or unsafe values: negative counts, a negative cache TTL, a cache TTL over 24h, and a graph depth over 64. A negative CheckLogRetention or MaintenanceInterval is allowed: it switches purging or the maintenance loop off.
type Decision ¶
type Decision string
Decision is the authorization outcome.
const ( // DecisionAllow means the request is permitted. DecisionAllow Decision = "allow" // DecisionDeny means the request is denied (generic). DecisionDeny Decision = "deny" // DecisionDenyExplicit means an explicit deny policy matched. DecisionDenyExplicit Decision = "deny_explicit" // DecisionDenyDefault means no matching allow rule was found. DecisionDenyDefault Decision = "deny_default" // DecisionDenyNoRoles means the subject has no roles assigned. DecisionDenyNoRoles Decision = "deny_no_roles" // DecisionDenyNoPerms means no role grants the required permission. DecisionDenyNoPerms Decision = "deny_no_perms" // DecisionDenyCondition means an ABAC condition blocked the request. DecisionDenyCondition Decision = "deny_condition" // DecisionDenyRelation means no matching relation was found. DecisionDenyRelation Decision = "deny_relation" )
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the central authorization engine. It coordinates RBAC, ReBAC, and ABAC evaluation, manages the store, and fires extension hooks.
func (*Engine) CanI ¶
func (e *Engine) CanI(ctx context.Context, subjectKind SubjectKind, subjectID, action, resourceType, resourceID string, opts ...CallOption) (bool, error)
CanI is a shorthand for a simple authorization check. Optional CallOption values override scope for this single call.
func (*Engine) Check ¶
func (e *Engine) Check(ctx context.Context, req *CheckRequest, opts ...CallOption) (*CheckResult, error)
Check performs an authorization check. This is the hot path. Optional CallOption values override scope for this single call.
func (*Engine) CheckLogLoss ¶ added in v1.7.0
func (e *Engine) CheckLogLoss() (CheckLogLoss, bool)
CheckLogLoss reports how many checks the engine ran whose entries may not have been recorded since this engine started. Failed checks count too, because a failed check's entry goes through the same writer. A write that times out after the store committed also counts as failed. ok is false when check logging is off.
func (*Engine) Enforce ¶
func (e *Engine) Enforce(ctx context.Context, req *CheckRequest, opts ...CallOption) error
Enforce returns an error if the authorization check is denied. Optional CallOption values override scope for this single call.
func (*Engine) ExpandRelation ¶ added in v1.7.0
func (e *Engine) ExpandRelation(ctx context.Context, objectType, objectID, rel string, opts ...CallOption) (*Expansion, error)
ExpandRelation walks the relation graph from objectType:objectID#rel as Check's walker does, without a target, using the configured graph budget and the call's tenant and namespace (relations cascade from ancestors). It writes nothing.
It records no walker metrics (GraphNodesVisited, GraphBudgetExceeded): those describe the walks checks make, and an expansion is someone looking. A store failure still counts under StoreError("graph_expand"), as SubjectRoles counts its own; a cancelled context does not.
func (*Engine) Explain ¶ added in v1.7.0
func (e *Engine) Explain(ctx context.Context, req *CheckRequest, opts ...CallOption) (*Explanation, error)
Explain evaluates req exactly as Check does, as a dry run, and reports each model's part. It never reads or fills the cache, fires no hooks and writes no check log. A request Check would refuse before evaluating (a missing field, a missing tenant) returns that error; a model's store failure is reported in the Explanation instead. As in a dry-run Check, a store failure still increments Metrics.StoreError and a failed permission expression still logs its warning.
func (*Engine) InvalidateSubject ¶ added in v1.7.0
func (e *Engine) InvalidateSubject(ctx context.Context, tenantID string, subjectKind SubjectKind, subjectID string)
InvalidateSubject clears every cached Check result for one subject. A no-op when no Cache is configured.
func (*Engine) InvalidateTenant ¶ added in v1.7.0
InvalidateTenant clears every cached Check result for a tenant. A no-op when no Cache is configured.
func (*Engine) RunMaintenance ¶ added in v1.7.0
func (e *Engine) RunMaintenance(ctx context.Context) (MaintenanceReport, error)
RunMaintenance purges role assignments that have expired and, when Config.CheckLogRetention > 0 (0 or negative is off), check log entries older than the retention window, in every tenant. When either purge actually removed rows and a Cache is configured, it also does a full Cache.Clear: Cache has no "list every tenant" operation, so a targeted per-tenant invalidation isn't possible here; a maintenance run is infrequent enough that a full flush is cheap relative to serving one stale permission.
func (*Engine) RunTenantMaintenance ¶ added in v1.7.0
func (e *Engine) RunTenantMaintenance(ctx context.Context, tenantID string) (MaintenanceReport, error)
RunTenantMaintenance is RunMaintenance for one tenant. It purges that tenant's role assignments that have expired and, when Config.CheckLogRetention > 0 (0 or negative is off), that tenant's check log entries older than the retention window. No other tenant's rows are touched. When either purge removed rows and a Cache is configured, it invalidates that tenant's cached decisions (Cache.InvalidateTenant), not the whole cache.
An empty tenantID returns ErrTenantRequired before anything is deleted: the stores read an empty tenant filter as every tenant, so empty is never passed through as "all of them". RunMaintenance is the engine-wide pass.
func (*Engine) SetExpressionEvaluator ¶
func (e *Engine) SetExpressionEvaluator(ev ExpressionEvaluator)
SetExpressionEvaluator overrides the resource-type expression evaluator at runtime. Used by the extension to wire dsl.NewEngineEvaluator after engine construction, since the evaluator depends on the engine's store.
func (*Engine) Start ¶
Start performs startup initialization: logs a summary of the engine's check-log/cache/maintenance configuration and, when Config.MaintenanceInterval > 0 (0 or negative is off), starts the background maintenance loop (RunMaintenance on that interval) that Stop later cancels.
func (*Engine) StartMaintenance ¶ added in v1.7.0
StartMaintenance runs RunMaintenance on Config.MaintenanceInterval until ctx is cancelled. A no-op when MaintenanceInterval is 0 or negative, the values that switch the loop off. Engine.Start calls this automatically; it is also safe to call directly for full control over the maintenance context's lifetime.
func (*Engine) Stop ¶
Stop performs graceful shutdown: cancels the maintenance loop, drains the check log writer (best-effort, bounded by ctx), and notifies Shutdown plugin hooks.
func (*Engine) SubjectRoles ¶ added in v1.7.0
func (e *Engine) SubjectRoles(ctx context.Context, kind SubjectKind, subjectID string, opts ...CallOption) (direct, all []*role.Role, err error)
SubjectRoles reports the roles a subject holds at a namespace, as Check resolves them for a check on no particular resource: the roles assigned at that namespace or an ancestor (expired assignments excluded), then every parent reached through ParentSlug. direct lists the assigned roles; all lists direct plus inherited, in the order Check resolves them. Resource-scoped assignments are not included: they grant only for a check on their own resource.
type Evaluator ¶
type Evaluator interface {
Evaluate(ctx context.Context, policies []*policy.Policy, req *CheckRequest, roleSlugs []string) (*CheckResult, error)
}
Evaluator evaluates ABAC/PBAC policies against a check request.
roleSlugs is the set of role slugs (direct and inherited) held by the requesting subject, resolved by the engine before evaluation. It powers role-scoped policy.SubjectMatch.Role matching: a policy naming {Role: "editor"} only applies to subjects holding a role with that slug.
func DefaultEvaluator ¶
func DefaultEvaluator() Evaluator
DefaultEvaluator returns the built-in condition evaluator backed by the system wall clock.
func NewConditionEvaluator ¶
NewConditionEvaluator returns an Evaluator that uses the supplied clock for PBAC time-window evaluation (NotBefore / NotAfter). Pass time.Now for production; tests can pass a fixed-time function.
type ExpandEdge ¶ added in v1.7.0
ExpandEdge is one tuple: from the object node to its subject node.
type ExpandNode ¶ added in v1.7.0
type ExpandNode struct {
Type, ID, Relation string // Relation is "" for a single subject
Depth int
// Walked is true when the walk took this node off its queue, listed
// its tuples and went through them, so its edges are all of them. It
// is false for a node reached but not walked: one still queued when a
// depth or visited stop ended the walk, or the node whose hop tripped
// the fanout limit (its tuples were listed but not walked, so it has
// no edges). A single subject is never walked, so it is always false
// there. A walked subject set with no tuples is Walked with no edges.
Walked bool
}
ExpandNode is one (objectType, objectID, relation) the walk visited, or a single subject it reached.
type ExpandStop ¶ added in v1.7.0
type ExpandStop string
ExpandStop says why an expansion ended.
const ( ExpandComplete ExpandStop = "complete" // every reachable tuple was visited ExpandDepth ExpandStop = "depth" // a node beyond MaxGraphDepth was reached ExpandVisited ExpandStop = "visited" // more than MaxGraphVisited distinct nodes ExpandFanout ExpandStop = "fanout" // one hop returned at least MaxGraphFanout tuples )
type Expansion ¶ added in v1.7.0
type Expansion struct {
Nodes []ExpandNode
Edges []ExpandEdge
Stop ExpandStop
// Limit is the effective value of the limit that stopped the walk,
// the one named by Stop: the walker's own value, which is the
// walker's default when Config holds 0 for it. 0 for ExpandComplete.
Limit int
// Parent maps a node index to the node it was first reached from (-1
// for the root), in the walker's BFS order, so PathTo reproduces the
// path Walk would report.
Parent []int
// ExactWalk is true when this is the walk Check makes: the engine's
// graph walker is the built-in BFS walker, whether NewEngine built it
// or WithGraphWalker installed one from NewGraphWalker or
// DefaultGraphWalker. It is false when WithGraphWalker installed
// another kind of walker: Check walks with that one, and the
// expansion falls back to the built-in BFS walk with Config's budget,
// so neither its shape nor PathTo is guaranteed to match Check.
ExactWalk bool
}
Expansion is the walk Check would perform from one object and relation, with no target subject, so it visits everything reachable within the engine's budget.
func (*Expansion) PathTo ¶ added in v1.7.0
PathTo returns the walk's path from the root to the first node for subjectType:subjectID, formatted exactly as the walker's reconstructPath, or "" when the expansion never reached it.
Edges are in the order the walk saw their tuples, so the first edge into a subjectType:subjectID node (a single subject or a subject set: Walk matches either) is the tuple Walk would have stopped on.
type Explanation ¶ added in v1.7.0
type Explanation struct {
// Result is what Check would have returned. Nil when a model failed.
Result *CheckResult
// Err is the error Check would have returned when a model failed.
Err string
RBAC, ReBAC, ABAC Lane
}
Explanation is an explained check: the merged result Check would return, and each model's own part in it, from one evaluation.
type ExpressionEvaluator ¶
type ExpressionEvaluator interface {
EvalPermission(ctx context.Context, tenantID, namespacePath, resourceType, permName, subjectKind, subjectID, resourceID string) (matched bool, err error)
}
ExpressionEvaluator is an optional engine hook that evaluates resource-type permission expressions (the SpiceDB-style `viewer or editor or parent->read` expressions stored on ResourceType.Permissions[].Expression).
The DSL package implements this; wire it via WithExpressionEvaluator. When nil, resource-type expressions are inert (the relation graph walker still handles direct + transitive relations).
type GraphWalker ¶
type GraphWalker interface {
Walk(ctx context.Context, relStore relation.Store, tenantID, namespacePath string, req *CheckRequest) (allowed bool, path string, err error)
}
GraphWalker traverses the relation graph for ReBAC evaluation.
Walk is invoked with the namespace path of the request. Relations cascade like roles and policies: the walker follows tuples in the request namespace and every ancestor namespace (see AncestorNamespaces), so a relationship granted at a parent namespace is honored for descendant-scoped checks.
func DefaultGraphWalker ¶
func DefaultGraphWalker(maxDepth int) GraphWalker
DefaultGraphWalker returns a BFS graph walker with the given max depth and the given metrics sink. Fan-out and visited-node budgets default to 1000 and 5000 respectively; use NewGraphWalker to configure them from Config.
func NewGraphWalker ¶ added in v1.7.0
func NewGraphWalker(maxDepth, maxVisited, maxFanout int, metrics Metrics) GraphWalker
NewGraphWalker returns a BFS graph walker bounded by maxDepth (hop count), maxVisited (distinct nodes visited before aborting), and maxFanout (subjects fetched per hop, passed through to ListRelationSubjects). Zero values fall back to sane defaults. A nil metrics sink uses NoopMetrics.
type Lane ¶ added in v1.7.0
type Lane struct {
State LaneState
// Result is the model's own result: set for LaneAllow and LaneDeny, and
// for LaneNoMatch when the model returned one.
Result *CheckResult
// Err is the store error, for LaneError.
Err string
// WalkTruncated is set on the ReBAC lane when the graph walk stopped at
// MaxGraphDepth or MaxGraphVisited before it could answer.
WalkTruncated bool
// ExpressionErr is set on the ReBAC lane when the resource type's
// permission expression failed and was treated as no match.
ExpressionErr string
}
Lane is one model's part in an explained check.
type LaneState ¶ added in v1.7.0
type LaneState string
LaneState is what one authorization model did during an explained check.
const ( // LaneAllow: the model granted the request. LaneAllow LaneState = "allow" // LaneDeny: an explicit deny policy matched. ABAC only. LaneDeny LaneState = "deny" // LaneNoMatch: the model ran and granted nothing. Result carries its // reason when it gave one (RBAC and ReBAC always do; ABAC with no // matching policy has no result at all). LaneNoMatch LaneState = "noMatch" // LaneSkipped: ReBAC only. RBAC already allowed and EvaluateAllModels // is off, so the graph walk did not run. LaneSkipped LaneState = "skipped" // LaneDisabled: the model is turned off in Config. LaneDisabled LaneState = "disabled" // LaneError: the model's store read failed. Check returns an error here. LaneError LaneState = "error" // LaneNotEvaluated: an earlier model failed, so this one never ran, as // in Check. LaneNotEvaluated LaneState = "notEvaluated" )
type MaintenanceReport ¶ added in v1.7.0
MaintenanceReport summarizes one RunMaintenance or RunTenantMaintenance pass.
type MatchInfo ¶
type MatchInfo struct {
Source string `json:"source"` // "rbac", "abac", "rebac"
RuleID string `json:"rule_id,omitempty"`
Detail string `json:"detail,omitempty"`
}
MatchInfo describes what rule matched during evaluation.
type MemoryCache ¶ added in v1.7.0
type MemoryCache struct {
// contains filtered or unexported fields
}
MemoryCache is the engine's built-in Cache implementation: a sharded, bounded, TTL'd LRU with O(1)-ish tenant/subject invalidation. It lives in the root package (rather than the cache/ subpackage) because the engine must be able to construct it directly from Config.CacheTTL / Config.CacheMaxSize without an import cycle: cache/ imports warden.
func NewMemoryCache ¶ added in v1.7.0
func NewMemoryCache(opts ...MemoryCacheOption) *MemoryCache
NewMemoryCache constructs the engine's built-in memory cache.
func (*MemoryCache) Clear ¶ added in v1.7.0
func (m *MemoryCache) Clear(_ context.Context)
Clear removes every cached entry across every tenant.
func (*MemoryCache) Get ¶ added in v1.7.0
func (m *MemoryCache) Get(_ context.Context, tenantID, namespacePath string, req *CheckRequest) (*CheckResult, bool)
Get returns a cached check result, if present and not expired.
func (*MemoryCache) InvalidateSubject ¶ added in v1.7.0
func (m *MemoryCache) InvalidateSubject(_ context.Context, tenantID string, subjectKind SubjectKind, subjectID string)
InvalidateSubject removes all cached results for a specific subject, across every namespace it was checked in.
func (*MemoryCache) InvalidateTenant ¶ added in v1.7.0
func (m *MemoryCache) InvalidateTenant(_ context.Context, tenantID string)
InvalidateTenant removes all cached results for a tenant. A tenant's entries always live in a single shard (shard selection is a pure function of tenantID), so this only ever locks one shard.
func (*MemoryCache) Set ¶ added in v1.7.0
func (m *MemoryCache) Set(_ context.Context, tenantID, namespacePath string, req *CheckRequest, result *CheckResult)
Set stores a check result in the cache.
type MemoryCacheOption ¶ added in v1.7.0
type MemoryCacheOption func(*memoryCacheConfig)
MemoryCacheOption configures a MemoryCache.
func WithCacheMaxSize ¶ added in v1.7.0
func WithCacheMaxSize(n int) MemoryCacheOption
WithCacheMaxSize sets the maximum number of entries the cache holds in total, across every shard. Defaults to 10000.
func WithCacheTTL ¶ added in v1.7.0
func WithCacheTTL(ttl time.Duration) MemoryCacheOption
WithCacheTTL sets the cache entry time-to-live. Defaults to 5 minutes.
type Metrics ¶ added in v1.7.0
type Metrics interface {
// CheckEvaluated is called once per Check, whether it hit the cache or
// was fully evaluated.
CheckEvaluated(decision Decision, source string, dur time.Duration, cached bool)
// StoreError is called when a store call returns an error during
// evaluation. op names the failing operation (e.g. "list_roles").
StoreError(op string)
// CheckLogDropped is called when the check log writer's queue is full
// and an entry is dropped rather than written.
CheckLogDropped()
// CheckLogWritten is called after a batch of check log entries is
// flushed to the store. n is the batch size.
CheckLogWritten(n int)
// HookError is called when a plugin hook panics or returns an error.
HookError(hook, plugin string)
// GraphNodesVisited is called after a ReBAC graph walk completes with
// the number of nodes visited during that walk.
GraphNodesVisited(n int)
// GraphBudgetExceeded is called when a ReBAC graph walk is aborted
// because it exceeded MaxGraphVisited or MaxGraphFanout.
GraphBudgetExceeded()
// AssignmentsPurged is called after a maintenance run purges expired
// role assignments, with the number of rows removed.
AssignmentsPurged(n int64)
// CheckLogsPurged is called after a maintenance run purges old check
// log entries, with the number of rows removed.
CheckLogsPurged(n int64)
// CacheInvalidated is called whenever the check cache is invalidated.
// scope is "tenant" or "subject".
CacheInvalidated(scope string)
}
Metrics receives instrumentation events from the engine. Implement this interface to wire warden into your metrics backend (Prometheus, OpenTelemetry, statsd, ...). WithMetrics installs a custom implementation; NoopMetrics is the default when none is configured.
type NoopMetrics ¶ added in v1.7.0
type NoopMetrics struct{}
NoopMetrics implements Metrics with no-ops. It is the default when no Metrics is configured via WithMetrics.
func (NoopMetrics) AssignmentsPurged ¶ added in v1.7.0
func (NoopMetrics) AssignmentsPurged(int64)
func (NoopMetrics) CacheInvalidated ¶ added in v1.7.0
func (NoopMetrics) CacheInvalidated(string)
func (NoopMetrics) CheckEvaluated ¶ added in v1.7.0
func (NoopMetrics) CheckLogDropped ¶ added in v1.7.0
func (NoopMetrics) CheckLogDropped()
func (NoopMetrics) CheckLogWritten ¶ added in v1.7.0
func (NoopMetrics) CheckLogWritten(int)
func (NoopMetrics) CheckLogsPurged ¶ added in v1.7.0
func (NoopMetrics) CheckLogsPurged(int64)
func (NoopMetrics) GraphBudgetExceeded ¶ added in v1.7.0
func (NoopMetrics) GraphBudgetExceeded()
func (NoopMetrics) GraphNodesVisited ¶ added in v1.7.0
func (NoopMetrics) GraphNodesVisited(int)
func (NoopMetrics) HookError ¶ added in v1.7.0
func (NoopMetrics) HookError(string, string)
func (NoopMetrics) StoreError ¶ added in v1.7.0
func (NoopMetrics) StoreError(string)
type Option ¶
type Option func(*Engine)
Option is a functional option for the Engine.
func WithEvaluator ¶
WithEvaluator sets the ABAC policy evaluator.
func WithExpressionEvaluator ¶
func WithExpressionEvaluator(ev ExpressionEvaluator) Option
WithExpressionEvaluator sets the resource-type permission expression evaluator. The DSL package's NewEngineEvaluator is the canonical implementation. When unset, resource-type expressions are inert.
func WithGraphWalker ¶
func WithGraphWalker(gw GraphWalker) Option
WithGraphWalker sets the ReBAC graph walker.
func WithMetrics ¶ added in v1.7.0
WithMetrics sets the engine's metrics sink.
func WithPlugin ¶
WithPlugin registers a plugin with the engine.
type Resource ¶
type Resource struct {
Type string `json:"type"`
ID string `json:"id"`
Attributes map[string]any `json:"attributes,omitempty"`
}
Resource represents the target of an authorization check.
type Subject ¶
type Subject struct {
Kind SubjectKind `json:"kind"`
ID string `json:"id"`
Attributes map[string]any `json:"attributes,omitempty"`
}
Subject represents an actor in an authorization check.
type SubjectKind ¶
type SubjectKind string
SubjectKind identifies the type of actor making an authorization request.
const ( // SubjectUser represents a human user. SubjectUser SubjectKind = "user" // SubjectAPIKey represents an API key (e.g., from Keysmith). SubjectAPIKey SubjectKind = "api_key" // SubjectService represents a service-to-service caller. SubjectService SubjectKind = "service" // SubjectServiceAcct represents a service account. SubjectServiceAcct SubjectKind = "service_acct" )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
_examples
|
|
|
abac
command
Example: attribute-based access control with conditions.
|
Example: attribute-based access control with conditions. |
|
forge
command
Example: Warden as a Forge extension with API routes.
|
Example: Warden as a Forge extension with API routes. |
|
rbac
command
Example: pure RBAC with role inheritance.
|
Example: pure RBAC with role inheritance. |
|
rebac
command
Example: Zanzibar-style ReBAC with transitive relations.
|
Example: Zanzibar-style ReBAC with transitive relations. |
|
standalone
command
Example: standalone Warden usage without Forge.
|
Example: standalone Warden usage without Forge. |
|
standalone-embed
command
Standalone-embed example — boots a Warden engine, applies an embedded .warden config tree, then runs a sample Check.
|
Standalone-embed example — boots a Warden engine, applies an embedded .warden config tree, then runs a sample Check. |
|
Package api provides HTTP handlers for the Warden authorization engine.
|
Package api provides HTTP handlers for the Warden authorization engine. |
|
Package assignment defines the Assignment entity (role→subject binding).
|
Package assignment defines the Assignment entity (role→subject binding). |
|
Package cache provides caching implementations for Warden check results.
|
Package cache provides caching implementations for Warden check results. |
|
Package checklog defines the check audit log Entry entity.
|
Package checklog defines the check audit log Entry entity. |
|
cmd
|
|
|
internal/cli
Package cli holds shared helpers for the warden CLI binaries.
|
Package cli holds shared helpers for the warden CLI binaries. |
|
warden
command
Command warden is the CLI for the .warden DSL.
|
Command warden is the CLI for the .warden DSL. |
|
warden-lsp
command
Command warden-lsp is a Language Server Protocol (LSP) server for the `.warden` DSL.
|
Command warden-lsp is a Language Server Protocol (LSP) server for the `.warden` DSL. |
|
Package dsl implements the .warden declarative configuration language.
|
Package dsl implements the .warden declarative configuration language. |
|
Package extension provides a Forge extension entry point for Warden.
|
Package extension provides a Forge extension entry point for Warden. |
|
contract
authz.go: the authorization decision in front of every dashboard intent.
|
authz.go: the authorization decision in front of every dashboard intent. |
|
Package id defines TypeID-based identity types for all Warden entities.
|
Package id defines TypeID-based identity types for all Warden entities. |
|
Package keysmithbridge lets Keysmith's warden_hook extension drive Warden.
|
Package keysmithbridge lets Keysmith's warden_hook extension drive Warden. |
|
Package lsp implements the Warden Language Server Protocol server for the .warden DSL.
|
Package lsp implements the Warden Language Server Protocol server for the .warden DSL. |
|
Package middleware provides HTTP authorization middleware for Warden.
|
Package middleware provides HTTP authorization middleware for Warden. |
|
Package permission defines the Permission entity and its store interface.
|
Package permission defines the Permission entity and its store interface. |
|
Package plugin defines the plugin system for Warden.
|
Package plugin defines the plugin system for Warden. |
|
auditlog
Package auditlog provides an in-tree structured-log Audit plugin.
|
Package auditlog provides an in-tree structured-log Audit plugin. |
|
Package policy defines the Policy entity used for ABAC and PBAC evaluation.
|
Package policy defines the Policy entity used for ABAC and PBAC evaluation. |
|
Package relation defines the Tuple entity for ReBAC (Zanzibar-style relations).
|
Package relation defines the Tuple entity for ReBAC (Zanzibar-style relations). |
|
Package resourcetype defines the ResourceType entity for ReBAC schema definitions.
|
Package resourcetype defines the ResourceType entity for ReBAC schema definitions. |
|
Package role defines the Role entity and its store interface for RBAC.
|
Package role defines the Role entity and its store interface for RBAC. |
|
Package store defines the aggregate persistence interface.
|
Package store defines the aggregate persistence interface. |
|
contract
Package contract provides shared test contracts that every Warden store backend must satisfy.
|
Package contract provides shared test contracts that every Warden store backend must satisfy. |
|
memory
Package memory provides an in-memory implementation of the Warden composite store.
|
Package memory provides an in-memory implementation of the Warden composite store. |
|
mongo
Package mongo provides a MongoDB Store implementation for the Warden store.
|
Package mongo provides a MongoDB Store implementation for the Warden store. |
|
postgres
Package postgres provides a PostgreSQL implementation of the Warden composite store using grove ORM with Go-based migrations.
|
Package postgres provides a PostgreSQL implementation of the Warden composite store using grove ORM with Go-based migrations. |
|
sqlite
Package sqlite provides a SQLite Store implementation for embedded/edge use.
|
Package sqlite provides a SQLite Store implementation for embedded/edge use. |
|
Package wardenerr holds shared sentinel errors used across warden subpackages.
|
Package wardenerr holds shared sentinel errors used across warden subpackages. |