warden

package module
v1.7.0 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: Apache-2.0 Imports: 29 Imported by: 9

README

Warden — Composable permissions & authorization engine

Go Reference Go Version CI

Warden is a composable authorization engine supporting RBAC, ABAC, and ReBAC in a single unified API. It answers "are you allowed to do this?" and integrates natively with the Forge ecosystem.

Features

  • RBAC — Roles, permissions, role inheritance, resource-scoped assignments
  • ABAC — Attribute-based policies with conditions (IP ranges, time windows, regex, etc.)
  • ReBAC — Zanzibar-style relation tuples with BFS graph walking
  • Multi-model — Use RBAC, ABAC, and ReBAC together; explicit deny > allow > default deny
  • Multi-tenant — All data is tenant-scoped via forge.Scope or standalone context helpers
  • Extensible — Plugin hooks for audit logging, metrics, and custom lifecycle events
  • Caching — Built-in in-memory LRU cache with TTL and per-tenant/subject invalidation
  • Forge native — Drop-in Forge extension with DI, API routes, and middleware

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/xraph/warden"
    "github.com/xraph/warden/assignment"
    "github.com/xraph/warden/id"
    "github.com/xraph/warden/permission"
    "github.com/xraph/warden/role"
    "github.com/xraph/warden/store/memory"
)

