mirror of
https://github.com/grafana/grafana.git
synced 2024-12-27 09:21:35 -06:00
2788817107
* AuthZ: Implement Check --------- Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
202 lines
6.8 KiB
Go
202 lines
6.8 KiB
Go
package authz
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
authzlib "github.com/grafana/authlib/authz"
|
|
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
|
"github.com/grafana/authlib/claims"
|
|
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/authn"
|
|
"github.com/grafana/grafana/pkg/services/authz/mappers"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/folder"
|
|
"github.com/grafana/grafana/pkg/services/grpcserver"
|
|
)
|
|
|
|
var _ authzv1.AuthzServiceServer = (*legacyServer)(nil)
|
|
var _ grpc_auth.ServiceAuthFuncOverride = (*legacyServer)(nil)
|
|
var _ authzlib.ServiceAuthorizeFuncOverride = (*legacyServer)(nil)
|
|
|
|
func newLegacyServer(
|
|
authnSvc authn.Service, ac accesscontrol.AccessControl, folderSvc folder.Service,
|
|
features featuremgmt.FeatureToggles, grpcServer grpcserver.Provider, tracer tracing.Tracer, cfg *Cfg,
|
|
) (*legacyServer, error) {
|
|
if !features.IsEnabledGlobally(featuremgmt.FlagAuthZGRPCServer) {
|
|
return nil, nil
|
|
}
|
|
|
|
l := &legacyServer{
|
|
ac: ac.WithoutResolvers(), // We want to skip the folder tree resolution as it's done by the service
|
|
authnSvc: authnSvc,
|
|
folderSvc: folderSvc,
|
|
logger: log.New("authz-grpc-server"),
|
|
tracer: tracer,
|
|
mapper: mappers.NewK8sRbacMapper(),
|
|
}
|
|
|
|
if cfg.listen {
|
|
if !cfg.allowInsecure {
|
|
l.logger.Error("Not allowing the authz service to run in insecure mode as Auth is skipped")
|
|
} else {
|
|
grpcServer.GetServer().RegisterService(&authzv1.AuthzService_ServiceDesc, l)
|
|
}
|
|
}
|
|
|
|
return l, nil
|
|
}
|
|
|
|
type legacyServer struct {
|
|
authzv1.UnimplementedAuthzServiceServer
|
|
|
|
ac accesscontrol.AccessControl
|
|
authnSvc authn.Service
|
|
folderSvc folder.Service
|
|
logger log.Logger
|
|
tracer tracing.Tracer
|
|
mapper *mappers.K8sRbacMapper
|
|
}
|
|
|
|
// AuthFuncOverride is a function that allows to override the default auth function.
|
|
// This is ok for now since we don't have on-prem access token support.
|
|
func (l *legacyServer) AuthFuncOverride(ctx context.Context, _ string) (context.Context, error) {
|
|
ctx, span := l.tracer.Start(ctx, "authz.AuthFuncOverride")
|
|
defer span.End()
|
|
|
|
return ctx, nil
|
|
}
|
|
|
|
// AuthorizeFuncOverride is a function that allows to override the default authorize function that checks the namespace of the caller.
|
|
// This is ok for now since we don't have on-prem access token support.
|
|
func (l *legacyServer) AuthorizeFuncOverride(ctx context.Context) error {
|
|
_, span := l.tracer.Start(ctx, "authz.AuthorizeFuncOverride")
|
|
defer span.End()
|
|
|
|
return nil
|
|
}
|
|
|
|
func wrapErr(err error) error {
|
|
return status.Error(codes.Internal, fmt.Errorf("authz check failed: %w", err).Error())
|
|
}
|
|
|
|
func validateRequest(req *authzv1.CheckRequest) error {
|
|
if req.GetGroup() == "" {
|
|
return status.Error(codes.InvalidArgument, "group is required")
|
|
}
|
|
if req.GetResource() == "" {
|
|
return status.Error(codes.InvalidArgument, "resource is required")
|
|
}
|
|
if req.GetVerb() == "" {
|
|
return status.Error(codes.InvalidArgument, "verb is required")
|
|
}
|
|
if req.GetSubject() == "" {
|
|
return status.Error(codes.InvalidArgument, "subject is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *legacyServer) Check(ctx context.Context, req *authzv1.CheckRequest) (*authzv1.CheckResponse, error) {
|
|
ctx, span := l.tracer.Start(ctx, "authz.Check")
|
|
defer span.End()
|
|
ctxLogger := l.logger.FromContext(ctx)
|
|
|
|
deny := &authzv1.CheckResponse{Allowed: false}
|
|
if err := validateRequest(req); err != nil {
|
|
ctxLogger.Error("invalid request", "error", err)
|
|
return deny, err
|
|
}
|
|
|
|
namespace := req.GetNamespace()
|
|
info, err := claims.ParseNamespace(namespace)
|
|
// We have to check the stackID as ParseNamespace returns orgID 1 for stacks namespaces
|
|
if err != nil || info.OrgID == 0 || info.StackID != 0 {
|
|
ctxLogger.Error("invalid namespace", "namespace", namespace, "error", err)
|
|
return deny, status.Error(codes.InvalidArgument, "invalid namespace: "+namespace)
|
|
}
|
|
|
|
// Get the RBAC action associated with the request
|
|
action, ok := l.mapper.Action(req.Group, req.Resource, req.Verb)
|
|
if !ok {
|
|
ctxLogger.Error("could not find associated rbac action", "group", req.Group, "resource", req.Resource, "verb", req.Verb)
|
|
return deny, wrapErr(fmt.Errorf("could not find associated rbac action"))
|
|
}
|
|
|
|
// Get the user from the subject
|
|
user, err := l.authnSvc.ResolveIdentity(ctx, info.OrgID, req.Subject)
|
|
if err != nil {
|
|
// TODO: should probably distinguish between not found and other errors
|
|
ctxLogger.Error("could not resolve identity", "subject", req.Subject, "orgId", info.OrgID)
|
|
return deny, wrapErr(fmt.Errorf("could not resolve identity"))
|
|
}
|
|
|
|
// Check if the user has the action solely
|
|
if req.Name == "" && req.Folder == "" {
|
|
ev := accesscontrol.EvalPermission(action)
|
|
hasAccess, err := l.ac.Evaluate(ctx, user, ev)
|
|
if err != nil {
|
|
ctxLogger.Error("could not evaluate permission", "subject", req.Subject, "orgId", info.OrgID, "action", action)
|
|
return deny, wrapErr(fmt.Errorf("could not evaluate permission"))
|
|
}
|
|
|
|
return &authzv1.CheckResponse{Allowed: hasAccess}, nil
|
|
}
|
|
|
|
scopes := make([]string, 0, 1)
|
|
// If a parent is specified: Check if the user has access to any of the parent folders
|
|
if req.Folder != "" {
|
|
scopes, err = l.getFolderTree(ctx, info.OrgID, req.Folder)
|
|
if err != nil {
|
|
ctxLogger.Error("could not get folder tree", "folder", req.Folder, "orgId", info.OrgID, "error", err)
|
|
return nil, wrapErr(err)
|
|
}
|
|
}
|
|
// If a resource is specified: Check if the user has access to the requested resource
|
|
if req.Name != "" {
|
|
scope, ok := l.mapper.Scope(req.Group, req.Resource, req.Name)
|
|
if !ok {
|
|
ctxLogger.Error("could not get attribute for resource", "resource", req.Resource)
|
|
return deny, wrapErr(fmt.Errorf("could not get attribute for resource"))
|
|
}
|
|
scopes = append(scopes, scope)
|
|
}
|
|
|
|
ev := accesscontrol.EvalPermission(action, scopes...)
|
|
allowed, err := l.ac.Evaluate(ctx, user, ev)
|
|
if err != nil {
|
|
ctxLogger.Error("could not evaluate permission",
|
|
"subject", req.Subject,
|
|
"orgId", info.OrgID,
|
|
"action", action,
|
|
"folder", req.Folder,
|
|
"scopes_count", len(scopes))
|
|
return deny, fmt.Errorf("could not evaluate permission")
|
|
}
|
|
|
|
return &authzv1.CheckResponse{Allowed: allowed}, nil
|
|
}
|
|
|
|
func (l *legacyServer) getFolderTree(ctx context.Context, orgID int64, parent string) ([]string, error) {
|
|
ctx, span := l.tracer.Start(ctx, "authz.getFolderTree")
|
|
defer span.End()
|
|
|
|
scopes := make([]string, 0, 6)
|
|
// Get the folder tree
|
|
folders, err := l.folderSvc.GetParents(ctx, folder.GetParentsQuery{UID: parent, OrgID: orgID})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scopes = append(scopes, "folders:uid:"+parent)
|
|
for _, f := range folders {
|
|
scopes = append(scopes, "folders:uid:"+f.UID)
|
|
}
|
|
|
|
return scopes, nil
|
|
}
|