extension

package
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: 26 Imported by: 0

Documentation

Overview

Package extension provides a Forge extension entry point for Warden.

It implements the forge.Extension interface to integrate Warden into a Forge application with automatic dependency discovery, route registration, and lifecycle management.

Configuration can be provided programmatically via Option functions or via YAML configuration files under "extensions.warden" or "warden" keys.

Index

Constants

View Source
const ExtensionDescription = "Composable permissions & authorization engine (RBAC, ABAC, ReBAC)"

ExtensionDescription is the human-readable description.

View Source
const ExtensionName = "warden"

ExtensionName is the name registered with Forge.

View Source
const ExtensionVersion = "0.1.0"

ExtensionVersion is the semantic version.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthConfig added in v1.7.0

type AuthConfig struct {
	// RequireIdentity, when true (the default), makes every route 401 an
	// unauthenticated caller, except the three check endpoints when
	// AllowAnonymousChecks is on. Turning it off is refused wherever the
	// API can be mounted unless WithInsecureAllowUnauthenticatedRoutes was
	// also passed, so nobody disables it by accident: with routes enabled
	// Register returns an error, and with routes disabled Handler panics
	// and RegisterRoutes returns the error. A code-set false alone does
	// not turn it off (see AuthConfig).
	RequireIdentity bool `json:"require_identity" mapstructure:"require_identity" yaml:"require_identity"`

	// AllowAnonymousChecks opens the three check endpoints
	// (/v1/authz/check, /v1/authz/enforce, /v1/authz/batch-check) to
	// callers with no resolved identity. Every other route, including
	// AuthZEN, still requires one. Off by default.
	AllowAnonymousChecks bool `json:"allow_anonymous_checks" mapstructure:"allow_anonymous_checks" yaml:"allow_anonymous_checks"`

	// AuditLog registers the in-tree plugin/auditlog sink by default,
	// logging one structured line per audit event through the extension
	// logger. On by default; set to false to opt out (e.g. because a
	// different Audit plugin is registered instead).
	AuditLog bool `json:"audit_log" mapstructure:"audit_log" yaml:"audit_log"`
}

AuthConfig controls how the extension authenticates and authorizes callers of the warden management/decision HTTP API.

An AuthConfig set in code with all three fields false is the zero value, and the extension reads it as unset: it gets DefaultAuthConfig, so identity stays required and the audit log stays on. To turn identity off from code, set another field as well (AuditLog: true, for example). In YAML, auth.require_identity: false is enough on its own.

func DefaultAuthConfig added in v1.7.0

func DefaultAuthConfig() AuthConfig

DefaultAuthConfig returns the AuthConfig defaults: identity required, anonymous checks off, audit log on.

type Config