func main() {
    ctx := warden.WithTenant(context.Background(), "myapp", "tenant-1")
    s := memory.New()

    eng, err := warden.NewEngine(warden.WithStore(s))
    if err != nil {
        log.Fatal(err)
    }

    // Create role + permission.
    roleID := id.NewRoleID()
    _ = s.CreateRole(ctx, &role.Role{ID: roleID, TenantID: "tenant-1", Name: "editor", Slug: "editor"})
    _ = s.CreatePermission(ctx, &permission.Permission{ID: id.NewPermissionID(), TenantID: "tenant-1", Name: "doc:read", Resource: "doc", Action: "read"})
    _ = s.AttachPermission(ctx, "tenant-1", roleID, permission.Ref{Name: "doc:read"})

    // Assign role to user.
    _ = s.CreateAssignment(ctx, &assignment.Assignment{
        ID: id.NewAssignmentID(), TenantID: "tenant-1",
        RoleID: roleID, SubjectKind: "user", SubjectID: "alice",
    })

    // Check authorization.
    result, err := eng.Check(ctx, &warden.CheckRequest{
        Subject:  warden.Subject{Kind: warden.SubjectUser, ID: "alice"},
        Action:   warden.Action{Name: "read"},
        Resource: warden.Resource{Type: "doc", ID: "d1"},
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Allowed: %v\n", result.Allowed) // true
}

Installation

Library
go get github.com/xraph/warden
CLI (warden and warden-lsp)

Using go install — recommended if you have a Go toolchain:

go install github.com/xraph/warden/cmd/warden@latest
go install github.com/xraph/warden/cmd/warden-lsp@latest

Pre-built binaries — multi-platform archives (linux / darwin / windows × amd64 / arm64) are attached to every GitHub Release. Each archive contains both warden and warden-lsp.

# Pick a platform from https://github.com/xraph/warden/releases/latest
curl -L -o warden.tar.gz \
  https://github.com/xraph/warden/releases/latest/download/warden-<version>-<os>-<arch>.tar.gz

# Verify the checksum
curl -L -o checksums.txt \
  https://github.com/xraph/warden/releases/latest/download/checksums.txt
shasum -a 256 -c checksums.txt --ignore-missing

tar -xzf warden.tar.gz
sudo mv warden warden-lsp /usr/local/bin/
warden version
VS Code extension
code --install-extension xraph.vscode-warden

Or search for "Warden" in the Extensions panel. See editor/vscode-warden/README.md for a walkthrough.

Architecture

Check Request
     |
     v
+----------+     +---------+     +---------+
|   RBAC   | --> |  ReBAC  | --> |  ABAC   |
| roles &  |     | relation|     | policy  |
| perms    |     | tuples  |     | engine  |
+----------+     +---------+     +---------+
     |                |               |
     +-------+--------+-------+------+
             |                 |
       mergeDecisions    explicit deny
             |            > allow
             v            > default deny
       CheckResult

The engine evaluates all three models and merges results:

  1. Explicit deny (from ABAC) always wins
  2. Any allow from any model grants access
  3. Default deny if no rules match

Authorization Models

RBAC (Role-Based Access Control)

Roles contain permissions. Users are assigned roles (globally or scoped to specific resources). Roles support inheritance via parent chains.

ReBAC (Relationship-Based Access Control)

Zanzibar-style relation tuples (object#relation@subject) with BFS graph walking for transitive permissions. Subject sets enable hierarchical access (e.g., folder membership granting document access).

ABAC (Attribute-Based Access Control)

Policies with conditions evaluate against subject attributes, resource attributes, and request context. Supported operators: eq, neq, in, not_in, contains, starts_with, gt, lt, ip_in_cidr, time_after, time_before, regex, and more.

Store Backends

Warden ships with four pluggable store backends. All implement the composite Store interface from store/store.go.

Backend Package Use case
Memory store/memory Testing, development
PostgreSQL store/postgres Production (Grove ORM, migrations, transactions)
SQLite store/sqlite Embedded / edge deployments (Grove ORM)
MongoDB store/mongo NoSQL / document-oriented workloads (BSON, compound indexes)
import "github.com/xraph/warden/store/memory"
import "github.com/xraph/warden/store/postgres"
import "github.com/xraph/warden/store/sqlite"
import "github.com/xraph/warden/store/mongo"

// Memory (no config needed)
s := memory.New()

// PostgreSQL / SQLite — pass a Grove database instance
s := postgres.New(groveDB)

// MongoDB — pass a Grove MongoDB instance
s := mongo.New(mongoDB)

// All stores support Migrate() for schema setup
_ = s.Migrate(ctx)

Plugin System

Warden provides a granular plugin system with 19 lifecycle hooks. Plugins implement the base plugin.Plugin interface (just Name() string) and opt in to specific hooks by implementing additional interfaces. Every hook takes a context.Context first and returns an error; request/result values that would otherwise require importing the root warden package (e.g. *warden.CheckRequest) are typed any to avoid an import cycle.

import "github.com/xraph/warden/plugin"

type AuditPlugin struct{}

func (p *AuditPlugin) Name() string { return "audit" }

// Opt in to the hooks you care about:
func (p *AuditPlugin) OnRoleCreated(ctx context.Context, r *role.Role) error {
    log.Printf("role created: %s", r.Name)
    return nil
}
func (p *AuditPlugin) OnAfterCheck(ctx context.Context, req, result any) error {
    log.Printf("check completed")
    return nil
}

The in-tree plugin/auditlog package implements Audit and logs one structured line per mutation; the Forge extension registers it by default (auth.audit_log: true).

Available Hooks
Category Hook Trigger
Check OnBeforeCheck(ctx, req any) Before authorization evaluation
Check OnAfterCheck(ctx, req, result any) After authorization evaluation
Roles OnRoleCreated(ctx, *role.Role) Role created
Roles OnRoleUpdated(ctx, *role.Role) Role modified
Roles OnRoleDeleted(ctx, id.RoleID) Role removed
Permissions OnPermissionCreated(ctx, *permission.Permission) Permission created
Permissions OnPermissionDeleted(ctx, id.PermissionID) Permission removed
Permissions OnPermissionAttached(ctx, id.RoleID, id.PermissionID) Permission attached to role
Permissions OnPermissionDetached(ctx, id.RoleID, id.PermissionID) Permission detached from role
Assignments OnRoleAssigned(ctx, *assignment.Assignment) Role assigned to subject
Assignments OnRoleUnassigned(ctx, *assignment.Assignment) Role unassigned from subject
Relations OnRelationWritten(ctx, *relation.Tuple) Relation tuple created
Relations OnRelationDeleted(ctx, id.RelationID) Relation tuple removed
Policies OnPolicyCreated(ctx, *policy.Policy) ABAC policy created
Policies OnPolicyUpdated(ctx, *policy.Policy) ABAC policy modified
Policies OnPolicyDeleted(ctx, id.PolicyID) ABAC policy removed
Policies OnPolicyObligationFired(ctx, id.PolicyID, obligation string, req, result any) Obligation emitted by a matched policy
Audit OnAudit(ctx, plugin.Event) Any mutation above, once per typed hook plus this one
Lifecycle OnShutdown(ctx) Engine shutting down

Hook errors and panics are logged, counted against warden_hook_errors_total{hook,plugin}, and never block the caller. See Plugin System for the full interface definitions.

Caching

Warden includes a built-in in-memory LRU cache for Check() results with TTL-based expiration and scoped invalidation.

import "github.com/xraph/warden/cache"

c := cache.NewMemory(
    cache.WithTTL(5 * time.Minute),   // Default: 5m
    cache.WithMaxSize(10000),          // Default: 10,000 entries
)

eng, _ := warden.NewEngine(
    warden.WithStore(s),
    warden.WithCache(c),
)

Cache keys are scoped by tenantID:subjectKind:subjectID:action:resourceType:resourceID.

Invalidation
// Invalidate all cached results for a tenant.
c.InvalidateTenant(ctx, "tenant-1")

// Invalidate all cached results for a specific subject.
c.InvalidateSubject(ctx, "tenant-1", warden.SubjectUser, "alice")

The cache automatically invalidates when roles, permissions, or assignments are modified through the engine.

Middleware

Warden provides HTTP middleware for Forge route protection.

import wardenmw "github.com/xraph/warden/middleware"

// Require a single permission — returns 403 if denied.
router.GET("/documents/:id", handler,
    forge.WithMiddleware(wardenmw.Require(eng, "read", "document")),
)

// RequireAny — allows if ANY check passes (OR logic).
router.POST("/admin/action", handler,
    forge.WithMiddleware(wardenmw.RequireAny(eng, []warden.CheckRequest{
        {Action: warden.Action{Name: "admin"}, Resource: warden.Resource{Type: "system"}},
        {Action: warden.Action{Name: "write"}, Resource: warden.Resource{Type: "config"}},
    })),
)

// RequireAll — allows only if ALL checks pass (AND logic).
router.DELETE("/documents/:id", handler,
    forge.WithMiddleware(wardenmw.RequireAll(eng, []warden.CheckRequest{
        {Action: warden.Action{Name: "delete"}, Resource: warden.Resource{Type: "document"}},
        {Action: warden.Action{Name: "admin"}, Resource: warden.Resource{Type: "document"}},
    })),
)

Subject resolution:

  1. forge.UserIDFromContext() resolves to warden.Subject{Kind: warden.SubjectUser, ID: userID}.
  2. No resolved user ID: 401 Unauthorized, unless wardenmw.AllowAnonymous() was passed, in which case the request runs as the fixed subject {Kind: warden.SubjectUser, ID: "anonymous"}.

There's no API-key branch yet: Forge doesn't currently expose one in context, so an API-key-only caller resolves as "no identity" here. See Middleware for WithContext and the 401/403/503 response semantics.

Configuration

eng, err := warden.NewEngine(
    warden.WithStore(store),                  // Required: store backend
    warden.WithCache(cache),                  // Optional: check result cache
    warden.WithConfig(warden.Config{
        MaxGraphDepth: 10,                    // ReBAC graph traversal depth (default: 10)
        CacheTTL:      5 * time.Minute,       // Check result cache TTL (0 = disabled)
        // EnableRBAC, EnableABAC, EnableReBAC are *bool; nil (the zero
        // value) means "default true". Take the address of a local
        // variable to turn one off: see docs/authorization/check-engine.mdx.
    }),
    warden.WithPlugin(auditPlugin),           // Optional: lifecycle plugins
    warden.WithEvaluator(customEvaluator),    // Optional: custom ABAC evaluator
    warden.WithGraphWalker(customWalker),     // Optional: custom ReBAC graph walker
    warden.WithLogger(logger),                // Optional: structured logger
)

ID System

Warden uses TypeID — UUIDv7-based, K-sortable, URL-safe identifiers with type prefixes for all entities.

role_01h2xcejqtf2nbrexx3vqjhp41
perm_01h2xcejqtf2nbrexx3vqjhp41
asgn_01h2xcejqtf2nbrexx3vqjhp41
Prefixes
Prefix Entity Constructor
role Role id.NewRoleID()
perm Permission id.NewPermissionID()
asgn Assignment id.NewAssignmentID()
wpol Policy id.NewPolicyID()
rel Relation id.NewRelationID()
chklog Check log id.NewCheckLogID()
rtype Resource type id.NewResourceTypeID()
cond Condition id.NewConditionID()
import "github.com/xraph/warden/id"

// Create
roleID := id.NewRoleID()       // role_01h2xce...
permID := id.NewPermissionID() // perm_01h2xce...

// Parse
parsed, err := id.ParseRoleID("role_01h2xcejqtf2nbrexx3vqjhp41")

// Check
if roleID.IsNil() { /* zero-value */ }

// Database support: SQL (Value/Scan), BSON (MarshalBSONValue/UnmarshalBSONValue)

Forge Integration

import (
    "github.com/xraph/forge"
    wardenext "github.com/xraph/warden/extension"
    wardenmw "github.com/xraph/warden/middleware"
)

// Register as Forge extension.
app := forge.New(
    forge.WithExtensions(
        wardenext.New(
            wardenext.WithStore(store),
        ),
    ),
)

// Use middleware for route protection.
router.GET("/documents/:id", handler,
    forge.WithMiddleware(wardenmw.Require(eng, "read", "document")),
)

Examples

See the _examples/ directory:

  • _examples/standalone/ — Warden without Forge
  • _examples/forge/ — Warden as Forge extension
  • _examples/rbac/ — Pure RBAC with role inheritance
  • _examples/rebac/ — Zanzibar-style ReBAC
  • _examples/abac/ — Attribute-based policies

License

Part of the Forge ecosystem.

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

View Source
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

View Source
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")
)
View Source
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

func AncestorNamespaces(path string) []string

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

func IsAncestorOrSelf(ancestor, path string) bool

IsAncestorOrSelf reports whether ancestor is path itself or a strict ancestor of path. Both must be valid namespace paths (or empty string).

func JoinNamespace

func JoinNamespace(parent, child string) string

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

func NamespaceFromContext(ctx context.Context) string

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

func ScopeFromContext(ctx context.Context) (appID, tenantID string)

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

func ValidateNamespacePath(path string, maxDepth int) error

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

func WithActor(ctx context.Context, a Actor) context.Context

WithActor returns a context carrying an explicit Actor. Takes priority over any actor inferred from ActorFromContext's fallback chain.

func WithNamespace

func WithNamespace(ctx context.Context, namespacePath string) context.Context

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

func WithRequestIP(ctx context.Context, ip string) context.Context

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.

func WithTenant

func WithTenant(ctx context.Context, appID, tenantID string) context.Context

WithTenant returns a context with the given app and tenant IDs. Use this for standalone mode (without Forge).

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

func ActorFromContext(ctx context.Context) (Actor, bool)

ActorFromContext returns the Actor for the current request.

Resolution order:

  1. An explicit Actor set via WithActor.
  2. log.UserIDFromContext(ctx), wrapped as {Kind: "user", ID: uid, Via: "forge"}.
  3. 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

func (c Config) Validate() error

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 NewEngine

func NewEngine(opts ...Option) (*Engine, error)

NewEngine creates a new Warden engine with the given options.

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) Config

func (e *Engine) Config() Config

Config returns the engine configuration.

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) Health

