authz

package
v1.27.0 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package authz provides authorization for the Nucleus framework using Casbin. It wraps Casbin's enforcer with sensible RBAC defaults and provides Chi middleware for route-level access control.

Index

Constants

View Source
const BootstrapSubject = "anonymous"

BootstrapSubject is the subject used by SeedBootstrapAllowList and by the default-deny middleware in pkg/app when no JWT claims are present on a request. Operators can grant or deny access for unauthenticated callers by writing policies against this subject; framework-owned routes registered by SeedBootstrapAllowList are the canonical example.

Variables

This section is empty.

Functions

func BootstrapAllowList

func BootstrapAllowList() []struct{ Object, Action string }

BootstrapAllowList returns the routes the framework registers under BootstrapSubject before any user policy file loads. These are paths the framework itself owns and that must respond without authorization — Kubernetes probes, Prometheus scrapes, the login flow, and the static assets that the runtime mounts. Operators cannot override this list via config; removing an entry requires a code change.

Returned as ((object, action) tuples); the subject is implicit (BootstrapSubject) and the action is "*" because these routes serve every HTTP method that the underlying handler accepts.

Types

type ActionResolver

type ActionResolver func(r *http.Request) string

ActionResolver derives the policy action (the act in Enforcer.Can) from a request. Supply one via AuthzOptions to express actions the HTTP method alone cannot — e.g. a pure-HTML SSR form POSTs to both update and delete routes, so a resolver maps a POST whose path ends in /delete to "delete" rather than the default "create". When nil, Middleware maps the HTTP method (GET→read, POST→create, PUT/PATCH→update, DELETE→delete).

type AuthzOptions

type AuthzOptions struct {
	// OnDeny, when non-nil, is invoked instead of writing the default JSON
	// error envelope whenever a request is rejected (whether unauthenticated
	// or forbidden). SSR applications set this; JSON APIs leave it nil.
	OnDeny DenialHandler

	// ResolveSubject, when non-nil, overrides the policy subject Middleware
	// checks (default: claims.UserID). RequireRole ignores it — it matches the
	// claim's role directly, not through the policy store.
	ResolveSubject SubjectResolver

	// ResolveAction, when non-nil, overrides the policy action Middleware checks
	// (default: the HTTP-method mapping). RequireRole ignores it.
	ResolveAction ActionResolver
}

AuthzOptions configures how Middleware/RequireRole answer a denial and, for Middleware, how the policy subject and action are derived. The zero value preserves the default behaviour (a JSON error envelope, subject = claims.UserID, action = HTTP method), so existing callers are unaffected.

type CSVMigrationReport

type CSVMigrationReport struct {
	// Path is the input file that was migrated.
	Path string
	// PolicyLinesUpgraded counts `p, sub, obj, act` rows that received an
	// `eft` column. Lines already in 4-column form are not counted.
	PolicyLinesUpgraded int
	// PolicyLinesAlreadyMigrated counts `p, sub, obj, act, eft` rows that
	// were left untouched because they already had the effect column.
	PolicyLinesAlreadyMigrated int
	// GroupingLinesPreserved counts `g, ...` rows; grouping policies have
	// no eft column under the default model and are never rewritten.
	GroupingLinesPreserved int
	// BlankOrCommentLines counts blank lines and `#`-prefixed comments
	// preserved verbatim.
	BlankOrCommentLines int
	// Changed reports whether the file on disk was rewritten. False means
	// every policy line was already in the post-migration shape.
	Changed bool
}

CSVMigrationReport summarizes what MigrateCSVPolicyFile did.

func MigrateCSVPolicyFile

func MigrateCSVPolicyFile(path, defaultEffect string) (CSVMigrationReport, error)

MigrateCSVPolicyFile rewrites a Casbin RBAC CSV policy file in place so every `p` row carries an `eft` column compatible with the deny-override model introduced alongside ADR-004.

Behaviour, line by line:

  • blank lines and `#` comments are preserved verbatim
  • `g, user, role` (and any non-`p` ptype) is preserved verbatim
  • `p, sub, obj, act` (exactly four fields) is rewritten to `p, sub, obj, act, <defaultEffect>`
  • `p, sub, obj, act, eft` (five or more fields) is preserved verbatim

The function is idempotent: running it twice produces the same file as running it once, and it does not rewrite the file if no upgrade is needed (Changed=false in that case).