type Config struct {
	// DisableRoutes prevents HTTP route registration.
	DisableRoutes bool `json:"disable_routes" mapstructure:"disable_routes" yaml:"disable_routes"`

	// DisableMigrate prevents auto-migration on start.
	DisableMigrate bool `json:"disable_migrate" mapstructure:"disable_migrate" yaml:"disable_migrate"`

	// BasePath is the URL prefix for warden routes (default: "/warden").
	BasePath string `json:"base_path" mapstructure:"base_path" yaml:"base_path"`

	// MaxGraphDepth controls the maximum depth for ReBAC graph traversal.
	MaxGraphDepth int `json:"max_graph_depth" mapstructure:"max_graph_depth" yaml:"max_graph_depth"`

	// GroveDatabase is the name of a grove.DB registered in the DI container.
	// When set, the extension resolves this named database and auto-constructs
	// the appropriate store based on the driver type (pg/sqlite/mongo).
	// When empty and WithGroveDatabase was called, the default (unnamed) DB is used.
	GroveDatabase string `json:"grove_database" mapstructure:"grove_database" yaml:"grove_database"`

	// RequireConfig requires config to be present in YAML files.
	// If true and no config is found, Register returns an error.
	RequireConfig bool `json:"-" yaml:"-"`

	// DeclarativePath is a single .warden file, directory, or glob pattern
	// to load. When unset, declarative auto-apply is skipped.
	DeclarativePath string `json:"declarative_path" mapstructure:"declarative_path" yaml:"declarative_path"`

	// DeclarativePaths is a list of paths to load (in addition to
	// DeclarativePath). Useful for splitting tenant-root and per-tenant
	// configs across multiple roots.
	DeclarativePaths []string `json:"declarative_paths" mapstructure:"declarative_paths" yaml:"declarative_paths"`

	// DeclarativeOnStart toggles auto-apply at Start time.
	DeclarativeOnStart bool `json:"declarative_on_start" mapstructure:"declarative_on_start" yaml:"declarative_on_start"`

	// DeclarativePrune deletes tenant entries not present in the config.
	// kubectl-style apply with prune. Defaults to false; opt-in only.
	DeclarativePrune bool `json:"declarative_prune" mapstructure:"declarative_prune" yaml:"declarative_prune"`

	// DeclarativeStrict makes startup fail when the apply produces
	// diagnostics. When false (default), errors are logged but startup
	// continues so a misconfigured tenant doesn't crash the app.
	DeclarativeStrict bool `json:"declarative_strict" mapstructure:"declarative_strict" yaml:"declarative_strict"`

	// DeclarativeTenantID overrides any `tenant <id>` declared in source.
	// Useful for multi-tenant deployments where the same source is applied
	// to many tenants.
	DeclarativeTenantID string `json:"declarative_tenant_id" mapstructure:"declarative_tenant_id" yaml:"declarative_tenant_id"`

	// Auth controls authentication/authorization of the management API
	// itself. See AuthConfig.
	Auth AuthConfig `json:"auth" mapstructure:"auth" yaml:"auth"`

	// CacheTTL is the time-to-live for cached check results. When >0, the
	// engine builds its memory cache from CacheTTL/CacheMaxSize (unless
	// WithEngineOptions already supplied a WithCache).
	CacheTTL time.Duration `json:"cache_ttl" mapstructure:"cache_ttl" yaml:"cache_ttl"`

	// CacheMaxSize bounds the auto-built memory cache's entry count.
	CacheMaxSize int `json:"cache_max_size" mapstructure:"cache_max_size" yaml:"cache_max_size"`

	// MaxGraphVisited caps the number of distinct nodes a ReBAC graph walk
	// may visit before it aborts with ErrGraphBudgetExceeded.
	MaxGraphVisited int `json:"max_graph_visited" mapstructure:"max_graph_visited" yaml:"max_graph_visited"`

	// MaxGraphFanout caps the number of subjects fetched per hop during a
	// ReBAC graph walk.
	MaxGraphFanout int `json:"max_graph_fanout" mapstructure:"max_graph_fanout" yaml:"max_graph_fanout"`

	// EvaluateAllModels, when true, always evaluates RBAC, ReBAC and ABAC
	// even after an earlier model already allowed the request.
	EvaluateAllModels bool `json:"evaluate_all_models" mapstructure:"evaluate_all_models" yaml:"evaluate_all_models"`

	// CheckLogQueueSize bounds the check-log writer's internal channel.
	CheckLogQueueSize int `json:"check_log_queue_size" mapstructure:"check_log_queue_size" yaml:"check_log_queue_size"`

	// CheckLogRetention is how long check log entries are kept before
	// RunMaintenance purges them. A negative value turns purging off. 0
	// counts as unset: the extension fills it with the programmatic value,
	// else the 90 day default, so 0 cannot turn purging off here (an engine
	// built directly reads 0 as off too).
	CheckLogRetention time.Duration `json:"check_log_retention" mapstructure:"check_log_retention" yaml:"check_log_retention"`

	// MaintenanceInterval is how often the engine runs RunMaintenance in
	// the background. A negative value turns the loop off. 0 counts as
	// unset: the extension fills it with the programmatic value, else 1
	// hour, so 0 cannot turn the loop off here (an engine built directly
	// reads 0 as off too).
	MaintenanceInterval time.Duration `json:"maintenance_interval" mapstructure:"maintenance_interval" yaml:"maintenance_interval"`

	// MaxBatchChecks bounds the number of checks a batch-check or AuthZEN
	// evaluations API call may request in one call.
	MaxBatchChecks int `json:"max_batch_checks" mapstructure:"max_batch_checks" yaml:"max_batch_checks"`

	// RequireTenant, when true (the default), makes Check return
	// ErrTenantRequired whenever the resolved scope has no tenant ID. A
	// nil pointer means "use the engine's default (true)"; only an
	// explicit false disables the requirement.
	RequireTenant *bool `json:"require_tenant,omitempty" mapstructure:"require_tenant" yaml:"require_tenant"`

	// EnableCheckLog enables writing authorization check results to the
	// check log store. A nil pointer means "use the engine's default
	// (true)"; only an explicit false disables it.
	EnableCheckLog *bool `json:"enable_check_log,omitempty" mapstructure:"enable_check_log" yaml:"enable_check_log"`

	// Dashboard configures the React dashboard surfaces.
	Dashboard DashboardConfig `json:"dashboard" mapstructure:"dashboard" yaml:"dashboard"`
}