func (e *Engine) Health(ctx context.Context) error

Health checks the health of the engine by pinging its store.

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

func (e *Engine) InvalidateTenant(ctx context.Context, tenantID string)

InvalidateTenant clears every cached Check result for a tenant. A no-op when no Cache is configured.

func (*Engine) Plugins

func (e *Engine) Plugins() *plugin.Registry

Plugins returns the plugin registry (may be nil).

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

func (e *Engine) Start(_ context.Context) error

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

func (e *Engine) StartMaintenance(ctx context.Context)

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

func (e *Engine) Stop(ctx context.Context) error

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) Store

func (e *Engine) Store() store.Store

Store returns the underlying composite store.

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

func NewConditionEvaluator(now func() time.Time) Evaluator

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

type ExpandEdge struct {
	From, To      int // indexes into Nodes
	NamespacePath string
}

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

func (x *Expansion) PathTo(subjectType, subjectID string) string

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 ID

type ID = id.ID

ID is the primary identifier type for all Warden entities.

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

type MaintenanceReport struct {
	AssignmentsPurged int64
	CheckLogsPurged   int64
}

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) CheckEvaluated(Decision, string, time.Duration, bool)

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 WithCache

func WithCache(c Cache) Option

WithCache sets the check result cache.

func WithConfig

func WithConfig(c Config) Option

WithConfig sets the engine configuration.

func WithEvaluator

func WithEvaluator(ev Evaluator) Option

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 WithLogger

func WithLogger(l log.Logger) Option

WithLogger sets the structured logger.

func WithMetrics added in v1.7.0

func WithMetrics(m Metrics) Option

WithMetrics sets the engine's metrics sink.

func WithPlugin

func WithPlugin(x plugin.Plugin) Option

WithPlugin registers a plugin with the engine.

func WithStore

func WithStore(s store.Store) Option

WithStore sets the composite store.

type Prefix

type Prefix = id.Prefix

Prefix identifies the entity type encoded in a TypeID.

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"
)

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.

Jump to

Keyboard shortcuts

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