Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor authentication server plugin mechanism #8503

Merged
merged 3 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
576 changes: 446 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