Config holds the Warden extension configuration. Fields can be set programmatically via Option functions or loaded from YAML configuration files (under "extensions.warden" or "warden" keys). A key the YAML section sets wins over the code-set value; a key it leaves out keeps the code-set value, else the default.

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 extension configuration for internally inconsistent values. It delegates the engine-knob checks to warden.Config.Validate via toEngineConfig, so the same bounds apply whether a value came from YAML, an Option, or a default.

type DashboardConfig added in v1.7.0

type DashboardConfig struct {
	// TenantID scopes every dashboard request when the caller's principal
	// carries no tenant claim.
	//
	// Nothing populates dashboard principal claims today, so in practice
	// this is what makes the dashboard answer at all. Set it for a
	// single-tenant deployment.
	//
	// Leave it empty in a multi-tenant deployment. Every read then refuses
	// with PERMISSION_DENIED, which is the correct behaviour: the empty
	// string in a store ListFilter matches every tenant's rows rather than
	// none, so guessing would serve one operator every tenant's data, and
	// on a check could return an allow the real tenant would have denied.
	TenantID string `json:"tenant_id" mapstructure:"tenant_id" yaml:"tenant_id"`
}

DashboardConfig configures warden's dashboard surfaces.

type Extension

type Extension struct {
	*forge.BaseExtension
	// contains filtered or unexported fields
}

Extension adapts Warden as a Forge extension.

func New

func New(opts ...Option) *Extension

New creates a Warden Forge extension with the given options.

func (*Extension) API

func (e *Extension) API() *api.API

API returns the API handler, or nil before Register. With auth.require_identity false and no WithInsecureAllowUnauthenticatedRoutes, it still requires an identity on every route, except the three check endpoints when auth.allow_anonymous_checks is on: the insecure opt-in is the only way to get an API that skips the identity check everywhere.

func (*Extension) BootstrapAdmin added in v1.7.0

func (e *Extension) BootstrapAdmin(ctx context.Context, tenantID string, subject warden.Subject) error

BootstrapAdmin creates (idempotently, by slug) a system role "warden-admin" in tenantID with the permissions the management API's default authorizer requires, and assigns it to subject.

Operators call this once per tenant, typically right after Start, to grant themselves access to an otherwise fully locked-down management API: with C1's default-deny authorizer, nobody has warden:*:manage until something grants it, and this is that something. Safe to call repeatedly (idempotent on the role slug, the permission names, the attachments and the assignment).

func (*Extension) Engine

func (e *Extension) Engine() *warden.Engine

Engine returns the underlying Warden engine.

func (*Extension) Handler

func (e *Extension) Handler() http.Handler

Handler returns a standalone http.Handler serving warden's API and nothing else, or a handler that answers 404 before Register.