defaultEffect must be either `allow` or `deny`. Empty string is treated as `allow` because every existing 3-column policy was conceptually an allow rule under the legacy single-effect Casbin model.

The rewrite is atomic: the new content is written to a sibling temp file in the same directory and renamed over the original.

type Denial

type Denial struct {
	// Status is the HTTP status the default renderer would use:
	// http.StatusUnauthorized (401) when no identity was present, or
	// http.StatusForbidden (403) when the request was authenticated but not
	// permitted.
	Status int

	// Authenticated reports whether the request carried identity (JWT claims).
	// When false the visitor is anonymous — a login redirect is usually the
	// right response; when true they are signed in but lack the role or
	// permission — render a 403.
	Authenticated bool

	// Reason is the human-readable explanation (e.g. "insufficient role").
	Reason string
}

Denial describes an authorization rejection handed to a DenialHandler. It lets an application choose how to answer — redirect an anonymous visitor to a login page, or render a styled 403 for a signed-in user who lacks the required role/permission — instead of the default JSON error envelope. This is what makes the authz middlewares usable from a server-rendered (SSR) UI, not only a JSON API.

type DenialHandler

type DenialHandler func(w http.ResponseWriter, r *http.Request, d Denial)

DenialHandler answers an authorization denial. Supply one via AuthzOptions to render a page or issue a redirect; when nil, the middleware writes the default JSON error envelope. The handler owns the response from this point — it must write a status and body and must not call the next handler. If it returns without writing a status, net/http sends an empty 200 OK, which a client reads as success; either way the protected handler is NOT called. The signature deliberately omits next — do not capture the wrapped handler in a closure and call it from OnDeny, which would grant access.

type Enforcer

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

Enforcer wraps a Casbin enforcer with logging and convenience methods.

The underlying *casbin.Enforcer is held in an unexported field rather than embedded so that Casbin's concrete type and its full method set do not leak onto this stable public surface (ADR-015, F-4). Every Casbin capability Nucleus exposes is forwarded by an explicit method below.

func New

func New(logger *slog.Logger, policyPath ...string) (*Enforcer, error)

New creates an Enforcer with the default RBAC model. If policyPath is provided, policies are loaded from that CSV file. If policyPath is empty, no policies are loaded (add them programmatically).

func NewFromModel

func NewFromModel(modelStr string, logger *slog.Logger, policyPath ...string) (*Enforcer, error)

NewFromModel creates an Enforcer with a custom Casbin model string.

func (*Enforcer) AddPolicy

func (e *Enforcer) AddPolicy(sub, obj, act string) error

AddPolicy adds an allow policy (subject, object, action). The eft column is auto-stamped to `allow`. To add a deny rule, use Deny.

func (*Enforcer) AddRole

func (e *Enforcer) AddRole(user, role string) error

AddRole assigns a role to a user (e.g. AddRole("alice", "admin")).

func (*Enforcer) AllowAll

func (e *Enforcer) AllowAll(role string) error

AllowAll grants the given role full access to all resources.

func (*Enforcer) AllowResource

func (e *Enforcer) AllowResource(role, resource string, actions ...string) error

AllowResource grants a role access to a specific resource pattern with actions.

func (*Enforcer) Can

func (e *Enforcer) Can(sub, obj, act string) bool

Can checks if the subject is allowed to perform the action on the object.

func (*Enforcer) Deny

func (e *Enforcer) Deny(sub, obj, act string) error

Deny adds an explicit deny policy (subject, object, action). Deny rules override every matching allow rule under the model's deny-override effect formula, so this is the primitive for "block this user even though their role normally has access".

func (*Enforcer) GetAllRoles

func (e *Enforcer) GetAllRoles() ([]string, error)

GetAllRoles returns every role referenced by a grouping policy.

func (*Enforcer) GetGroupingPolicy

func (e *Enforcer) GetGroupingPolicy() ([][]string, error)

GetGroupingPolicy returns all role-assignment (grouping) rules as (user, role) string tuples.

func (*Enforcer) GetPolicy

func (e *Enforcer) GetPolicy() ([][]string, error)

GetPolicy returns all permission policy rules as (subject, object, action, eft) string tuples. It forwards Casbin's policy store without exposing any Casbin type, so callers (e.g. the admin RBAC inspector) can read the live ruleset.

func (*Enforcer) GetRoles

func (e *Enforcer) GetRoles(user string) []string

