Skip to content

Commit

Permalink
Merge pull request #8503 from dbussink/more-flexible-auth-server
Browse files Browse the repository at this point in the history
Refactor authentication server plugin mechanism
  • Loading branch information
harshit-gangal authored Jul 23, 2021
2 parents f2da287 + 67fc057 commit 48da281
Show file tree
Hide file tree
Showing 17 changed files with 1,019 additions and 459 deletions.
577 changes: 447 additions & 130 deletions go/mysql/auth_server.go

Large diffs are not rendered by default.

76 changes: 45 additions & 31 deletions go/mysql/auth_server_clientcert.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,72 +17,86 @@ limitations under the License.
package mysql

import (
"crypto/x509"
"flag"
"fmt"
"net"

"vitess.io/vitess/go/vt/log"
)

var clientcertAuthMethod = flag.String("mysql_clientcert_auth_method", MysqlClearPassword, "client-side authentication method to use. Supported values: mysql_clear_password, dialog.")
var clientcertAuthMethod = flag.String("mysql_clientcert_auth_method", string(MysqlClearPassword), "client-side authentication method to use. Supported values: mysql_clear_password, dialog.")

// AuthServerClientCert implements AuthServer which enforces client side certificates
type AuthServerClientCert struct {
Method string
methods []AuthMethod
Method AuthMethodDescription
}