The API is built on a router of its own, never the app's router, so it adds no routes or middleware to the app and does not clash with the base-path mount Register makes when routes are enabled. Routes sit at /v1/... and, for AuthZEN, /access/v1/... on that router, with no base path: mount the handler wherever you like, and strip your prefix first (http.StripPrefix("/authz", h) to serve /authz/v1/... and /authz/access/v1/...). The router is built on the first call, and every later call returns the same handler.

It panics when auth.require_identity is false and WithInsecureAllowUnauthenticatedRoutes was not passed, the same refusal Register returns when routes are enabled: handing out the API would otherwise mount it with no opt-in. It has no error to return, and api.API.Handler panics on a failed registration the same way.

func (*Extension) Health

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

Health implements forge.Extension.

func (*Extension) Register

func (e *Extension) Register(fapp forge.App) error

Register implements forge.Extension. It loads configuration, initializes the engine, registers it in the DI container, and optionally registers HTTP routes.

func (*Extension) RegisterContractContributor added in v1.7.0

func (e *Extension) RegisterContractContributor(
	disp *dispatcher.Dispatcher,
	reg dashcontract.Registry,
	wreg dashcontract.WardenRegistry,
) error

RegisterContractContributor implements the dashboard's contract auto-discovery. It registers the `warden` contributor so the React shell can read warden's intents.

func (*Extension) RegisterRoutes

func (e *Extension) RegisterRoutes(router forge.Router) error

RegisterRoutes registers all warden API routes into a Forge router. It registers nothing before Register.

It returns an error, and registers nothing, when auth.require_identity is false and WithInsecureAllowUnauthenticatedRoutes was not passed: the same refusal Register returns when routes are enabled.

func (*Extension) Start

func (e *Extension) Start(ctx context.Context) error

Start begins the warden engine and runs migrations if enabled.

func (*Extension) Stop

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

Stop gracefully shuts down the warden engine.

type Option

type Option func(*Extension)

Option configures the Warden Forge extension.

func WithBasePath

func WithBasePath(path string) Option

WithBasePath sets the URL prefix for warden routes.

func WithConfig

func WithConfig(cfg Config) Option

WithConfig sets the extension configuration.

func WithDisableMigrate

func WithDisableMigrate() Option

WithDisableMigrate disables auto-migration on start.

func WithDisableRoutes

func WithDisableRoutes() Option

WithDisableRoutes disables the registration of HTTP routes.

func WithEngineOptions

func WithEngineOptions(opts ...warden.Option) Option

WithEngineOptions adds engine-level options.

func WithGroveDatabase

func WithGroveDatabase(name string) Option

WithGroveDatabase sets the name of the grove.DB to resolve from the DI container. The extension will auto-construct the appropriate store backend (postgres/sqlite/mongo) based on the grove driver type. Pass an empty string to use the default (unnamed) grove.DB.

func WithInsecureAllowUnauthenticatedRoutes added in v1.7.0

func WithInsecureAllowUnauthenticatedRoutes() Option

WithInsecureAllowUnauthenticatedRoutes is the explicit, unmissable opt-in required to mount the HTTP API with auth.require_identity set to false. Without it, nothing mounts an unauthenticated management API: Register refuses to start when routes are enabled, Extension.Handler panics, Extension.RegisterRoutes returns an error, and the *api.API that Extension.API returns still requires an identity. Any network peer would otherwise be able to grant roles or read the audit log.

The name is deliberately long and says exactly what it does, so nobody reaches for it by accident while skimming a list of options.

func WithPlugin

func WithPlugin(x plugin.Plugin) Option

WithPlugin registers a lifecycle hook plugin.

func WithRequireConfig

func WithRequireConfig() Option

WithRequireConfig requires config to be present in YAML files. If true and no config is found, Register returns an error.

func WithStore

func WithStore(s store.Store) Option

WithStore sets the persistence backend.

Directories

Path Synopsis
authz.go: the authorization decision in front of every dashboard intent.
authz.go: the authorization decision in front of every dashboard intent.

Jump to

Keyboard shortcuts

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