mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			159 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			159 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
package interceptors
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"google.golang.org/grpc/codes"
 | 
						|
	"google.golang.org/grpc/metadata"
 | 
						|
	"google.golang.org/grpc/status"
 | 
						|
 | 
						|
	"github.com/grafana/grafana/pkg/components/satokengen"
 | 
						|
	"github.com/grafana/grafana/pkg/infra/log"
 | 
						|
	"github.com/grafana/grafana/pkg/services/accesscontrol"
 | 
						|
	"github.com/grafana/grafana/pkg/services/apikey"
 | 
						|
	grpccontext "github.com/grafana/grafana/pkg/services/grpcserver/context"
 | 
						|
	"github.com/grafana/grafana/pkg/services/org"
 | 
						|
	"github.com/grafana/grafana/pkg/services/user"
 | 
						|
)
 | 
						|
 | 
						|
type Authenticator interface {
 | 
						|
	Authenticate(ctx context.Context) (context.Context, error)
 | 
						|
}
 | 
						|
 | 
						|
// authenticator can authenticate GRPC requests.
 | 
						|
type authenticator struct {
 | 
						|
	contextHandler grpccontext.ContextHandler
 | 
						|
	logger         log.Logger
 | 
						|
 | 
						|
	APIKeyService        apikey.Service
 | 
						|
	UserService          user.Service
 | 
						|
	AccessControlService accesscontrol.Service
 | 
						|
}
 | 
						|
 | 
						|
func ProvideAuthenticator(apiKeyService apikey.Service, userService user.Service, accessControlService accesscontrol.Service, contextHandler grpccontext.ContextHandler) Authenticator {
 | 
						|
	return &authenticator{
 | 
						|
		contextHandler: contextHandler,
 | 
						|
		logger:         log.New("grpc-server-authenticator"),
 | 
						|
 | 
						|
		AccessControlService: accessControlService,
 | 
						|
		APIKeyService:        apiKeyService,
 | 
						|
		UserService:          userService,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Authenticate checks that a token exists and is valid, and then removes the token from the
 | 
						|
// authorization header in the context.
 | 
						|
func (a *authenticator) Authenticate(ctx context.Context) (context.Context, error) {
 | 
						|
	return a.tokenAuth(ctx)
 | 
						|
}
 | 
						|
 | 
						|
const tokenPrefix = "Bearer "
 | 
						|
 | 
						|
func (a *authenticator) tokenAuth(ctx context.Context) (context.Context, error) {
 | 
						|
	auth, err := extractAuthorization(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return ctx, err
 | 
						|
	}
 | 
						|
 | 
						|
	if !strings.HasPrefix(auth, tokenPrefix) {
 | 
						|
		return ctx, status.Error(codes.Unauthenticated, `missing "Bearer " prefix in "authorization" value`)
 | 
						|
	}
 | 
						|
 | 
						|
	token := strings.TrimPrefix(auth, tokenPrefix)
 | 
						|
	if token == "" {
 | 
						|
		return ctx, status.Error(codes.Unauthenticated, "token required")
 | 
						|
	}
 | 
						|
 | 
						|
	newCtx := purgeHeader(ctx, "authorization")
 | 
						|
 | 
						|
	signedInUser, err := a.getSignedInUser(ctx, token)
 | 
						|
	if err != nil {
 | 
						|
		a.logger.Warn("request with invalid token", "error", err, "token", token)
 | 
						|
		return ctx, status.Error(codes.Unauthenticated, "invalid token")
 | 
						|
	}
 | 
						|
 | 
						|
	newCtx = a.contextHandler.SetUser(newCtx, signedInUser)
 | 
						|
 | 
						|
	return newCtx, nil
 | 
						|
}
 | 
						|
 | 
						|
func (a *authenticator) getSignedInUser(ctx context.Context, token string) (*user.SignedInUser, error) {
 | 
						|
	decoded, err := satokengen.Decode(token)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	hash, err := decoded.Hash()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	apikey, err := a.APIKeyService.GetAPIKeyByHash(ctx, hash)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if apikey == nil || apikey.ServiceAccountId == nil {
 | 
						|
		return nil, status.Error(codes.Unauthenticated, "api key does not have a service account")
 | 
						|
	}
 | 
						|
 | 
						|
	querySignedInUser := user.GetSignedInUserQuery{UserID: *apikey.ServiceAccountId, OrgID: apikey.OrgID}
 | 
						|
	signedInUser, err := a.UserService.GetSignedInUserWithCacheCtx(ctx, &querySignedInUser)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if signedInUser == nil {
 | 
						|
		return nil, status.Error(codes.Unauthenticated, "service account not found")
 | 
						|
	}
 | 
						|
 | 
						|
	if !signedInUser.HasRole(org.RoleAdmin) {
 | 
						|
		return nil, status.Error(codes.PermissionDenied, "service account does not have admin role")
 | 
						|
	}
 | 
						|
 | 
						|
	// disabled service accounts are not allowed to access the API
 | 
						|
	if signedInUser.IsDisabled {
 | 
						|
		return nil, status.Error(codes.PermissionDenied, "service account is disabled")
 | 
						|
	}
 | 
						|
 | 
						|
	if signedInUser.Permissions == nil {
 | 
						|
		signedInUser.Permissions = make(map[int64]map[string][]string)
 | 
						|
	}
 | 
						|
 | 
						|
	if signedInUser.Permissions[signedInUser.OrgID] == nil {
 | 
						|
		permissions, err := a.AccessControlService.GetUserPermissions(ctx, signedInUser, accesscontrol.Options{})
 | 
						|
		if err != nil {
 | 
						|
			a.logger.Error("failed fetching permissions for user", "userID", signedInUser.UserID, "error", err)
 | 
						|
		}
 | 
						|
		signedInUser.Permissions[signedInUser.OrgID] = accesscontrol.GroupScopesByAction(permissions)
 | 
						|
	}
 | 
						|
 | 
						|
	return signedInUser, nil
 | 
						|
}
 | 
						|
 | 
						|
func extractAuthorization(ctx context.Context) (string, error) {
 | 
						|
	md, ok := metadata.FromIncomingContext(ctx)
 | 
						|
	if !ok {
 | 
						|
		return "", status.Error(codes.Unauthenticated, "no headers in request")
 | 
						|
	}
 | 
						|
 | 
						|
	authHeaders, ok := md["authorization"]
 | 
						|
	if !ok {
 | 
						|
		return "", status.Error(codes.Unauthenticated, `no "authorization" header in request`)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(authHeaders) != 1 {
 | 
						|
		return "", status.Error(codes.Unauthenticated, `malformed "authorization" header: one value required`)
 | 
						|
	}
 | 
						|
 | 
						|
	return authHeaders[0], nil
 | 
						|
}
 | 
						|
 | 
						|
func purgeHeader(ctx context.Context, header string) context.Context {
 | 
						|
	md, _ := metadata.FromIncomingContext(ctx)
 | 
						|
	mdCopy := md.Copy()
 | 
						|
	mdCopy[header] = nil
 | 
						|
	return metadata.NewIncomingContext(ctx, mdCopy)
 | 
						|
}
 |