authz

package
v0.24.0 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: BSD-3-Clause-Clear Imports: 14 Imported by: 0

Documentation

Overview

Package authz provides the authorization interface and types for the OpenTDF platform. It defines the contract between the authentication middleware and authorization engines.

Index

Constants

View Source
const (
	SubjectRolePrefix   = "role:"
	SubjectClientPrefix = "client:"
)
View Source
const DefaultEngine = "casbin"

DefaultEngine is the default authorization engine when none is specified.

Variables

View Source
var (
	ErrClientIDClaimNotConfigured = errors.New("no client ID claim configured")
	ErrClientIDClaimNotFound      = errors.New("client ID claim not found")
	ErrClientIDClaimNotString     = errors.New("client ID claim is not a string")
	ErrTokenRequired              = errors.New("token is required")
	ErrSubjectExtractorLogger     = errors.New("subject extractor logger is required")
	ErrSubjectExtractorRoles      = errors.New("subject extractor role provider is required")
)

Functions

func AdapterConfigFromExternal

func AdapterConfigFromExternal(cfg Config) any

AdapterConfigFromExternal maps external configuration to the appropriate internal adapter configuration. This provides a clean boundary between customer-facing config (stable) and internal adapter config (can evolve).

The external PolicyConfig is what customers configure in YAML/JSON. The internal adapter configs are what the authorization engines consume.

Engine selection:

  • "casbin" (default): Returns CasbinV1Config or CasbinV2Config based on Version
  • "cedar": Returns CedarConfig (future)
  • "opa": Returns OPAConfig (future)

func ContextWithResolverContext

func ContextWithResolverContext(ctx context.Context, rc *ResolverContext) context.Context

ContextWithResolverContext returns a new context with the ResolverContext attached. This is called by the auth interceptor after resolution to make cached data available to handlers.

func GetResolvedDataFromContext

func GetResolvedDataFromContext(ctx context.Context, key string) any

GetResolvedDataFromContext is a convenience function to retrieve cached data from the ResolverContext in the given context. Returns nil if no ResolverContext or key not found.

func NewJWTClaimsRoleProvider

func NewJWTClaimsRoleProvider(groupsClaim string, logger *logger.Logger) platformauthz.RoleProvider

NewJWTClaimsRoleProvider constructs the default JWT claims role provider.

func RegisterFactory

func RegisterFactory(name string, factory Factory)

RegisterFactory registers an authorization engine factory. This is called during init() by each authorizer implementation.

Types

type Authorizer

type Authorizer interface {
	// Authorize performs an authorization check.
	//
	// The implementation should:
	// 1. Extract subjects (roles/username) from the token
	// 2. Apply the appropriate authorization model based on configuration
	// 3. For v2: Use ResourceContext dimensions if available
	// 4. Return an error only for system failures, not for denied access
	//
	// Thread-safety: This method may be called concurrently from multiple goroutines.
	Authorize(ctx context.Context, req *Request) (*Decision, error)

	// Version returns the authorization model version this authorizer implements.
	// Returns "v1" for legacy path-based, "v2" for RPC+dimensions, etc.
	Version() string

	// SupportsResourceAuthorization returns true if this authorizer
	// supports resource-level authorization with dimensions.
	// If false, ResourceContext will always be ignored.
	SupportsResourceAuthorization() bool
}

Authorizer is the interface for pluggable authorization engines. Implementations must be thread-safe.

The OpenTDF platform supports multiple authorization versions:

  • v1: Legacy path-based authorization using (subject, resource, action) tuple
  • v2: RPC + dimensions authorization using (subject, rpc, dimensions) tuple

When implementing a new authorization engine (e.g., OPA, Cedar), implement this interface and register it via the Factory.

func New

func New(cfg Config) (Authorizer, error)

New creates an Authorizer based on configuration. The engine is selected based on cfg.PolicyConfig.Engine:

  • "casbin" (default): Casbin policy engine
  • "cedar": AWS Cedar policy engine (future)
  • "opa": Open Policy Agent engine (future)

For Casbin, the version determines the authorization model:

  • "v1" (default): Legacy path-based model (subject, resource, action)
  • "v2": RPC+dimensions model (subject, rpc, dimensions)

type CasbinV1Config

type CasbinV1Config struct {
	PolicyConfig

	// RoleProvider extracts role/group subjects.
	RoleProvider platformauthz.RoleProvider

	// Adapter is a custom policy adapter (e.g., SQL).
	// If nil, uses string adapter with Csv content.
	Adapter persist.Adapter
}

CasbinV1Config configures the legacy path-based Casbin authorizer. This model uses (subject, resource, action) tuples for authorization.

