github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/remotesrv/interceptors.go (about) 1 // Copyright 2023 Dolthub, Inc. 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 package remotesrv 16 17 import ( 18 "context" 19 "fmt" 20 21 "github.com/sirupsen/logrus" 22 "google.golang.org/grpc" 23 "google.golang.org/grpc/codes" 24 "google.golang.org/grpc/status" 25 ) 26 27 type RequestCredentials struct { 28 Username string 29 Password string 30 Address string 31 } 32 33 type ServerInterceptor struct { 34 Lgr *logrus.Entry 35 AccessController AccessControl 36 } 37 38 var SUPER_USER_RPC_METHODS = map[string]bool{ 39 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/AddTableFiles": true, 40 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/Commit": true, 41 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/GetUploadLocations": true, 42 } 43 44 var CLONE_ADMIN_RPC_METHODS = map[string]bool{ 45 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/GetDownloadLocations": true, 46 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/GetRepoMetadata": true, 47 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/HasChunks": true, 48 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/ListTableFiles": true, 49 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/RefreshTableFileUrl": true, 50 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/Root": true, 51 "/dolt.services.remotesapi.v1alpha1.ChunkStoreService/StreamDownloadLocations": true, 52 } 53 54 // AccessControl is an interface that provides authentication and authorization for the gRPC server. 55 type AccessControl interface { 56 // ApiAuthenticate checks the incoming request for authentication credentials and validates them. If the user's 57 // identity checks out, the returned context will have the sqlContext within it, which contains the user's ID. 58 // If the user is not legitimate, an error is returned. 59 ApiAuthenticate(ctx context.Context) (context.Context, error) 60 // ApiAuthorize checks that the authenticated user has sufficient privileges to perform the requested action. 61 // Currently, our resource policy is binary currently, a user either is a SuperUser (form Commit) or they have a 62 // CLONE_ADMIN grant for read operations. 63 // More resource aware authorization decisions will be needed in the future, but this is sufficient for now. 64 ApiAuthorize(ctx context.Context, superUserReq bool) (bool, error) 65 } 66 67 func (si *ServerInterceptor) Stream() grpc.StreamServerInterceptor { 68 return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 69 needSuperUser, err := requireSuperUser(info.FullMethod) 70 if err != nil { 71 return err 72 } 73 74 if err := si.authenticate(ss.Context(), needSuperUser); err != nil { 75 return err 76 } 77 78 return handler(srv, ss) 79 } 80 } 81 82 func (si *ServerInterceptor) Unary() grpc.UnaryServerInterceptor { 83 return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 84 needSuperUser, err := requireSuperUser(info.FullMethod) 85 if err != nil { 86 return nil, err 87 } 88 89 if err := si.authenticate(ctx, needSuperUser); err != nil { 90 return nil, err 91 } 92 93 return handler(ctx, req) 94 } 95 } 96 97 func (si *ServerInterceptor) Options() []grpc.ServerOption { 98 return []grpc.ServerOption{ 99 grpc.ChainUnaryInterceptor(si.Unary()), 100 grpc.ChainStreamInterceptor(si.Stream()), 101 } 102 } 103 104 func requireSuperUser(path string) (bool, error) { 105 if SUPER_USER_RPC_METHODS[path] { 106 return true, nil 107 } 108 109 if CLONE_ADMIN_RPC_METHODS[path] { 110 return false, nil 111 } 112 113 return false, fmt.Errorf("unknown rpc method: %s", path) 114 } 115 116 // authenticate checks the incoming request for authentication credentials and validates them. If the user is 117 // legitimate, an authorization check is performed. If no error is returned, the user should be allowed to proceed. 118 func (si *ServerInterceptor) authenticate(ctx context.Context, needsSuperUser bool) error { 119 ctx, err := si.AccessController.ApiAuthenticate(ctx) 120 if err != nil { 121 si.Lgr.Warnf("authentication failed: %s", err.Error()) 122 return status.Error(codes.Unauthenticated, err.Error()) 123 } 124 125 // Have a valid user in the context. Check authorization. 126 if authorized, err := si.AccessController.ApiAuthorize(ctx, needsSuperUser); !authorized { 127 si.Lgr.Warnf("authorization failed: %s", err.Error()) 128 return status.Error(codes.PermissionDenied, err.Error()) 129 } 130 131 // Access Granted. 132 return nil 133 }