GetRoles returns all roles assigned to a user.

func (*Enforcer) Middleware

func (e *Enforcer) Middleware() func(http.Handler) http.Handler

Middleware returns a Chi middleware that checks authorization using the enforcer. It extracts the user identity from the JWT claims in the context (set by auth.JWTManager.Middleware), derives the resource from the URL path, and maps the HTTP method to a CRUD action. Denials are answered with a JSON error envelope; for an SSR-friendly page or redirect, use MiddlewareWithOptions.

func (*Enforcer) MiddlewareWithOptions

func (e *Enforcer) MiddlewareWithOptions(opts AuthzOptions) func(http.Handler) http.Handler

MiddlewareWithOptions is Middleware with a configurable denial response and, optionally, custom subject/action derivation. opts.OnDeny replaces the default JSON envelope on every rejection; opts.ResolveSubject and opts.ResolveAction override how the policy subject and action are derived from the request (defaults: claims.UserID and the HTTP-method mapping). The zero AuthzOptions value preserves Middleware's behaviour exactly.

func (*Enforcer) RemovePolicy

func (e *Enforcer) RemovePolicy(sub, obj, act string) error

RemovePolicy removes a permission policy regardless of its eft. Both allow and deny variants matching (sub, obj, act) are dropped, which matches operator intent — "stop applying this rule" should not require knowing how the rule was originally written.

func (*Enforcer) RemoveRole

func (e *Enforcer) RemoveRole(user, role string) error

RemoveRole removes a role assignment from a user.

func (*Enforcer) RequireRole

func (e *Enforcer) RequireRole(roles ...string) func(http.Handler) http.Handler

RequireRole returns middleware that checks if the authenticated user has one of the specified roles. Denials are answered with a JSON error envelope; for an SSR-friendly page or redirect, use RequireRoleWithOptions.

func (*Enforcer) RequireRoleWithOptions

func (e *Enforcer) RequireRoleWithOptions(opts AuthzOptions, roles ...string) func(http.Handler) http.Handler

RequireRoleWithOptions is RequireRole with a configurable denial response. When opts.OnDeny is set it is invoked on every rejection (instead of the default JSON envelope); otherwise the behaviour is identical to RequireRole.

func (*Enforcer) SeedBootstrapAllowList

func (e *Enforcer) SeedBootstrapAllowList() error

SeedBootstrapAllowList programmatically adds the BootstrapAllowList entries to the enforcer under BootstrapSubject. pkg/app calls this during App.New (before mounting the default authz middleware) so the framework's own probe / login routes respond without authorization regardless of whether the operator has loaded a user policy file.

func (*Enforcer) SeedBootstrapAllowListExcluding

func (e *Enforcer) SeedBootstrapAllowListExcluding(skip ...string) error

SeedBootstrapAllowListExcluding is SeedBootstrapAllowList with an operator-driven subtraction: entries whose Object equals one of skip are not seeded, so those routes fall under the default-deny policy like any user route. pkg/app uses it to honor `metrics_public: false` — the metrics path stays out of the anonymous allow-list and answers only under an explicit policy grant.

func (*Enforcer) SetupAdminPolicies

func (e *Enforcer) SetupAdminPolicies(prefix string, modelNames ...string) error

SetupAdminPolicies configures common admin policies for a set of model names, in the URL shape of the Orbit panel: `<prefix>/api/models/<name>/*` per model, plus `<prefix>/*` read for admins. The "admin" role gets full access to every model and "viewer" read-only. The path layout is the panel's, not this package's (the admin was extracted to Orbit); for any other resource layout use SetupModelPolicies with the full prefix.

func (*Enforcer) SetupModelPolicies

func (e *Enforcer) SetupModelPolicies(resourcePrefix string, modelNames ...string) error

SetupModelPolicies grants "admin" full access and "viewer" read access to `<resourcePrefix>/<name>/*` for each model name, with no assumption about where the models are served.

type SubjectResolver

type SubjectResolver func(r *http.Request, claims *auth.Claims) string

SubjectResolver derives the policy subject (the sub in Enforcer.Can) from a request and its claims. Supply one via AuthzOptions to check policies keyed by something other than the user id — e.g. an app whose policy table is keyed by role (rather than by user via Casbin grouping rules) returns claims.Role. When nil, Middleware uses claims.UserID.

Jump to

Keyboard shortcuts

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