authz

package
v0.0.0-...-84d76fe Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2024 License: Apache-2.0 Imports: 24 Imported by: 1

README

Authz: User and Namespace Access Control

This library provides utilities for your Grafana applications or plugins to manage user permissions and control access to resources within namespaces.

Features:

  • Single-tenant RBAC client, typically used by plugins to query Grafana for user permissions and control their access.
  • [unstable / under development] Multi-tenant client, typically used by multi-tenant applications to enforce service and user access.
  • A composable namespace checker to authorize requests based on JWT namespaces

Access-control EnforcementClient

This package exports an RBAC client library that contains a set of utilities to check users permissions from Grafana.

Grafana Configuration

Grafana needs to be configured with the accessControlOnCall feature toggle set for the search permissions endpoint to be registered.

[feature_toggles]
enable = accessControlOnCall

Example: Check if a user can list users

Here is an example on how to check access on a resouce for a user.

package main

import (
	"context"
	"log"

	"github.com/grafana/authlib/authz"
)

func main() {
	client, err := authz.NewEnforcementClient(authz.Config{
		APIURL:  "http://localhost:3000",
		Token:   "<service account token>",
		JWKsURL: "<jwks url>",
	})

	if err != nil {
		log.Fatal("failed to construct authz client", err)
	}

	ok, err := client.HasAccess(context.Background(), "<id token>", "users:read", authz.Resource{
		Kind: "users",
		Attr: "id",
		ID:   "1",
	})

	if err != nil {
		log.Fatal("failed to perform access check", err)
	}

	log.Println("has access: ", ok)
}

[unstable / under development ] Multi-tenant authz client

Namespace access

Documentation

Index

Constants

View Source
const (
	DefaultStackIDMetadataKey   = "X-Stack-ID"
	DefaultNamespaceMetadataKey = "X-Namespace"
)
View Source
const (
	NamespaceServiceAccount = "service-account"
	NamespaceUser           = "user"
)

Variables

View Source
var (
	ErrMissingConfig           = errors.New("missing config")
	ErrMissingRequestNamespace = errors.New("missing request namespace")
	ErrInvalidRequestNamespace = errors.New("invalid request namespace")
	ErrMissingRequestGroup     = errors.New("missing request group")
	ErrMissingRequestResource  = errors.New("missing request resource")
	ErrMissingRequestVerb      = errors.New("missing request verb")
	ErrMissingCaller           = errors.New("missing caller")
	ErrMissingSubject          = errors.New("missing subject")
)
View Source
var (
	ErrorMissingMetadata              = status.Errorf(codes.Unauthenticated, "unauthenticated: missing metadata")
	ErrorInvalidStackID               = status.Errorf(codes.Unauthenticated, "unauthenticated: invalid stack ID")
	ErrorMissingIDToken               = status.Errorf(codes.Unauthenticated, "unauthenticated: missing id token")
	ErrorMissingAccessToken           = status.Errorf(codes.Unauthenticated, "unauthenticated: missing access token")
	ErrorIDTokenNamespaceMismatch     = status.Errorf(codes.PermissionDenied, "unauthorized: id token namespace does not match expected namespace")
	ErrorAccessTokenNamespaceMismatch = status.Errorf(codes.PermissionDenied, "unauthorized: access token namespace does not match expected namespace")
)
View Source
var (
	ErrInvalidQuery          = errors.New("invalid query")
	ErrInvalidIDToken        = errors.New("invalid id token: cannot extract namespaced ID")
	ErrInvalidToken          = errors.New("invalid token: cannot query server")
	ErrInvalidResponse       = errors.New("invalid response from server")
	ErrUnexpectedStatus      = errors.New("unexpected response status")
	ErrInvalidTokenNamespace = errors.New("invalid token: can only query server for users and service-accounts")
)
View Source
var (
	ErrTooManyPermissions = errors.New("unexpected number of permissions returned by the server")
)

Functions

func StreamAuthorizeInterceptor

func StreamAuthorizeInterceptor(accessFunc AuthorizeFunc) grpc.StreamServerInterceptor

StreamAuthorizeInterceptor returns a new stream server interceptor that performs per-request authorization.

func UnaryAuthorizeInterceptor

func UnaryAuthorizeInterceptor(accessFunc AuthorizeFunc) grpc.UnaryServerInterceptor

UnaryAuthorizeInterceptor returns a new unary server interceptor that performs per-request authorization.

Types

type AccessChecker

type AccessChecker interface {
	// Check checks whether the user can perform the given action for all requests
	Check(ctx context.Context, id claims.AuthInfo, req CheckRequest) (CheckResponse, error)
}

type AccessClient

type AccessClient interface {
	AccessChecker
	AccessLister
}

type AccessLister

