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.
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.
// errors.Is(err, ErrForbidden) is the idiomatic deny check.
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. Use errors.Is(err, ErrForbidden) for flow control:
d, err := authorizer.Can(ctx, principalCtx, authz.Permission("tunnel", "write"))
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:
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 via 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(err, ErrForbidden) for flow control.
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. 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. Only aliases listed here are accepted; unmapped external roles are
// rejected. This prevents a token from accidentally carrying an internal role
// name (e.g. "admin") and gaining elevated privileges.
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 grants any "resource:action" capability when "resource:any" is present.
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 via 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.