// Init is public so it can be called from plugin_auth_clientcert.go (go/cmd/vtgate)
// InitAuthServerClientCert is public so it can be called from plugin_auth_clientcert.go (go/cmd/vtgate)
func InitAuthServerClientCert() {
if flag.CommandLine.Lookup("mysql_server_ssl_ca").Value.String() == "" {
log.Info("Not configuring AuthServerClientCert because mysql_server_ssl_ca is empty")
return
}
if *clientcertAuthMethod != MysqlClearPassword && *clientcertAuthMethod != MysqlDialog {
if *clientcertAuthMethod != string(MysqlClearPassword) && *clientcertAuthMethod != string(MysqlDialog) {
log.Exitf("Invalid mysql_clientcert_auth_method value: only support mysql_clear_password or dialog")
}

ascc := newAuthServerClientCert()
RegisterAuthServer("clientcert", ascc)
}

func newAuthServerClientCert() *AuthServerClientCert {
ascc := &AuthServerClientCert{
Method: *clientcertAuthMethod,
Method: AuthMethodDescription(*clientcertAuthMethod),
}
RegisterAuthServerImpl("clientcert", ascc)
}

// AuthMethod is part of the AuthServer interface.
func (ascc *AuthServerClientCert) AuthMethod(user string) (string, error) {
return ascc.Method, nil
var authMethod AuthMethod
switch AuthMethodDescription(*clientcertAuthMethod) {
case MysqlClearPassword:
authMethod = NewMysqlClearAuthMethod(ascc, ascc)
case MysqlDialog:
authMethod = NewMysqlDialogAuthMethod(ascc, ascc, "")
default:
log.Exitf("Invalid mysql_ldap_auth_method value: only support mysql_clear_password or dialog")
}

ascc.methods = []AuthMethod{authMethod}
return ascc
}

// Salt is not used for this plugin.
func (ascc *AuthServerClientCert) Salt() ([]byte, error) {
return NewSalt()
// AuthMethods returns the implement auth methods for the client
// certificate authentication setup.
func (asl *AuthServerClientCert) AuthMethods() []AuthMethod {
return asl.methods
}

// ValidateHash is unimplemented.
func (ascc *AuthServerClientCert) ValidateHash(salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) {
panic("unimplemented")
// DefaultAuthMethodDescription returns always MysqlNativePassword
// for the client certificate authentication setup.
func (asl *AuthServerClientCert) DefaultAuthMethodDescription() AuthMethodDescription {
return MysqlNativePassword
}

// Negotiate is part of the AuthServer interface.
func (ascc *AuthServerClientCert) Negotiate(c *Conn, user string, remoteAddr net.Addr) (Getter, error) {
// This code depends on the fact that golang's tls server enforces client cert verification.
// Note that the -mysql_server_ssl_ca flag must be set in order for the vtgate to accept client certs.
// If not set, the vtgate will effectively deny all incoming mysql connections, since they will all lack certificates.
// For more info, check out go/vt/vtttls/vttls.go
certs := c.GetTLSClientCerts()
if len(certs) == 0 {
return nil, fmt.Errorf("no client certs for connection ID %v", c.ConnectionID)
}
// HandleUser is part of the UserValidator interface. We
// handle any user here since we don't check up front.
func (asl *AuthServerClientCert) HandleUser(user string) bool {
return true
}

if _, err := AuthServerReadPacketString(c); err != nil {
return nil, err
// UserEntryWithPassword is part of the PlaintextStorage interface
func (asl *AuthServerClientCert) UserEntryWithPassword(userCerts []*x509.Certificate, user string, password string, remoteAddr net.Addr) (Getter, error) {
if len(userCerts) == 0 {
return nil, fmt.Errorf("no client certs for connection")
}

commonName := certs[0].Subject.CommonName
commonName := userCerts[0].Subject.CommonName

if user != commonName {
return nil, fmt.Errorf("MySQL connection username '%v' does not match client cert common name '%v'", user, commonName)
}

return &StaticUserData{
username: commonName,
groups: certs[0].DNSNames,
groups: userCerts[0].DNSNames,
}, nil
}
8 changes: 2 additions & 6 deletions go/mysql/auth_server_clientcert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ const clientCertUsername = "Client Cert"
func TestValidCert(t *testing.T) {
th := &testHandler{}

authServer := &AuthServerClientCert{
Method: MysqlClearPassword,
}
authServer := newAuthServerClientCert()

// Create the listener, so we can get its host.
l, err := NewListener("tcp", ":0", authServer, th, 0, 0, false)
Expand Down Expand Up @@ -120,9 +118,7 @@ func TestValidCert(t *testing.T) {
func TestNoCert(t *testing.T) {
th := &testHandler{}

authServer := &AuthServerClientCert{
Method: MysqlClearPassword,
}
authServer := newAuthServerClientCert()

// Create the listener, so we can get its host.
l, err := NewListener("tcp", ":0", authServer, th, 0, 0, false)
Expand Down
44 changes: 28 additions & 16 deletions go/mysql/auth_server_none.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package mysql

import (
"crypto/x509"
"net"

querypb "vitess.io/vitess/go/vt/proto/query"
Expand All @@ -27,32 +28,43 @@ import (
// With this config, you can connect to a local vtgate using
// the following command line: 'mysql -P port -h ::'.
// It only uses MysqlNativePassword method.
type AuthServerNone struct{}
type AuthServerNone struct {
methods []AuthMethod
}

// AuthMethod is part of the AuthServer interface.
// We always return MysqlNativePassword.
func (a *AuthServerNone) AuthMethod(user string) (string, error) {
return MysqlNativePassword, nil
// AuthMethods returns the list of registered auth methods
// implemented by this auth server.
func (a *AuthServerNone) AuthMethods() []AuthMethod {
return a.methods
}

// Salt makes salt
func (a *AuthServerNone) Salt() ([]byte, error) {
return NewSalt()
// DefaultAuthMethodDescription returns MysqlNativePassword as the default
// authentication method for the auth server implementation.
func (a *AuthServerNone) DefaultAuthMethodDescription() AuthMethodDescription {
return MysqlNativePassword
}

// ValidateHash validates hash
func (a *AuthServerNone) ValidateHash(salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) {
return &NoneGetter{}, nil
// HandleUser validates if this user can use this auth method
func (a *AuthServerNone) HandleUser(user string) bool {
return true
}

// Negotiate is part of the AuthServer interface.
// It will never be called.
func (a *AuthServerNone) Negotiate(c *Conn, user string, remotAddr net.Addr) (Getter, error) {
panic("Negotiate should not be called as AuthMethod returned mysql_native_password")
// UserEntryWithHash validates the user if it exists and returns the information.
// Always accepts any user.
func (a *AuthServerNone) UserEntryWithHash(userCerts []*x509.Certificate, salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) {
return &NoneGetter{}, nil
}

func init() {
RegisterAuthServerImpl("none", &AuthServerNone{})
a := NewAuthServerNone()
RegisterAuthServer("none", a)
}

// NewAuthServerNone returns an empty auth server. Always accepts all clients.
func NewAuthServerNone() *AuthServerNone {
a := &AuthServerNone{}
a.methods = []AuthMethod{NewMysqlNativeAuthMethod(a, a)}
return a
}

// NoneGetter holds the empty string
Expand Down
Loading

0 comments on commit 48da281

Please sign in to comment.