type AccessLister interface {
	// Compile generates a function to check whether the id has access to items matching a request
	// This is particularly useful when you want to verify access to a list of resources.
	// Returns nil if there is no access to any matching items
	Compile(ctx context.Context, id claims.AuthInfo, req ListRequest) (ItemChecker, error)
}

type AuthorizeFunc

type AuthorizeFunc func(ctx context.Context) error

AuthorizeFunc is the pluggable function that performs access control checks.

The passed in `Context` will contain previous context values, such as grpc metadata or even the caller's claims if the grpc_authenticator interceptor is used before the authorize interceptor. The function should return an error if the caller does not have access.

If error is returned, its `grpc.Code()` will be returned to the user as well as the verbatim message. Please make sure you use `codes.PermissionDenied` (lacking perms) appropriately.

func NamespaceAuthorizationFunc

func NamespaceAuthorizationFunc(na NamespaceAccessChecker, nsExtract NamespaceExtractor) AuthorizeFunc

NamespaceAuthorizationFunc returns a AuthorizeFunc that checks the caller claims access to a given namespace. This function can be used with UnaryAuthorizeInterceptor and StreamAuthorizeInterceptor.

type AuthzClientOption

type AuthzClientOption func(*ClientImpl)

func WithCacheClientOption

func WithCacheClientOption(cache cache.Cache) AuthzClientOption

func WithDisableAccessTokenClientOption

func WithDisableAccessTokenClientOption() AuthzClientOption

WithDisableAccessTokenClientOption is an option to disable access token authorization. Warning: Using this option means there won't be any service authorization.

func WithGrpcConnectionClientOption

func WithGrpcConnectionClientOption(conn grpc.ClientConnInterface) AuthzClientOption

WithGrpcConnectionClientOption sets the gRPC client connection directly. Useful for running the client in the same process as the authorization service.

func WithGrpcDialOptionsClientOption

func WithGrpcDialOptionsClientOption(opts ...grpc.DialOption) AuthzClientOption

WithGrpcDialOptionsClientOption sets the gRPC dial options for client connection setup. Useful for adding client interceptors. These options are ignored if WithGrpcConnection is used.

func WithTracerClientOption

func WithTracerClientOption(tracer trace.Tracer) AuthzClientOption

type CheckRequest

type CheckRequest struct {
	// The requested access verb.
	// this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy,
	// or the lowercased HTTP verb associated with non-API requests (this includes get, put, post, patch, and delete)
	Verb string

	// API group (dashboards.grafana.app)
	Group string

	// ~Kind eg dashboards
	Resource string

	// tenant isolation
	Namespace string

	// The specific resource
	// In grafana, this was historically called "UID", but in k8s, it is the name
	Name string

	// Optional subresource
	Subresource string

	// For non-resource requests, this will be the requested URL path
	Path string

	// Folder is the parent folder of the requested resource
	Folder string
}

CheckRequest describes the requested access. This is designed bo to play nicely with the kubernetes authorization system: https://github.com/kubernetes/kubernetes/blob/v1.30.3/staging/src/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go#L28

type CheckResponse

type CheckResponse struct {
	// Allowed is true if the request is allowed, false otherwise.
	Allowed bool
}

type Checker

type Checker func(resources ...Resource) bool

Checker checks whether a user has access to any of the provided resources.

type ClientConfig

type ClientConfig struct {
	// RemoteAddress is the address of the authz service. It should be in the format "host:port".
	RemoteAddress string
	// contains filtered or unexported fields
}

type ClientImpl

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

func NewClient

func NewClient(cfg *ClientConfig, opts ...AuthzClientOption) (*ClientImpl, error)

func (*ClientImpl) Check

type ClientOption

type ClientOption func(*EnforcementClientImpl) error

ClientOption allows setting custom parameters during construction.

func WithCache

func WithCache(cache cache.Cache) ClientOption

func WithHTTPClient

func WithHTTPClient(doer HTTPRequestDoer) ClientOption

func WithSearchByPrefix

func WithSearchByPrefix(prefix string) ClientOption

WithSearchByPrefix makes the client search for permissions always using the given prefix. This can improve performance when the client is used to check permissions for a single action prefix.

type Config

type Config struct {
	APIURL  string
	Token   string
	JWKsURL string
}

type EnforcementClient

type EnforcementClient interface {
	// Compile generates a function to check whether the user has access to any scope of a given list of scopes.
	// This is particularly useful when you want to verify access to a list of resources.
	Compile(ctx context.Context, idToken string, action string, kinds ...string) (Checker, error)

	// HasAccess checks whether the user can perform the given action on any of the given resources.
	// If the scope is empty, it checks whether the user can perform the action.
	HasAccess(ctx context.Context, idToken string, action string, resources ...Resource) (bool, error)

	// Experimental: LookupResources returns the resources that the user has access to for the given action.
	LookupResources(ctx context.Context, idToken string, action string) ([]Resource, error)
}

