vitess.io/vitess@v0.16.2/go/vt/vtadmin/rbac/authentication.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package rbac 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "net/http" 24 "plugin" 25 "sync" 26 27 grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 28 "google.golang.org/grpc" 29 30 "vitess.io/vitess/go/vt/proto/vtrpc" 31 "vitess.io/vitess/go/vt/vterrors" 32 ) 33 34 // Authenticator defines the interface vtadmin authentication plugins must 35 // implement. Authenticators are installed at the grpc interceptor and http 36 // middleware layers. 37 type Authenticator interface { 38 // Authenticate returns an Actor given a context. This method is called 39 // from the stream and unary grpc server interceptors, and are passed the 40 // stream and request contexts, respectively. 41 // 42 // Returning an error from the authenticator will fail the request. To 43 // denote an authenticated request, return (nil, nil) instead. 44 Authenticate(ctx context.Context) (*Actor, error) 45 // AuthenticateHTTP returns an actor given an http.Request. 46 // 47 // Returning an error from the authenticator will fail the request. To 48 // denote an authenticated request, return (nil, nil) instead. 49 AuthenticateHTTP(r *http.Request) (*Actor, error) 50 } 51 52 // AuthenticationStreamInterceptor returns a grpc.StreamServerInterceptor that 53 // uses the given Authenticator create an Actor from the stream's context, which 54 // is then stored in the stream context for later use. 55 // 56 // If the authenticator returns an error, the overall streaming rpc returns an 57 // UNAUTHENTICATED error. 58 func AuthenticationStreamInterceptor(authn Authenticator) grpc.StreamServerInterceptor { 59 return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 60 actor, err := authn.Authenticate(ss.Context()) 61 if err != nil { 62 return vterrors.Errorf(vtrpc.Code_UNAUTHENTICATED, "%s", err) 63 } 64 65 wrappedStream := grpc_middleware.WrapServerStream(ss) 66 wrappedStream.WrappedContext = NewContext(ss.Context(), actor) 67 68 return handler(srv, wrappedStream) 69 } 70 } 71 72 // AuthenticationUnaryInterceptor returns a grpc.UnaryServerInterceptor that 73 // uses the given Authenticator create an Actor from the request context, which 74 // is then stored in the request context for later use. 75 // 76 // If the authenticator returns an error, the overall unary rpc returns an 77 // UNAUTHENTICATED error. 78 func AuthenticationUnaryInterceptor(authn Authenticator) grpc.UnaryServerInterceptor { 79 return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { 80 actor, err := authn.Authenticate(ctx) 81 if err != nil { 82 return nil, vterrors.Errorf(vtrpc.Code_UNAUTHENTICATED, "%s", err) 83 } 84 85 ctx = NewContext(ctx, actor) 86 return handler(ctx, req) 87 } 88 } 89 90 // Actor represents the subject in the "subject action resource" of an 91 // authorization check. It has a name and many roles. 92 type Actor struct { 93 Name string `json:"name"` 94 Roles []string `json:"roles"` 95 } 96 97 type actorkey struct{} 98 99 // NewContext returns a context with the given actor stored in it. This is used 100 // to pass actor information from the authentication middleware and interceptors 101 // to the actual vtadmin api methods. 102 func NewContext(ctx context.Context, actor *Actor) context.Context { 103 return context.WithValue(ctx, actorkey{}, actor) 104 } 105 106 // FromContext extracts an actor from the context, if one exists. 107 func FromContext(ctx context.Context) (*Actor, bool) { 108 actor, ok := ctx.Value(actorkey{}).(*Actor) 109 if !ok { 110 return nil, false 111 } 112 113 return actor, true 114 } 115 116 var ( 117 // ErrUnregisteredAuthenticationImpl is returned when an RBAC config 118 // specifies an authenticator name that was not registered. 119 ErrUnregisteredAuthenticationImpl = errors.New("unregistered Authenticator implementation") 120 authenticators = map[string]func() Authenticator{} 121 authenticatorsM sync.Mutex 122 ) 123 124 // RegisterAuthenticator registers an authenticator implementation by name. It 125 // is not safe for concurrent use. 126 // 127 // Plugin-based authenticators are loaded separately, and need not call this 128 // function. 129 func RegisterAuthenticator(name string, f func() Authenticator) { 130 if _, ok := authenticators[name]; ok { 131 panic(fmt.Sprintf("authenticator already registered with name: %s", name)) 132 } 133 134 authenticators[name] = f 135 } 136 137 func loadAuthenticatorPlugin(path string) (Authenticator, error) { 138 authenticatorsM.Lock() 139 defer authenticatorsM.Unlock() 140 141 if f, ok := authenticators[path]; ok { 142 return f(), nil 143 } 144 145 p, err := plugin.Open(path) 146 if err != nil { 147 return nil, err 148 } 149 150 sym, err := p.Lookup("NewAuthenticator") 151 if err != nil { 152 return nil, err 153 } 154 155 f, ok := sym.(func() Authenticator) 156 if !ok { 157 return nil, fmt.Errorf("symbol NewAuthenticator must be of type `func() Authenticator`; have %T", sym) 158 } 159 160 authenticators[path] = f 161 return f(), nil 162 }