Split Authentication and Authorization in the chunkstore API impl

This commit is contained in:
Neil Macneale IV
2023-11-16 10:50:34 -08:00
parent e7180e42a5
commit 477a1af160
3 changed files with 54 additions and 14 deletions

View File

@@ -384,7 +384,7 @@ func Serve(
}
ctxFactory := func() (*sql.Context, error) { return sqlEngine.NewDefaultContext(ctx) }
authenticator := newAuthenticator(ctxFactory, sqlEngine.GetUnderlyingEngine().Analyzer.Catalog.MySQLDb)
authenticator := newAccessController(ctxFactory, sqlEngine.GetUnderlyingEngine().Analyzer.Catalog.MySQLDb)
args = sqle.WithUserPasswordAuth(args, authenticator)
args.TLSConfig = serverConf.TLSConfig
@@ -587,29 +587,56 @@ func acquireGlobalSqlServerLock(port int, dEnv *env.DoltEnv) (*env.DBLock, error
return &lck, nil
}
// remotesapiAuth facilitates the implementation remotesrv.AccessControl for the remotesapi server.
type remotesapiAuth struct {
// ctxFactory is a function that returns a new sql.Context. This will create a new conext every time it is called,
// so it should be called once per API request.
ctxFactory func() (*sql.Context, error)
rawDb *mysql_db.MySQLDb
}
func newAuthenticator(ctxFactory func() (*sql.Context, error), rawDb *mysql_db.MySQLDb) remotesrv.Authenticator {
func newAccessController(ctxFactory func() (*sql.Context, error), rawDb *mysql_db.MySQLDb) remotesrv.AccessControl {
return &remotesapiAuth{ctxFactory, rawDb}
}
func (r *remotesapiAuth) Authenticate(creds *remotesrv.RequestCredentials) bool {
// ApiAuthenticate checks the provided credentials against the database and return a SQL context if the credentials are
// valid. If the credentials are invalid, then a nil context is returned. Failures to authenticate are logged.
func (r *remotesapiAuth) ApiAuthenticate(creds *remotesrv.RequestCredentials, lgr *logrus.Entry) *sql.Context {
err := commands.ValidatePasswordWithAuthResponse(r.rawDb, creds.Username, creds.Password)
if err != nil {
return false
lgr.Warnf("API Authentication Failure: %v", err)
return nil
}
ctx, err := r.ctxFactory()
if err != nil {
return false
lgr.Warnf("API Runtime error: %v", err)
return nil
}
ctx.Session.SetClient(sql.Client{User: creds.Username, Address: creds.Address, Capabilities: 0})
address := creds.Address
if strings.Index(address, ":") > 0 {
address, _, err = net.SplitHostPort(creds.Address)
if err != nil {
lgr.Warnf("Invalid Host string for authentication: %s", creds.Address)
return nil
}
}
ctx.Session.SetClient(sql.Client{User: creds.Username, Address: address, Capabilities: 0})
return ctx
}
func (r *remotesapiAuth) ApiAuthorize(ctx *sql.Context, lgr *logrus.Entry) bool {
privOp := sql.NewDynamicPrivilegedOperation(plan.DynamicPrivilege_CloneAdmin)
return r.rawDb.UserHasPrivileges(ctx, privOp)
authorized := r.rawDb.UserHasPrivileges(ctx, privOp)
if !authorized {
lgr.Warnf("API Authorization Failure: %s has not been granted CLONE_ADMIN access", ctx.Session.Client().User)
return false
}
return true
}
func LoadClusterTLSConfig(cfg cluster.Config) (*tls.Config, error) {

View File

@@ -19,6 +19,7 @@ import (
"encoding/base64"
"strings"
"github.com/dolthub/go-mysql-server/sql"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@@ -34,12 +35,13 @@ type RequestCredentials struct {
}
type ServerInterceptor struct {
Lgr *logrus.Entry
Authenticator Authenticator
Lgr *logrus.Entry
AccessController AccessControl
}
type Authenticator interface {
Authenticate(creds *RequestCredentials) bool
type AccessControl interface {
ApiAuthenticate(creds *RequestCredentials, lgr *logrus.Entry) *sql.Context
ApiAuthorize(ctx *sql.Context, lgr *logrus.Entry) bool
}
func (si *ServerInterceptor) Stream() grpc.StreamServerInterceptor {
@@ -69,6 +71,8 @@ func (si *ServerInterceptor) Options() []grpc.ServerOption {
}
}
// authenticate checks the incoming request for authentication credentials and validates them. If the user is
// legitimate, an authorization check is performed. If no error is returned, the user should be allowed to proceed.
func (si *ServerInterceptor) authenticate(ctx context.Context) error {
if md, ok := metadata.FromIncomingContext(ctx); ok {
var username string
@@ -98,9 +102,18 @@ func (si *ServerInterceptor) authenticate(ctx context.Context) error {
si.Lgr.Info("incoming request had no peer")
return status.Error(codes.Unauthenticated, "unauthenticated")
}
if authed := si.Authenticator.Authenticate(&RequestCredentials{Username: username, Password: password, Address: addr.Addr.String()}); !authed {
creds := &RequestCredentials{Username: username, Password: password, Address: addr.Addr.String()}
sqlCtx := si.AccessController.ApiAuthenticate(creds, si.Lgr)
if sqlCtx == nil {
return status.Error(codes.Unauthenticated, "unauthenticated")
}
if authorized := si.AccessController.ApiAuthorize(sqlCtx, si.Lgr); !authorized {
return status.Error(codes.PermissionDenied, "unauthorized")
}
// Access Granted.
return nil
}

View File

@@ -78,10 +78,10 @@ func RemoteSrvServerArgs(ctxFactory func(context.Context) (*sql.Context, error),
return args, nil
}
func WithUserPasswordAuth(args remotesrv.ServerArgs, auth remotesrv.Authenticator) remotesrv.ServerArgs {
func WithUserPasswordAuth(args remotesrv.ServerArgs, authnz remotesrv.AccessControl) remotesrv.ServerArgs {
si := remotesrv.ServerInterceptor{
Lgr: args.Logger,
Authenticator: auth,
Lgr: args.Logger,
AccessController: authnz,
}
args.Options = append(args.Options, si.Options()...)
return args