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 }