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  }