Example policy:

p, role:admin, *, *, allow
p, role:standard, /attributes*, read, allow

type CasbinV2Config

type CasbinV2Config struct {
	PolicyConfig

	// RoleProvider extracts role/group subjects.
	RoleProvider platformauthz.RoleProvider

	// Adapter is a custom policy adapter (e.g., SQL).
	// If nil, uses string adapter with Csv content.
	Adapter persist.Adapter
}

CasbinV2Config configures the RPC + dimensions Casbin authorizer. This model uses (subject, rpc, dimensions) tuples for authorization.

Example policy:

p, role:admin, *, *, allow
p, role:standard, /policy.attributes.AttributesService/*, read, allow
p, role:ns-admin, /policy.attributes.AttributesService/*, *, ns:my-namespace, allow

type CedarConfig

type CedarConfig struct {
	PolicyConfig

	// RoleProvider extracts role/group subjects.
	RoleProvider platformauthz.RoleProvider

	// SchemaPath is the path to the Cedar schema file.
	SchemaPath string

	// PoliciesPath is the path to Cedar policy files.
	PoliciesPath string

	// EntitiesPath is the path to Cedar entities file.
	EntitiesPath string
}

CedarConfig configures the AWS Cedar authorization engine (future). Cedar provides a policy language with strong typing and formal verification.

type Config

type Config struct {
	// Policy configuration (claims, CSV, adapter, etc.)
	PolicyConfig

	// Logger for authorization decisions
	Logger any

	// Options for engine-specific configuration
	Options []Option
}

Config provides configuration for authorization engine initialization.

type Decision

type Decision struct {
	// Allowed indicates whether the request is permitted.
	Allowed bool

	// Reason provides a human-readable explanation for audit logging.
	Reason string

	// Mode indicates which authorization model was used.
	Mode Mode

	// MatchedPolicy optionally contains the policy rule that matched (for debugging).
	MatchedPolicy string

	// Metadata contains supplemental information for audit logging.
	Metadata DecisionMetadata
}

Decision represents the result of an authorization check.

type DecisionMetadata

type DecisionMetadata struct {
	// GroupsClaim is the configured JWT claim used to extract authorization groups.
	GroupsClaim string
}

DecisionMetadata contains supplemental authorization decision metadata.

type EnforcementResult

type EnforcementResult struct {
	// Allowed indicates whether the request is permitted.
	Allowed bool

	// GroupsClaim is the configured JWT claim used to extract authorization groups.
	GroupsClaim string
}

EnforcementResult represents the v1 authorization enforcement result.

type EngineType

type EngineType string

EngineType identifies the authorization engine implementation.

const (
	// EngineCasbin uses Casbin for policy enforcement.
	EngineCasbin EngineType = "casbin"
)

type Factory

type Factory func(cfg Config) (Authorizer, error)

Factory creates Authorizer instances based on configuration. This allows the platform to instantiate different authorization engines (Casbin, OPA, Cedar) based on configuration.

func GetFactory

func GetFactory(name string) (Factory, bool)

GetFactory returns the factory for the given name, if registered.

type JWTClaimsRoleProvider

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

JWTClaimsRoleProvider extracts role/group values from a configured JWT claim.

func (*JWTClaimsRoleProvider) Roles

type Mode

type Mode string

Mode indicates which authorization strategy was used for a decision.

const (
	// ModeV1 indicates legacy path-based authorization (v1 model).
	ModeV1 Mode = "v1"
	// ModeV2 indicates RPC + dimensions authorization (v2 model).
	ModeV2 Mode = "v2"
)

type OPAConfig

type OPAConfig struct {
	PolicyConfig

	// RoleProvider extracts role/group subjects.
	RoleProvider platformauthz.RoleProvider

	// BundlePath is the path to the OPA bundle.
	BundlePath string

	// Query is the Rego query for authorization decisions.
	Query string
}

OPAConfig configures the Open Policy Agent authorization engine (future). OPA provides a general-purpose policy engine with Rego query language.

type Option

type Option func(*optionConfig)

Option is a functional option for authorizer configuration.

func WithRoleProvider

func WithRoleProvider(provider platformauthz.RoleProvider) Option

WithRoleProvider sets the role provider used for subject extraction.

type PolicyConfig

type PolicyConfig struct {
	Builtin string `mapstructure:"-" json:"-"`

	// Issuer is the configured token issuer for role provider requests.
	Issuer string `mapstructure:"-" json:"-"`

	// Engine specifies the authorization engine to use.
	// - "casbin" (default): Casbin policy engine
	// - "cedar": AWS Cedar policy engine (future)
	// - "opa": Open Policy Agent engine (future)
	Engine string `mapstructure:"engine" json:"engine" default:"casbin"`

	// Version specifies the engine-specific authorization model version.
	// For Casbin:
	// - "v1" (default): Legacy path-based authorization (subject, resource, action)
	// - "v2": RPC + dimensions authorization (subject, rpc, dimensions)
	// v2 enables fine-grained resource-level authorization using AuthzResolvers.
	Version string `mapstructure:"version" json:"version" default:"v1"`

	// Username claim to use for user information
	UserNameClaim string `mapstructure:"username_claim" json:"username_claim" default:"preferred_username"`

	// Claim to use for group/role information
	GroupsClaim string `mapstructure:"groups_claim" json:"groups_claim" default:"realm_access.roles"`

	// Role provider configuration (resolved via StartOptions)
	RolesProvider RolesProviderConfig `mapstructure:"roles_provider" json:"roles_provider"`

	// Claim to use to reference idP clientID
	ClientIDClaim string `mapstructure:"client_id_claim" json:"client_id_claim" default:"azp"`

	// Deprecated: Use GroupsClaim instead
	RoleClaim string `mapstructure:"claim" json:"claim" default:"realm_access.roles"`

	// Deprecated: Use Casbin grouping statements g, <user/group>, <role>
	RoleMap map[string]string `mapstructure:"map" json:"map"`

	// Override the builtin policy with a custom policy
	Csv string `mapstructure:"csv" json:"csv"`

	// Extend the builtin policy with a custom policy
	Extension string `mapstructure:"extension" json:"extension"`

	Model string `mapstructure:"model" json:"model"`

	// Adapter is intentionally any to allow future adapter config shapes beyond Casbin persist.Adapter.
	// Conversion and validation happen downstream.
	Adapter any `mapstructure:"-" json:"-"`
}

PolicyConfig contains the policy configuration for authorization.

type Request

type Request struct {
	// Subject information extracted from JWT
	Token jwt.Token

	// RPC method path (e.g., "/policy.attributes.AttributesService/UpdateAttribute")
	// Used as the primary resource identifier in v2 model.
	RPC string

	// Action derived from RPC method (read, write, delete, unsafe).
	// Used in v1 model; informational in v2 model.
	Action string

	// ResourceContext contains resolved authorization dimensions (namespace, attribute, etc.).
	// If non-nil, indicates resource-level authorization should be attempted.
	// Populated by ResolverRegistry when a resolver is registered for the RPC.
	ResourceContext *ResolverContext
}

Request encapsulates all information needed for an authorization decision. This is the contract between the interceptor and any authorization engine.

type ResolverContext

type ResolverContext struct {
	Resources []*ResolverResource

	// ResolvedData stores data fetched during resolution (e.g., attributes, namespaces)
	// to avoid duplicate DB queries in handlers. Keys are service-defined strings.
	// Handlers can retrieve this data via GetResolvedDataFromContext().
	ResolvedData map[string]any
}

ResolverContext holds the resolved authorization context for a request. Multiple resources are supported for operations like "move from A to B" where authorization is required for both source and destination.

func NewResolverContext

func NewResolverContext() ResolverContext

NewResolverContext creates a new empty resolver context.

func ResolverContextFromContext

func ResolverContextFromContext(ctx context.Context) *ResolverContext

ResolverContextFromContext retrieves the ResolverContext from the context. Returns nil if not present (e.g., no resolver registered for the method).

func (*ResolverContext) GetResolvedData

func (a *ResolverContext) GetResolvedData(key string) any

GetResolvedData retrieves cached data by key. Returns nil if key not found. Caller should type-assert the result.

func (*ResolverContext) NewResource

func (a *ResolverContext) NewResource() *ResolverResource

NewResource creates and adds a new resource to the context.

func (*ResolverContext) SetResolvedData

func (a *ResolverContext) SetResolvedData(key string, value any)

SetResolvedData stores data in the resolver context cache. Use this to cache fetched resources (e.g., attributes) for handler reuse. The key should be a descriptive string (e.g., "attribute", "namespace").

type ResolverFunc

type ResolverFunc func(ctx context.Context, req connect.AnyRequest) (ResolverContext, error)

ResolverFunc is the function signature for service-provided resolvers. Services implement this to extract authorization dimensions from requests.

Parameters:

  • ctx: Request context (includes auth info, can be used for DB calls)
  • req: The connect request (use Deserialize helper to get typed proto)

Returns:

  • ResolverContext with populated dimensions
  • Error if resolution fails (results in 403)

Service maintainers are responsible for:

  1. Deserializing the request using the provided helper
  2. Extracting relevant fields
  3. Performing any required DB lookups
  4. Populating dimensions in ResolverContext

type ResolverRegistry

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

ResolverRegistry holds resolver functions keyed by service method. This is the global registry used by the interceptor. It is thread-safe for concurrent read/write access.

func NewResolverRegistry

func NewResolverRegistry() *ResolverRegistry

NewResolverRegistry creates a new resolver registry.

func (*ResolverRegistry) Get

func (r *ResolverRegistry) Get(method string) (ResolverFunc, bool)

Get returns the resolver for a method, if registered.

func (*ResolverRegistry) ScopedForService

func (r *ResolverRegistry) ScopedForService(serviceDesc *grpc.ServiceDesc) *ScopedResolverRegistry

ScopedForService creates a namespace-scoped registry that only allows registering resolvers for the given service's methods. This prevents services from registering resolvers for other services. Panics if serviceDesc is nil.

type ResolverResource

type ResolverResource map[string]string

ResolverResource represents a single resource's authorization dimensions. Each key-value pair is a dimension (e.g., "namespace" -> "hr").

func (*ResolverResource) AddDimension

func (a *ResolverResource) AddDimension(dimension, value string)

AddDimension adds a dimension to the resource.

type RolesProviderConfig

type RolesProviderConfig struct {
	Name   string         `mapstructure:"name" json:"name"`
	Config map[string]any `mapstructure:"config" json:"config"`
}

RolesProviderConfig contains role-provider selection and provider-specific settings.

type ScopedResolverRegistry

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

ScopedResolverRegistry is a namespace-scoped view of the registry. It only allows registering resolvers for the service it was created for.

func (*ScopedResolverRegistry) MustRegister

func (s *ScopedResolverRegistry) MustRegister(methodName string, resolver ResolverFunc)

MustRegister is like Register but panics on error. Use during service initialization where errors should be fatal.

func (*ScopedResolverRegistry) Register

func (s *ScopedResolverRegistry) Register(methodName string, resolver ResolverFunc) error

Register adds a resolver for a method in this service. Only the method name is required (e.g., "UpdateAttribute"), not the full path. The full path is derived from the ServiceDesc.

Returns an error if the method doesn't exist in the ServiceDesc.

func (*ScopedResolverRegistry) ServiceName

func (s *ScopedResolverRegistry) ServiceName() string

ServiceName returns the service name this registry is scoped to.

type SubjectExtractor

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

func NewSubjectExtractor

func NewSubjectExtractor(userNameClaim, clientIDClaim string, roleProvider platformauthz.RoleProvider, log *logger.Logger) (SubjectExtractor, error)

NewSubjectExtractor constructs a subject extractor with required dependencies.

func (SubjectExtractor) BuildV1SubjectsFromToken

func (e SubjectExtractor) BuildV1SubjectsFromToken(ctx context.Context, token jwt.Token, req platformauthz.RoleRequest) ([]string, []string, error)

BuildV1SubjectsFromToken preserves legacy subjects: claims.Groups plus claims.Subject as-is, including empty values. It does not emit an independent client ID subject or filter reserved role:/client: username prefixes.

func (SubjectExtractor) BuildV2SubjectsFromToken

func (e SubjectExtractor) BuildV2SubjectsFromToken(ctx context.Context, token jwt.Token, req platformauthz.RoleRequest) ([]string, []string, error)

BuildV2SubjectsFromToken emits typed subjects: client IDs and roles use reserved prefixes, empty roles are filtered, and usernames with role:/client: prefixes are skipped to avoid collisions.

func (SubjectExtractor) ClaimsForRequest

func (SubjectExtractor) ClientIDFromToken

func (e SubjectExtractor) ClientIDFromToken(ctx context.Context, token jwt.Token) (string, error)

func (SubjectExtractor) ContextWithClaims

func (e SubjectExtractor) ContextWithClaims(ctx context.Context, token jwt.Token, req platformauthz.RoleRequest) (context.Context, error)

Directories

Path Synopsis
Package casbin registers the Casbin authorization engine and dispatches to the configured versioned implementation.
Package casbin registers the Casbin authorization engine and dispatches to the configured versioned implementation.
v1
Package v1 provides the legacy path-based Casbin authorization implementation.
Package v1 provides the legacy path-based Casbin authorization implementation.
v2
Package v2 provides the resource/dimension-based Casbin authorization implementation.
Package v2 provides the resource/dimension-based Casbin authorization implementation.

Jump to

Keyboard shortcuts

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