github.com/cs3org/reva/v2@v2.27.7/internal/grpc/interceptors/auth/auth.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package auth
    20  
    21  import (
    22  	"context"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/bluele/gcache"
    27  	authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
    28  	gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    29  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    30  	"github.com/cs3org/reva/v2/pkg/appctx"
    31  	"github.com/cs3org/reva/v2/pkg/auth/scope"
    32  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    33  	"github.com/cs3org/reva/v2/pkg/errtypes"
    34  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    35  	"github.com/cs3org/reva/v2/pkg/sharedconf"
    36  	"github.com/cs3org/reva/v2/pkg/token"
    37  	tokenmgr "github.com/cs3org/reva/v2/pkg/token/manager/registry"
    38  	"github.com/cs3org/reva/v2/pkg/utils"
    39  	"github.com/mitchellh/mapstructure"
    40  	"github.com/pkg/errors"
    41  	semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
    42  	"go.opentelemetry.io/otel/trace"
    43  	"google.golang.org/grpc"
    44  	"google.golang.org/grpc/codes"
    45  	"google.golang.org/grpc/status"
    46  )
    47  
    48  // name is the Tracer name used to identify this instrumentation library.
    49  const tracerName = "auth"
    50  
    51  var (
    52  	userGroupsCache     gcache.Cache
    53  	scopeExpansionCache gcache.Cache
    54  	cacheOnce           sync.Once
    55  )
    56  
    57  type config struct {
    58  	// TODO(labkode): access a map is more performant as uri as fixed in length
    59  	// for SkipMethods.
    60  	TokenManager            string                            `mapstructure:"token_manager"`
    61  	TokenManagers           map[string]map[string]interface{} `mapstructure:"token_managers"`
    62  	GatewayAddr             string                            `mapstructure:"gateway_addr"`
    63  	UserGroupsCacheSize     int                               `mapstructure:"usergroups_cache_size"`
    64  	ScopeExpansionCacheSize int                               `mapstructure:"scope_expansion_cache_size"`
    65  }
    66  
    67  func parseConfig(m map[string]interface{}) (*config, error) {
    68  	c := &config{}
    69  	if err := mapstructure.Decode(m, c); err != nil {
    70  		err = errors.Wrap(err, "auth: error decoding conf")
    71  		return nil, err
    72  	}
    73  	return c, nil
    74  }
    75  
    76  // NewUnary returns a new unary interceptor that adds
    77  // trace information for the request.
    78  func NewUnary(m map[string]interface{}, unprotected []string, tp trace.TracerProvider) (grpc.UnaryServerInterceptor, error) {
    79  	conf, err := parseConfig(m)
    80  	if err != nil {
    81  		err = errors.Wrap(err, "auth: error parsing config")
    82  		return nil, err
    83  	}
    84  
    85  	if conf.TokenManager == "" {
    86  		conf.TokenManager = "jwt"
    87  	}
    88  	conf.GatewayAddr = sharedconf.GetGatewaySVC(conf.GatewayAddr)
    89  
    90  	if conf.UserGroupsCacheSize == 0 {
    91  		conf.UserGroupsCacheSize = 5000
    92  	}
    93  	if conf.ScopeExpansionCacheSize == 0 {
    94  		conf.ScopeExpansionCacheSize = 5000
    95  	}
    96  
    97  	cacheOnce.Do(func() {
    98  		userGroupsCache = gcache.New(conf.UserGroupsCacheSize).LFU().Build()
    99  		scopeExpansionCache = gcache.New(conf.ScopeExpansionCacheSize).LFU().Build()
   100  	})
   101  
   102  	h, ok := tokenmgr.NewFuncs[conf.TokenManager]
   103  	if !ok {
   104  		return nil, errtypes.NotFound("auth: token manager does not exist: " + conf.TokenManager)
   105  	}
   106  
   107  	tokenManager, err := h(conf.TokenManagers[conf.TokenManager])
   108  	if err != nil {
   109  		return nil, errors.Wrap(err, "auth: error creating token manager")
   110  	}
   111  
   112  	interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
   113  		log := appctx.GetLogger(ctx)
   114  
   115  		span := trace.SpanFromContext(ctx)
   116  		defer span.End()
   117  		if !span.SpanContext().HasTraceID() {
   118  			ctx, span = tp.Tracer(tracerName).Start(ctx, "grpc auth unary")
   119  		}
   120  
   121  		if utils.Skip(info.FullMethod, unprotected) {
   122  			log.Debug().Str("method", info.FullMethod).Msg("skipping auth")
   123  
   124  			// If a token is present, set it anyway, as we might need the user info
   125  			// to decide the storage provider.
   126  			tkn, ok := ctxpkg.ContextGetToken(ctx)
   127  			if ok {
   128  				u, tokenScope, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr)
   129  				if err == nil {
   130  					// store user and scopes in context
   131  					ctx = ctxpkg.ContextSetUser(ctx, u)
   132  					ctx = ctxpkg.ContextSetScopes(ctx, tokenScope)
   133  
   134  					span.SetAttributes(semconv.EnduserIDKey.String(u.Id.OpaqueId))
   135  				}
   136  			}
   137  			return handler(ctx, req)
   138  		}
   139  
   140  		tkn, ok := ctxpkg.ContextGetToken(ctx)
   141  
   142  		if !ok || tkn == "" {
   143  			log.Warn().Msg("access token not found or empty")
   144  			return nil, status.Errorf(codes.Unauthenticated, "auth: core access token not found")
   145  		}
   146  
   147  		// validate the token and ensure access to the resource is allowed
   148  		u, tokenScope, err := dismantleToken(ctx, tkn, req, tokenManager, conf.GatewayAddr)
   149  		if err != nil {
   150  			log.Warn().Err(err).Msg("access token is invalid")
   151  			return nil, status.Errorf(codes.PermissionDenied, "auth: core access token is invalid")
   152  		}
   153  
   154  		// store user and scopes in context
   155  		ctx = ctxpkg.ContextSetUser(ctx, u)
   156  		ctx = ctxpkg.ContextSetScopes(ctx, tokenScope)
   157  
   158  		span.SetAttributes(semconv.EnduserIDKey.String(u.Id.OpaqueId))
   159  
   160  		return handler(ctx, req)
   161  	}
   162  	return interceptor, nil
   163  }
   164  
   165  // NewStream returns a new server stream interceptor
   166  // that adds trace information to the request.
   167  func NewStream(m map[string]interface{}, unprotected []string, tp trace.TracerProvider) (grpc.StreamServerInterceptor, error) {
   168  	conf, err := parseConfig(m)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	if conf.TokenManager == "" {
   174  		conf.TokenManager = "jwt"
   175  	}
   176  
   177  	if conf.UserGroupsCacheSize == 0 {
   178  		conf.UserGroupsCacheSize = 10000
   179  	}
   180  	if conf.ScopeExpansionCacheSize == 0 {
   181  		conf.ScopeExpansionCacheSize = 10000
   182  	}
   183  	cacheOnce.Do(func() {
   184  		userGroupsCache = gcache.New(conf.UserGroupsCacheSize).LFU().Build()
   185  		scopeExpansionCache = gcache.New(conf.ScopeExpansionCacheSize).LFU().Build()
   186  	})
   187  
   188  	h, ok := tokenmgr.NewFuncs[conf.TokenManager]
   189  	if !ok {
   190  		return nil, errtypes.NotFound("auth: token manager not found: " + conf.TokenManager)
   191  	}
   192  
   193  	tokenManager, err := h(conf.TokenManagers[conf.TokenManager])
   194  	if err != nil {
   195  		return nil, errtypes.NotFound("auth: token manager not found: " + conf.TokenManager)
   196  	}
   197  
   198  	interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
   199  		ctx := ss.Context()
   200  		log := appctx.GetLogger(ctx)
   201  
   202  		span := trace.SpanFromContext(ctx)
   203  		defer span.End()
   204  		if !span.SpanContext().HasTraceID() {
   205  			ctx, span = tp.Tracer(tracerName).Start(ctx, "grpc auth new stream")
   206  		}
   207  
   208  		if utils.Skip(info.FullMethod, unprotected) {
   209  			log.Debug().Str("method", info.FullMethod).Msg("skipping auth")
   210  
   211  			// If a token is present, set it anyway, as we might need the user info
   212  			// to decide the storage provider.
   213  			tkn, ok := ctxpkg.ContextGetToken(ctx)
   214  			if ok {
   215  				u, tokenScope, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr)
   216  				if err == nil {
   217  					// store user and scopes in context
   218  					ctx = ctxpkg.ContextSetUser(ctx, u)
   219  					ctx = ctxpkg.ContextSetScopes(ctx, tokenScope)
   220  					ss = newWrappedServerStream(ctx, ss)
   221  
   222  					span.SetAttributes(semconv.EnduserIDKey.String(u.Id.OpaqueId))
   223  				}
   224  			}
   225  
   226  			return handler(srv, ss)
   227  		}
   228  
   229  		tkn, ok := ctxpkg.ContextGetToken(ctx)
   230  
   231  		if !ok || tkn == "" {
   232  			log.Warn().Msg("access token not found")
   233  			return status.Errorf(codes.Unauthenticated, "auth: core access token not found")
   234  		}
   235  
   236  		// validate the token and ensure access to the resource is allowed
   237  		u, tokenScope, err := dismantleToken(ctx, tkn, ss, tokenManager, conf.GatewayAddr)
   238  		if err != nil {
   239  			log.Warn().Err(err).Msg("access token is invalid")
   240  			return status.Errorf(codes.PermissionDenied, "auth: core access token is invalid")
   241  		}
   242  
   243  		// store user and scopes in context
   244  		ctx = ctxpkg.ContextSetUser(ctx, u)
   245  		ctx = ctxpkg.ContextSetScopes(ctx, tokenScope)
   246  		wrapped := newWrappedServerStream(ctx, ss)
   247  
   248  		span.SetAttributes(semconv.EnduserIDKey.String(u.Id.OpaqueId))
   249  
   250  		return handler(srv, wrapped)
   251  	}
   252  	return interceptor, nil
   253  }
   254  
   255  func newWrappedServerStream(ctx context.Context, ss grpc.ServerStream) *wrappedServerStream {
   256  	return &wrappedServerStream{ServerStream: ss, newCtx: ctx}
   257  }
   258  
   259  type wrappedServerStream struct {
   260  	grpc.ServerStream
   261  	newCtx context.Context
   262  }
   263  
   264  func (ss *wrappedServerStream) Context() context.Context {
   265  	return ss.newCtx
   266  }
   267  
   268  // dismantleToken extracts the user and scopes from the reva access token
   269  func dismantleToken(ctx context.Context, tkn string, req interface{}, mgr token.Manager, gatewayAddr string) (*userpb.User, map[string]*authpb.Scope, error) {
   270  	u, tokenScope, err := mgr.DismantleToken(ctx, tkn)
   271  	if err != nil {
   272  		return nil, nil, err
   273  	}
   274  
   275  	if sharedconf.SkipUserGroupsInToken() {
   276  		client, err := pool.GetGatewayServiceClient(gatewayAddr)
   277  		if err != nil {
   278  			return nil, nil, err
   279  		}
   280  		groups, err := getUserGroups(ctx, u, client)
   281  		if err != nil {
   282  			return nil, nil, err
   283  		}
   284  		u.Groups = groups
   285  	}
   286  
   287  	// Check if access to the resource is in the scope of the token
   288  	ok, err := scope.VerifyScope(ctx, tokenScope, req)
   289  	if err != nil {
   290  		return nil, nil, errtypes.InternalError("error verifying scope of access token")
   291  	}
   292  	if ok {
   293  		return u, tokenScope, nil
   294  	}
   295  
   296  	if err = expandAndVerifyScope(ctx, req, tokenScope, u, gatewayAddr, mgr); err != nil {
   297  		return nil, nil, err
   298  	}
   299  
   300  	return u, tokenScope, nil
   301  }
   302  
   303  func getUserGroups(ctx context.Context, u *userpb.User, client gatewayv1beta1.GatewayAPIClient) ([]string, error) {
   304  	if groupsIf, err := userGroupsCache.Get(u.Id.OpaqueId); err == nil {
   305  		log := appctx.GetLogger(ctx)
   306  		log.Info().Str("userid", u.Id.OpaqueId).Msg("user groups found in cache")
   307  		return groupsIf.([]string), nil
   308  	}
   309  
   310  	res, err := client.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: u.Id})
   311  	if err != nil {
   312  		return nil, errors.Wrap(err, "gateway: error calling GetUserGroups")
   313  	}
   314  	_ = userGroupsCache.SetWithExpire(u.Id.OpaqueId, res.Groups, 3600*time.Second)
   315  
   316  	return res.Groups, nil
   317  }