type EnforcementClientImpl

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

func NewEnforcementClient

func NewEnforcementClient(cfg Config, opt ...ClientOption) (*EnforcementClientImpl, error)

func (*EnforcementClientImpl) Compile

func (s *EnforcementClientImpl) Compile(ctx context.Context, idToken string,
	action string, kinds ...string) (Checker, error)

func (*EnforcementClientImpl) HasAccess

func (s *EnforcementClientImpl) HasAccess(ctx context.Context, idToken string,
	action string, resources ...Resource) (bool, error)

func (*EnforcementClientImpl) LookupResources

func (s *EnforcementClientImpl) LookupResources(ctx context.Context, idToken string, action string) ([]Resource, error)

Experimental: LookupResources returns the resources that the user has access to for the given action. Resource expansion is still not supported in this method.

type HTTPRequestDoer

type HTTPRequestDoer interface {
	Do(req *http.Request) (*http.Response, error)
}

HTTPRequestDoer performs HTTP requests. The standard http.Client implements this interface.

type ItemChecker

type ItemChecker func(namespace string, name, folder string) bool

TODO: Should the namespace be specified in the request instead. I don't think we'll be able to Compile over multiple namespaces. Checks access while iterating within a resource

type ListRequest

type ListRequest struct {
	// API group (dashboards.grafana.app)
	Group string

	// ~Kind eg dashboards
	Resource string

	// tenant isolation
	Namespace string

	// Optional subresource
	Subresource string
}

type NamespaceAccessChecker

type NamespaceAccessChecker interface {
	CheckAccess(ctx context.Context, caller claims.AuthInfo, namespace string) error
	CheckAccessByID(ctx context.Context, caller claims.AuthInfo, id int64) error
}

type NamespaceAccessCheckerImpl

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

func NewNamespaceAccessChecker

func NewNamespaceAccessChecker(namespaceFmt claims.NamespaceFormatter, opts ...NamespaceAccessCheckerOption) *NamespaceAccessCheckerImpl

NewNamespaceAuthorizer creates a new namespace authorizer. If both ID token and access token are disabled, the authorizer will always return nil.

func (*NamespaceAccessCheckerImpl) CheckAccess

func (na *NamespaceAccessCheckerImpl) CheckAccess(ctx context.Context, caller claims.AuthInfo, expectedNamespace string) error

func (*NamespaceAccessCheckerImpl) CheckAccessByID

func (na *NamespaceAccessCheckerImpl) CheckAccessByID(ctx context.Context, caller claims.AuthInfo, id int64) error

CheckAccessById uses the specified identifier to use with the namespace formatter to generate the expected namespace which will be checked for access.

type NamespaceAccessCheckerOption

type NamespaceAccessCheckerOption func(*NamespaceAccessCheckerImpl)

func WithDisableAccessTokenNamespaceAccessCheckerOption

func WithDisableAccessTokenNamespaceAccessCheckerOption() NamespaceAccessCheckerOption

WithDisableAccessTokenNamespaceAuthorizerOption disables access token namespace validation.

func WithIDTokenNamespaceAccessCheckerOption

func WithIDTokenNamespaceAccessCheckerOption(required bool) NamespaceAccessCheckerOption

WithIDTokenNamespaceAuthorizerOption enables ID token namespace validation. If required is true, the ID token is required for validation.

func WithTracerAccessCheckerOption

func WithTracerAccessCheckerOption(tracer trace.Tracer) NamespaceAccessCheckerOption

type NamespaceExtractor

type NamespaceExtractor func(context.Context) (string, error)

func MetadataNamespaceExtractor

func MetadataNamespaceExtractor(key string) NamespaceExtractor

MetadataStackIDExtractor extracts the stack ID from the gRPC metadata.

type Resource

type Resource struct {
	// Kind is the type of resource. Ex: "teams", "dashboards", "datasources"
	Kind string
	// The attribute is required for compatibility with the way scopes are defined in Grafana. Ex: "id", "uid"
	Attr string
	// ID is the unique identifier of the resource. Ex: "2", "YYxUSd7ik", "test-datasource"
	ID string
}

Resource represents a resource in Grafana.

func (*Resource) Scope

func (r *Resource) Scope() string

type ServiceAuthorizeFuncOverride

type ServiceAuthorizeFuncOverride interface {
	AuthorizeFuncOverride(ctx context.Context) error
}

ServiceAuthorizeFuncOverride allows a given gRPC service implementation to override the global `AuthorizeFunc`.

If a service implements the AuthorizeFuncOverride method, it takes precedence over the `AuthorizeFunc` method, and will be called instead of AuthorizeFunc for all method invocations within that service.

Directories

Path Synopsis
proto
v1
Package authzv1 is a reverse proxy.
Package authzv1 is a reverse proxy.

Jump to

Keyboard shortcuts

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