Documentation
¶
Overview ¶
Package authz provides a unified authorization model for the dioad platform.
Capability model ¶
All permissions and feature entitlements are expressed as Capability values using the colon-separated convention inherited from Casbin:
- Feature entitlements: "feature:<name>" e.g. "feature:custom-domain"
- Resource:action permissions: "<resource>:<action>" e.g. "tunnel:write"
Use FeatureCapability and Permission to construct typed capabilities rather than bare string literals.
Backends ¶
Multiple Authorizer implementations are provided:
- AllowAllAuthorizer: dev/test bypass — always grants
- RoleAuthorizer: in-memory role→capability map (lightweight, config-driven)
- MapAuthorizer: principal-ID→PrivilegeSet map (inline config, testing)
- CasbinAuthorizer: Casbin v2 policy engine (production)
- MultiAuthorizer: first-non-nil chain
CasbinAuthorizer is the recommended production backend for both connect and connect-control.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrForbidden = errors.New("forbidden")
ErrForbidden is returned by Authorizer.Can when a policy decision denies the requested capability. Use errors.Is(err, ErrForbidden) for flow control. A non-nil *Decision accompanies ErrForbidden. On infrastructure failures (non-policy errors), err is non-nil but *Decision is nil.
ErrUnauthorized is returned by Authorizer.Can when the request cannot be evaluated because there is no authenticated principal, or the caller is otherwise not eligible for authorization. Use errors.Is(err, ErrUnauthorized) for flow control. This is distinct from ErrForbidden, which means a principal was evaluated and denied by policy.
Functions ¶
This section is empty.
Types ¶
type AllowAllAuthorizer ¶
type AllowAllAuthorizer struct {
// contains filtered or unexported fields
}
AllowAllAuthorizer is an Authorizer that grants every capability. It is intended for dev/test environments where RBAC enforcement is explicitly disabled via configuration. It must never be used in production.
func NewAllowAllAuthorizer ¶
func NewAllowAllAuthorizer(metadata PolicyMetadata) *AllowAllAuthorizer
NewAllowAllAuthorizer returns an AllowAllAuthorizer with the given policy metadata (used only for Metadata() introspection).
func (*AllowAllAuthorizer) Can ¶
func (a *AllowAllAuthorizer) Can(_ context.Context, _ *auth.PrincipalContext, cap Capability) (*Decision, error)
Can always returns an allowed Decision with ReasonAllowAll.
func (*AllowAllAuthorizer) Metadata ¶
func (a *AllowAllAuthorizer) Metadata() PolicyMetadata
Metadata returns the policy metadata provided at construction.
func (*AllowAllAuthorizer) Privileges ¶
func (a *AllowAllAuthorizer) Privileges(_ context.Context, _ *auth.PrincipalContext) (Privilege, error)
Privileges returns a Privilege that grants every capability.
type Authorizer ¶
type Authorizer interface {
// Privileges returns the full capability set for the principal. It returns
// nil when the principal has no recognised roles (not an error). Callers
// should treat a nil Privilege as "no capabilities".
Privileges(ctx context.Context, principalCtx *auth.PrincipalContext) (Privilege, error)
// Can checks whether the principal holds cap. A non-nil *Decision is
// returned for every policy outcome; nil only on infrastructure errors.
// Use errors.Is(err, ErrUnauthorized) to detect a missing principal, and
// errors.Is(err, ErrForbidden) to detect a policy denial.
Can(ctx context.Context, principalCtx *auth.PrincipalContext, cap Capability) (*Decision, error)
// Metadata returns the policy metadata for introspection.
Metadata() PolicyMetadata
}
Authorizer determines whether a principal may exercise a capability.
Flow control ¶
[Can] returns a non-nil *Decision for every policy outcome (allow or deny). It returns a nil *Decision only for infrastructure failures unrelated to policy.
Two sentinel errors signal distinct failure modes:
- ErrUnauthorized — the request could not be evaluated because there is no authenticated principal (principalCtx is nil). This corresponds to an HTTP 401 condition.
- ErrForbidden — the principal was authenticated but the policy denied the requested capability. This corresponds to an HTTP 403 condition; a non-nil *Decision with audit information always accompanies this error.
Example:
d, err := authorizer.Can(ctx, principalCtx, authz.Permission("tunnel", "write"))
if errors.Is(err, authz.ErrUnauthorized) {
// no principal — redirect to login
}
if errors.Is(err, authz.ErrForbidden) {
// denied by policy — d contains audit info
}
Connect pattern (fetch-once, check-many) ¶
[Privileges] fetches the full capability set for a principal once; the caller then checks individual capabilities inline using Privilege.Has. This is efficient for handlers that perform multiple capability checks. Privilege.Has follows the same wildcard semantics as [Can], including "resource:any" action wildcards and keyMatch-style resource patterns:
privs, _ := authorizer.Privileges(ctx, principalCtx)
if privs.Has(authz.FeatureCapability("custom-domain")) { ... }
if privs.Has(authz.Permission("tunnel", "write")) { ... }
func NewDefaultCasbinAuthorizer ¶
func NewDefaultCasbinAuthorizer(metadata PolicyMetadata, logger zerolog.Logger) (Authorizer, error)
NewDefaultCasbinAuthorizer attempts to create a CasbinAuthorizer from the given metadata. If Casbin initialisation fails, it falls back to a RoleAuthorizer and logs a warning so operators can investigate.
type AuthorizerConfig ¶
type AuthorizerConfig struct {
// RoleCapabilities is the list of role→capability assignments.
RoleCapabilities []RoleCapabilityConfig `mapstructure:"role-capabilities"`
// RoleAliases maps external token role names to internal role names.
// Only roles listed here (as keys) are accepted; others are dropped.
RoleAliases map[string]string `mapstructure:"role-aliases"`
// AllowAll disables all policy checks; every capability is granted.
// Must only be set in dev/test environments.
AllowAll bool `mapstructure:"allow-all"`
}
AuthorizerConfig is the top-level config for constructing an Authorizer from a YAML/mapstructure config source.
func (AuthorizerConfig) ToMetadata ¶
func (c AuthorizerConfig) ToMetadata() PolicyMetadata
ToMetadata converts the config into a PolicyMetadata value suitable for constructing a RoleAuthorizer or CasbinAuthorizer.
type Capability ¶
type Capability string
Capability is a named thing a principal is allowed to do or use. All capabilities use ':' as the separator (Casbin convention):
"feature:<name>" — product entitlement (feature:custom-domain) "<resource>:<action>" — resource:action permission (tunnel:write)
Use FeatureCapability or Permission to construct values; avoid bare string literals in application code.
func FeatureCapability ¶
func FeatureCapability(name string) Capability
FeatureCapability constructs a feature-namespace capability. For example, FeatureCapability("custom-domain") returns "feature:custom-domain".
func Permission ¶
func Permission(resource, action string) Capability
Permission constructs a resource:action capability. For example, Permission("tunnel", "write") returns "tunnel:write".
type CasbinAuthorizer ¶
type CasbinAuthorizer struct {
// contains filtered or unexported fields
}
CasbinAuthorizer is an Authorizer backed by Casbin v2. It is the recommended production backend for both connect and connect-control.
Policy rules are loaded from PolicyMetadata.RoleCapabilities at construction. [Can] uses Casbin enforce for efficient per-action checks and records the granting role in the returned Decision.
func NewCasbinAuthorizer ¶
func NewCasbinAuthorizer(metadata PolicyMetadata) (*CasbinAuthorizer, error)
NewCasbinAuthorizer creates a CasbinAuthorizer from the given PolicyMetadata. Each capability in RoleCapabilities is split on ":" to derive the Casbin (obj, act) pair and loaded as a policy rule. Returns an error if the Casbin model or any policy rule cannot be created.
func (*CasbinAuthorizer) Can ¶
func (a *CasbinAuthorizer) Can(_ context.Context, principalCtx *auth.PrincipalContext, cap Capability) (*Decision, error)
Can checks whether the principal's roles grant cap using Casbin enforcement. The returned Decision includes GrantedBy — the first role that grants the capability — enabling fine-grained audit logging.
func (*CasbinAuthorizer) Metadata ¶
func (a *CasbinAuthorizer) Metadata() PolicyMetadata
Metadata returns the policy metadata.
func (*CasbinAuthorizer) Privileges ¶
func (a *CasbinAuthorizer) Privileges(_ context.Context, principalCtx *auth.PrincipalContext) (Privilege, error)
Privileges resolves the principal's roles from canonical role names or RoleAliases and returns the union of all matching role capabilities as a PrivilegeSet.
type Decision ¶
type Decision struct {
// Allowed is true when the capability was granted.
Allowed bool
// Reason is a stable token describing the outcome.
Reason DecisionReason
// GrantedBy is the Role that granted the capability.
// Empty when Allowed is false or when AllowAllAuthorizer is used.
GrantedBy Role
// Required is the Capability that was evaluated.
Required Capability
}
Decision records the outcome of an authorization check for audit logging. It is non-nil whenever the authorizer reaches a policy conclusion (allow or deny). It is nil only when the error is unrelated to policy — for example, an infrastructure failure in the underlying enforcement engine.
type DecisionReason ¶
type DecisionReason string
DecisionReason is a stable string token that describes why an authorization decision was made. It is intended for structured audit logging; callers should still use errors.Is with ErrUnauthorized or ErrForbidden for flow control, depending on the failure mode.
const ( // ReasonAllowAll is returned by [AllowAllAuthorizer] — every capability // is granted regardless of principal or policy. ReasonAllowAll DecisionReason = "allow_all" // ReasonGranted means a policy rule explicitly grants the capability. ReasonGranted DecisionReason = "granted" // ReasonDeniedNilPrincipal means no principal was supplied. ReasonDeniedNilPrincipal DecisionReason = "denied_nil_principal" // ReasonDeniedNoRoles means the principal carries no roles that appear in // the policy's RoleAliases map. Unmapped external roles are rejected. ReasonDeniedNoRoles DecisionReason = "denied_no_roles" // ReasonDeniedNoPermission means the principal's roles are recognised but // none of them grant the required capability. ReasonDeniedNoPermission DecisionReason = "denied_no_permission" )
type MapAuthorizer ¶
type MapAuthorizer struct {
// contains filtered or unexported fields
}
MapAuthorizer is an Authorizer backed by a principal-ID → PrivilegeSet map. It is useful for inline test configs and simple static deployments where each principal's capabilities are enumerated directly.
If a principal ID is not found in the map, Privileges returns nil (no capabilities). In a MultiAuthorizer chain this causes the next backend to be tried.
func NewMapAuthorizer ¶
func NewMapAuthorizer(privileges map[string]*PrivilegeSet, metadata PolicyMetadata) *MapAuthorizer
NewMapAuthorizer creates a MapAuthorizer with the given principal→privileges map and policy metadata (used for Metadata() introspection).
func (*MapAuthorizer) Can ¶
func (a *MapAuthorizer) Can(ctx context.Context, principalCtx *auth.PrincipalContext, cap Capability) (*Decision, error)
Can checks whether the principal's mapped PrivilegeSet contains cap.
func (*MapAuthorizer) Metadata ¶
func (a *MapAuthorizer) Metadata() PolicyMetadata
Metadata returns the policy metadata.
func (*MapAuthorizer) Privileges ¶
func (a *MapAuthorizer) Privileges(_ context.Context, principalCtx *auth.PrincipalContext) (Privilege, error)
Privileges looks up the principal's ID in the map and returns a wildcard-aware view so Privilege.Has semantics stay consistent with other authorizers. Returns nil when not found.
type MultiAuthorizer ¶
type MultiAuthorizer struct {
// contains filtered or unexported fields
}
MultiAuthorizer chains multiple Authorizer backends. [Privileges] returns the result from the first backend that returns a non-nil Privilege; [Can] similarly short-circuits on the first non-nil Privilege result. If all backends return nil Privileges the principal is denied.
This matches the connect MultiAuthoriser semantics and is useful for composing inline + dynamic backends.
func NewMultiAuthorizer ¶
func NewMultiAuthorizer(authorizers ...Authorizer) *MultiAuthorizer
NewMultiAuthorizer creates a MultiAuthorizer from the provided backends. The metadata from the first backend is used for Metadata() introspection.
func (*MultiAuthorizer) Can ¶
func (m *MultiAuthorizer) Can(ctx context.Context, principalCtx *auth.PrincipalContext, cap Capability) (*Decision, error)
Can checks whether the first backend that returns a non-nil Privilege grants cap. If no backend recognises the principal, the request is denied.
func (*MultiAuthorizer) Metadata ¶
func (m *MultiAuthorizer) Metadata() PolicyMetadata
Metadata returns the metadata from the first backend, or an empty PolicyMetadata when no backends are configured.
func (*MultiAuthorizer) Privileges ¶
func (m *MultiAuthorizer) Privileges(ctx context.Context, principalCtx *auth.PrincipalContext) (Privilege, error)
Privileges iterates the backends in order and returns the first non-nil Privilege. Returns nil when no backend recognises the principal.
type PolicyMetadata ¶
type PolicyMetadata struct {
// RoleCapabilities maps each internal Role to the set of Capabilities it grants.
RoleCapabilities map[Role][]Capability
// RoleAliases maps external role/group names (from IdP tokens) to internal
// Roles. Canonical internal role names are accepted when they appear in
// RoleCapabilities; otherwise only aliases listed here are accepted, and
// unrecognised role strings are ignored.
RoleAliases map[string]Role
}
PolicyMetadata describes a static role→capability policy used to configure RoleAuthorizer and CasbinAuthorizer.
func CloneMetadata ¶
func CloneMetadata(m PolicyMetadata) PolicyMetadata
CloneMetadata returns a deep copy of m.
func MergeRoleAliases ¶
func MergeRoleAliases(meta PolicyMetadata, aliases map[string]Role) PolicyMetadata
MergeRoleAliases returns a copy of meta with the provided aliases merged in. Existing aliases are preserved; entries in aliases override duplicates.
type Privilege ¶
type Privilege interface {
Has(Capability) bool
}
Privilege represents the set of capabilities granted to a principal. Callers use [Has] to check whether a specific capability is present. The concrete implementation is PrivilegeSet.
func NewWildcardPrivilege ¶
func NewWildcardPrivilege(ps *PrivilegeSet) Privilege
NewWildcardPrivilege returns a Privilege backed by ps that additionally supports action wildcards ("resource:any") and keyMatch-style resource patterns (for example, "resource/*:read").
type PrivilegeSet ¶
type PrivilegeSet struct {
// contains filtered or unexported fields
}
PrivilegeSet is a capability set backed by a map. It implements Privilege and can be built incrementally via [Grant]. Use NewPrivilegeSet to create a set from a slice of known capabilities.
func NewPrivilegeSet ¶
func NewPrivilegeSet(caps ...Capability) *PrivilegeSet
NewPrivilegeSet creates a PrivilegeSet containing the given capabilities.
func (*PrivilegeSet) Capabilities ¶
func (p *PrivilegeSet) Capabilities() []Capability
Capabilities returns a snapshot of all capabilities in the set.
func (*PrivilegeSet) Grant ¶
func (p *PrivilegeSet) Grant(cap Capability)
Grant adds cap to the set.
func (*PrivilegeSet) Has ¶
func (p *PrivilegeSet) Has(cap Capability) bool
Has reports whether the set contains cap.
func (*PrivilegeSet) Union ¶
func (p *PrivilegeSet) Union(other *PrivilegeSet) *PrivilegeSet
Union returns a new PrivilegeSet containing all capabilities from both sets (OR-merge). Neither input is modified.
type RoleAuthorizer ¶
type RoleAuthorizer struct {
// contains filtered or unexported fields
}
RoleAuthorizer is an Authorizer backed by an in-memory role→capability map. Matching roles are unioned to form the principal's privilege set. It is suitable for static config-driven deployments and unit tests. For production use with complex policies, prefer CasbinAuthorizer.
func NewRoleAuthorizer ¶
func NewRoleAuthorizer(metadata PolicyMetadata) *RoleAuthorizer
NewRoleAuthorizer creates a RoleAuthorizer from the given PolicyMetadata.
func (*RoleAuthorizer) Can ¶
func (a *RoleAuthorizer) Can(ctx context.Context, principalCtx *auth.PrincipalContext, cap Capability) (*Decision, error)
Can checks whether the principal's union of role capabilities includes cap.
func (*RoleAuthorizer) Metadata ¶
func (a *RoleAuthorizer) Metadata() PolicyMetadata
Metadata returns the policy metadata.
func (*RoleAuthorizer) Privileges ¶
func (a *RoleAuthorizer) Privileges(_ context.Context, principalCtx *auth.PrincipalContext) (Privilege, error)
Privileges resolves the principal's roles from canonical role names or RoleAliases and returns the union of all matching role capabilities. Returns nil when the principal has no recognised roles.
type RoleCapabilityConfig ¶
type RoleCapabilityConfig struct {
// Role is the internal role name.
Role string `mapstructure:"role"`
// Capabilities is the list of capability strings granted to this role.
// Each must use ':' as the separator, e.g. "tunnel:write", "feature:custom-domain".
Capabilities []string `mapstructure:"capabilities"`
}
RoleCapabilityConfig maps a role name to the capabilities it grants. Used in YAML/mapstructure-based configuration.