зеркало из https://github.com/github/vitess-gh.git
Adding AuthServer support for dialog.
Mostly the refactoring of the AuthServer interface is in here. There is no real test for the dialog plugin method.
This commit is contained in:
Родитель
26a4b33cab
Коммит
d33b929e34
|
@ -3,8 +3,10 @@ package mysqlconn
|
|||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
|
||||
log "github.com/golang/glog"
|
||||
"github.com/youtube/vitess/go/sqldb"
|
||||
)
|
||||
|
||||
// AuthServer is the interface that servers must implement to validate
|
||||
|
@ -21,26 +23,40 @@ import (
|
|||
// password is sent in the clear. That may not be suitable for some
|
||||
// use cases.
|
||||
type AuthServer interface {
|
||||
// UseClearText returns true is Cleartext auth is used.
|
||||
// - If it is not set, Salt() and ValidateHash() are called.
|
||||
// The server starts up in mysql_native_password mode.
|
||||
// (but ValidateClearText can also be called, if client
|
||||
// switched to Cleartext).
|
||||
// - If it is set, ValidateClearText() is called.
|
||||
// The server starts up in mysql_clear_password mode.
|
||||
UseClearText() bool
|
||||
// AuthMethod returns the authentication method to use for the
|
||||
// given user. If this returns MysqlNativePassword
|
||||
// (mysql_native_password), then ValidateHash() will be
|
||||
// called, and no further roundtrip with the client is
|
||||
// expected. If anything else is returned, Negotiate()
|
||||
// will be called on the connection, and the AuthServer
|
||||
// needs to handle the packets.
|
||||
AuthMethod(user string) (string, error)
|
||||
|
||||
// Salt returns the salt to use for a connection.
|
||||
// It should be 20 bytes of data.
|
||||
// Most implementations should just use mysqlconn.NewSalt().
|
||||
// (this is meant to support a plugin that would use an
|
||||
// existing MySQL server as the source of auth, and just forward
|
||||
// the salt generated by that server).
|
||||
// Do not return zero bytes, as a known salt can be the source
|
||||
// of a crypto attack.
|
||||
Salt() ([]byte, error)
|
||||
|
||||
// ValidateHash validates the data sent by the client matches
|
||||
// what the server computes. It also returns the user data.
|
||||
ValidateHash(salt []byte, user string, authResponse []byte) (Getter, error)
|
||||
|
||||
// ValidateClearText validates a user / password is correct.
|
||||
// It also returns the user data.
|
||||
ValidateClearText(user, password string) (Getter, error)
|
||||
// Negotiate is called if AuthMethod returns anything else
|
||||
// than MysqlNativePassword. It is handed the connection after the
|
||||
// AuthSwitchRequest packet is sent.
|
||||
// - If the negotiation fails, it should just return an error
|
||||
// (should be a sqldb.SQLError if possible).
|
||||
// The framework is responsible for writing the Error packet
|
||||
// and closing the connection in that case.
|
||||
// - If the negotiation works, it should return the Getter,
|
||||
// and no error. The framework is responsible for writing the
|
||||
// OK packet.
|
||||
Negotiate(c *Conn, user string) (Getter, error)
|
||||
}
|
||||
|
||||
// authServers is a registry of AuthServer implementations.
|
||||
|
@ -63,8 +79,8 @@ func GetAuthServer(name string) AuthServer {
|
|||
return authServer
|
||||
}
|
||||
|
||||
// newSalt returns a 20 character salt.
|
||||
func newSalt() ([]byte, error) {
|
||||
// NewSalt returns a 20 character salt.
|
||||
func NewSalt() ([]byte, error) {
|
||||
salt := make([]byte, 20)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return nil, err
|
||||
|
@ -109,3 +125,56 @@ func scramblePassword(salt, password []byte) []byte {
|
|||
}
|
||||
return scramble
|
||||
}
|
||||
|
||||
// AuthServerWritePacketString is a helper method to write a null
|
||||
// terminated string into a packet, mainly to be used with the dialog
|
||||
// client plugin.
|
||||
func AuthServerWritePacketString(c *Conn, message string) error {
|
||||
data := c.startEphemeralPacket(len(message) + 1)
|
||||
pos := writeNullString(data, 0, message)
|
||||
if pos != len(data) {
|
||||
return fmt.Errorf("error building AuthServerWritePacketString: got %v bytes expected %v", pos, len(data))
|
||||
}
|
||||
if err := c.writeEphemeralPacket(true); err != nil {
|
||||
return sqldb.NewSQLError(CRServerGone, SSUnknownSQLState, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthServerReadPacketString is a helper method to read a packet
|
||||
// as a null terminated string. It is used by the mysql_clear_password
|
||||
// and dialog plugins.
|
||||
func AuthServerReadPacketString(c *Conn) (string, error) {
|
||||
// Read a packet, the password is the payload, as a
|
||||
// zero terminated string.
|
||||
data, err := c.ReadPacket()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(data) == 0 || data[len(data)-1] != 0 {
|
||||
return "", fmt.Errorf("received invalid response packet")
|
||||
}
|
||||
return string(data[:len(data)-1]), nil
|
||||
}
|
||||
|
||||
// AuthServerNegotiateClearOrDialog will finish a negotiation based on
|
||||
// the method type for the connection. Only supports
|
||||
// MysqlClearPassword and MysqlDialog.
|
||||
func AuthServerNegotiateClearOrDialog(c *Conn, method string) (string, error) {
|
||||
switch method {
|
||||
case MysqlClearPassword:
|
||||
// The password is the next packet in plain text.
|
||||
return AuthServerReadPacketString(c)
|
||||
|
||||
case MysqlDialog:
|
||||
// Send a question packet.
|
||||
if err := AuthServerWritePacketString(c, "Enter Password:"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Read the password as the response.
|
||||
return AuthServerReadPacketString(c)
|
||||
default:
|
||||
return "", fmt.Errorf("unrecognized method: %v", method)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,18 +8,18 @@ import (
|
|||
// It's meant to be used for testing and prototyping.
|
||||
// With this config, you can connect to a local vtgate using
|
||||
// the following command line: 'mysql -P port -h ::'.
|
||||
type AuthServerNone struct {
|
||||
ClearText bool
|
||||
}
|
||||
// It only uses MysqlNativePassword method.
|
||||
type AuthServerNone struct{}
|
||||
|
||||
// UseClearText reports clear text status
|
||||
func (a *AuthServerNone) UseClearText() bool {
|
||||
return a.ClearText
|
||||
// AuthMethod is part of the AuthServer interface.
|
||||
// We always return MysqlNativePassword.
|
||||
func (a *AuthServerNone) AuthMethod(user string) (string, error) {
|
||||
return MysqlNativePassword, nil
|
||||
}
|
||||
|
||||
// Salt makes salt
|
||||
func (a *AuthServerNone) Salt() ([]byte, error) {
|
||||
return make([]byte, 20), nil
|
||||
return NewSalt()
|
||||
}
|
||||
|
||||
// ValidateHash validates hash
|
||||
|
@ -27,9 +27,10 @@ func (a *AuthServerNone) ValidateHash(salt []byte, user string, authResponse []b
|
|||
return &NoneGetter{}, nil
|
||||
}
|
||||
|
||||
// ValidateClearText validates clear text
|
||||
func (a *AuthServerNone) ValidateClearText(user, password string) (Getter, error) {
|
||||
return &NoneGetter{}, nil
|
||||
// Negotiate is part of the AuthServer interface.
|
||||
// It will never be called.
|
||||
func (a *AuthServerNone) Negotiate(c *Conn, user string) (Getter, error) {
|
||||
panic("Negotiate should not be called as AuthMethod returned mysql_native_password")
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -13,14 +13,18 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
mysqlAuthServerStaticFile = flag.String("mysql_auth_server_static_file", "", "JSON File to read the users/passwords from.")
|
||||
mysqlAuthServerStaticString = flag.String("mysql_auth_server_static_string", "", "JSON representation of the users/passwords config.")
|
||||
mysqlAuthServerStaticFile = flag.String("mysql_auth_server_static_file", "", "JSON File to read the users/passwords from.")
|
||||
mysqlAuthServerStaticString = flag.String("mysql_auth_server_static_string", "", "JSON representation of the users/passwords config.")
|
||||
)
|
||||
|
||||
// AuthServerStatic implements AuthServer using a static configuration.
|
||||
type AuthServerStatic struct {
|
||||
// ClearText can be set to force the use of ClearText auth.
|
||||
ClearText bool
|
||||
// Method can be set to:
|
||||
// - MysqlNativePassword
|
||||
// - MysqlClearPassword
|
||||
// - MysqlDialog
|
||||
// It defaults to MysqlNativePassword.
|
||||
Method string
|
||||
|
||||
// Entries contains the users, passwords and user data.
|
||||
Entries map[string]*AuthServerStaticEntry
|
||||
|
@ -32,7 +36,7 @@ type AuthServerStaticEntry struct {
|
|||
UserData string
|
||||
}
|
||||
|
||||
// Init Handles initializing the AuthServerStatic if necessary.
|
||||
// InitAuthServerStatic Handles initializing the AuthServerStatic if necessary.
|
||||
func InitAuthServerStatic() {
|
||||
// Check parameters.
|
||||
if *mysqlAuthServerStaticFile == "" && *mysqlAuthServerStaticString == "" {
|
||||
|
@ -52,8 +56,8 @@ func InitAuthServerStatic() {
|
|||
// NewAuthServerStatic returns a new empty AuthServerStatic.
|
||||
func NewAuthServerStatic() *AuthServerStatic {
|
||||
return &AuthServerStatic{
|
||||
ClearText: false,
|
||||
Entries: make(map[string]*AuthServerStaticEntry),
|
||||
Method: MysqlNativePassword,
|
||||
Entries: make(map[string]*AuthServerStaticEntry),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,14 +85,14 @@ func RegisterAuthServerStaticFromParams(file, str string) {
|
|||
RegisterAuthServerImpl("static", authServerStatic)
|
||||
}
|
||||
|
||||
// UseClearText is part of the AuthServer interface.
|
||||
func (a *AuthServerStatic) UseClearText() bool {
|
||||
return a.ClearText
|
||||
// AuthMethod is part of the AuthServer interface.
|
||||
func (a *AuthServerStatic) AuthMethod(user string) (string, error) {
|
||||
return a.Method, nil
|
||||
}
|
||||
|
||||
// Salt is part of the AuthServer interface.
|
||||
func (a *AuthServerStatic) Salt() ([]byte, error) {
|
||||
return newSalt()
|
||||
return NewSalt()
|
||||
}
|
||||
|
||||
// ValidateHash is part of the AuthServer interface.
|
||||
|
@ -108,8 +112,16 @@ func (a *AuthServerStatic) ValidateHash(salt []byte, user string, authResponse [
|
|||
return &StaticUserData{entry.UserData}, nil
|
||||
}
|
||||
|
||||
// ValidateClearText is part of the AuthServer interface.
|
||||
func (a *AuthServerStatic) ValidateClearText(user, password string) (Getter, error) {
|
||||
// Negotiate is part of the AuthServer interface.
|
||||
// It will be called if Method is anything else than MysqlNativePassword.
|
||||
// We only recognize MysqlClearPassword and MysqlDialog here.
|
||||
func (a *AuthServerStatic) Negotiate(c *Conn, user string) (Getter, error) {
|
||||
// Finish the negotiation.
|
||||
password, err := AuthServerNegotiateClearOrDialog(c, a.Method)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find the entry.
|
||||
entry, ok := a.Entries[user]
|
||||
if !ok {
|
||||
|
|
|
@ -262,7 +262,7 @@ func (c *Conn) clientHandshake(characterSet uint8, params *sqldb.ConnParams) err
|
|||
if err != nil {
|
||||
return sqldb.NewSQLError(CRServerHandshakeErr, SSUnknownSQLState, "cannot parse auth switch request: %v", err)
|
||||
}
|
||||
if pluginName != mysqlClearPassword {
|
||||
if pluginName != MysqlClearPassword {
|
||||
return sqldb.NewSQLError(CRServerHandshakeErr, SSUnknownSQLState, "server asked for unsupported auth method: %v", pluginName)
|
||||
}
|
||||
|
||||
|
@ -437,8 +437,8 @@ func (c *Conn) parseInitialHandshakePacket(data []byte) (uint32, []byte, error)
|
|||
authPluginName = string(data[pos : len(data)-1])
|
||||
}
|
||||
|
||||
if authPluginName != mysqlNativePassword {
|
||||
return 0, nil, sqldb.NewSQLError(CRMalformedPacket, SSUnknownSQLState, "parseInitialHandshakePacket: only support %v auth plugin name, but got %v", mysqlNativePassword, authPluginName)
|
||||
if authPluginName != MysqlNativePassword {
|
||||
return 0, nil, sqldb.NewSQLError(CRMalformedPacket, SSUnknownSQLState, "parseInitialHandshakePacket: only support %v auth plugin name, but got %v", MysqlNativePassword, authPluginName)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -572,7 +572,7 @@ func (c *Conn) writeHandshakeResponse41(capabilities uint32, salt []byte, charac
|
|||
}
|
||||
|
||||
// Assume native client during response
|
||||
pos = writeNullString(data, pos, mysqlNativePassword)
|
||||
pos = writeNullString(data, pos, MysqlNativePassword)
|
||||
|
||||
// Sanity-check the length.
|
||||
if pos != len(data) {
|
||||
|
|
|
@ -14,11 +14,15 @@ const (
|
|||
|
||||
// Supported auth forms.
|
||||
const (
|
||||
// mysqlNativePassword uses a salt and transmits a hash on the wire.
|
||||
mysqlNativePassword = "mysql_native_password"
|
||||
// MysqlNativePassword uses a salt and transmits a hash on the wire.
|
||||
MysqlNativePassword = "mysql_native_password"
|
||||
|
||||
// mysqlClearPassword transmits the password in the clear.
|
||||
mysqlClearPassword = "mysql_clear_password"
|
||||
// MysqlClearPassword transmits the password in the clear.
|
||||
MysqlClearPassword = "mysql_clear_password"
|
||||
|
||||
// MysqlDialog uses the dialog plugin on the client side.
|
||||
// It transmits data in the clear.
|
||||
MysqlDialog = "dialog"
|
||||
)
|
||||
|
||||
// Capability flags.
|
||||
|
|
|
@ -46,17 +46,13 @@ Our client will expect the server to always use mysql_native_password
|
|||
in its initial handshake. This is what a real server always does, even though
|
||||
it's not technically mandatory.
|
||||
|
||||
Our server can then use the client's auth methods right away:
|
||||
- mysql_native_password
|
||||
- mysql_clear_password
|
||||
|
||||
If our server's AuthServer UseClearText() returns true, and the
|
||||
client's auth method is not mysql_clear_password, we will
|
||||
The server's AuthServer plugin method AuthMethod() will then return
|
||||
what auth method the server wants to use. If it is
|
||||
mysql_native_password, and the client already returned the data, we
|
||||
use it. Otherwise we switch the auth to what the server wants (by
|
||||
sending an Authentication Method Switch Request packet) and
|
||||
re-negotiate.
|
||||
|
||||
If any of these methods doesn't work for the server, it will re-negotiate
|
||||
by sending an Authentication Method Switch Request Packet.
|
||||
The client will then handle that if it can.
|
||||
--
|
||||
Maximum Packet Size:
|
||||
|
||||
|
|
|
@ -20,7 +20,11 @@ import (
|
|||
func TestClearTextClientAuth(t *testing.T) {
|
||||
th := &testHandler{}
|
||||
|
||||
authServer := &AuthServerNone{ClearText: true}
|
||||
authServer := NewAuthServerStatic()
|
||||
authServer.Method = MysqlClearPassword
|
||||
authServer.Entries["user1"] = &AuthServerStaticEntry{
|
||||
Password: "password1",
|
||||
}
|
||||
|
||||
// Create the listener.
|
||||
l, err := NewListener("tcp", ":0", authServer, th)
|
||||
|
@ -75,7 +79,10 @@ func TestClearTextClientAuth(t *testing.T) {
|
|||
func TestSSLConnection(t *testing.T) {
|
||||
th := &testHandler{}
|
||||
|
||||
authServer := &AuthServerNone{ClearText: false}
|
||||
authServer := NewAuthServerStatic()
|
||||
authServer.Entries["user1"] = &AuthServerStaticEntry{
|
||||
Password: "password1",
|
||||
}
|
||||
|
||||
// Create the listener, so we can get its host.
|
||||
l, err := NewListener("tcp", ":0", authServer, th)
|
||||
|
@ -128,7 +135,7 @@ func TestSSLConnection(t *testing.T) {
|
|||
|
||||
// Make sure clear text auth works over SSL.
|
||||
t.Run("ClearText", func(t *testing.T) {
|
||||
authServer.ClearText = true
|
||||
authServer.Method = MysqlClearPassword
|
||||
testSSLConnectionClearText(t, params)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -19,12 +19,14 @@ import (
|
|||
var (
|
||||
ldapAuthConfigFile = flag.String("mysql_ldap_auth_config_file", "", "JSON File from which to read LDAP server config.")
|
||||
ldapAuthConfigString = flag.String("mysql_ldap_auth_config_string", "", "JSON representation of LDAP server config.")
|
||||
ldapAuthMethod = flag.String("mysql_ldap_auth_method", mysqlconn.MysqlClearPassword, "client-side authentication method to use. Supported values: mysql_clear_password, dialog.")
|
||||
)
|
||||
|
||||
// AuthServerLdap implements AuthServer with an LDAP backend
|
||||
type AuthServerLdap struct {
|
||||
Client
|
||||
ServerConfig
|
||||
Method string
|
||||
User string
|
||||
Password string
|
||||
GroupQuery string
|
||||
|
@ -42,7 +44,14 @@ func Init() {
|
|||
log.Infof("Both mysql_ldap_auth_config_file and mysql_ldap_auth_config_string are non-empty, can only use one.")
|
||||
return
|
||||
}
|
||||
ldapAuthServer := &AuthServerLdap{Client: &ClientImpl{}, ServerConfig: ServerConfig{}}
|
||||
if *ldapAuthMethod != mysqlconn.MysqlClearPassword && *ldapAuthMethod != mysqlconn.MysqlDialog {
|
||||
log.Fatalf("Invalid mysql_ldap_auth_method value: only support mysql_clear_password or dialog")
|
||||
}
|
||||
ldapAuthServer := &AuthServerLdap{
|
||||
Client: &ClientImpl{},
|
||||
ServerConfig: ServerConfig{},
|
||||
Method: *ldapAuthMethod,
|
||||
}
|
||||
|
||||
data := []byte(*ldapAuthConfigString)
|
||||
if *ldapAuthConfigFile != "" {
|
||||
|
@ -58,32 +67,37 @@ func Init() {
|
|||
mysqlconn.RegisterAuthServerImpl("ldap", ldapAuthServer)
|
||||
}
|
||||
|
||||
// UseClearText is always true for AuthServerLdap
|
||||
func (asl *AuthServerLdap) UseClearText() bool {
|
||||
return true
|
||||
// AuthMethod is part of the AuthServer interface.
|
||||
func (asl *AuthServerLdap) AuthMethod(user string) (string, error) {
|
||||
return asl.Method, nil
|
||||
}
|
||||
|
||||
// Salt is unimplemented for AuthServerLdap
|
||||
// Salt will be unused in AuthServerLdap.
|
||||
func (asl *AuthServerLdap) Salt() ([]byte, error) {
|
||||
panic("unimplemented")
|
||||
return mysqlconn.NewSalt()
|
||||
}
|
||||
|
||||
// ValidateHash is unimplemented for AuthServerLdap
|
||||
// ValidateHash is unimplemented for AuthServerLdap.
|
||||
func (asl *AuthServerLdap) ValidateHash(salt []byte, user string, authResponse []byte) (mysqlconn.Getter, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// ValidateClearText connects to the LDAP server over TLS
|
||||
// and attempts to bind as that user with the supplied password.
|
||||
// It returns the supplied username.
|
||||
func (asl *AuthServerLdap) ValidateClearText(username, password string) (mysqlconn.Getter, error) {
|
||||
err := asl.Client.Connect("tcp", &asl.ServerConfig)
|
||||
// Negotiate is part of the AuthServer interface.
|
||||
func (asl *AuthServerLdap) Negotiate(c *mysqlconn.Conn, user string) (mysqlconn.Getter, error) {
|
||||
// Finish the negotiation.
|
||||
password, err := mysqlconn.AuthServerNegotiateClearOrDialog(c, asl.Method)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return asl.validate(user, password)
|
||||
}
|
||||
|
||||
func (asl *AuthServerLdap) validate(username, password string) (mysqlconn.Getter, error) {
|
||||
if err := asl.Client.Connect("tcp", &asl.ServerConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer asl.Client.Close()
|
||||
err = asl.Client.Bind(fmt.Sprintf(asl.UserDnPattern, username), password)
|
||||
if err != nil {
|
||||
if err := asl.Client.Bind(fmt.Sprintf(asl.UserDnPattern, username), password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groups, err := asl.getGroups(username)
|
||||
|
|
|
@ -22,13 +22,18 @@ func (mlc *MockLdapClient) Search(searchRequest *ldap.SearchRequest) (*ldap.Sear
|
|||
}
|
||||
|
||||
func TestValidateClearText(t *testing.T) {
|
||||
asl := &AuthServerLdap{Client: &MockLdapClient{}, UserDnPattern: "%s", User: "testuser", Password: "testpass"}
|
||||
_, err := asl.ValidateClearText("testuser", "testpass")
|
||||
asl := &AuthServerLdap{
|
||||
Client: &MockLdapClient{},
|
||||
User: "testuser",
|
||||
Password: "testpass",
|
||||
UserDnPattern: "%s",
|
||||
}
|
||||
_, err := asl.validate("testuser", "testpass")
|
||||
if err != nil {
|
||||
t.Fatalf("AuthServerLdap failed to validate valid credentials. Got: %v", err)
|
||||
}
|
||||
|
||||
_, err = asl.ValidateClearText("invaliduser", "invalidpass")
|
||||
_, err = asl.validate("invaliduser", "invalidpass")
|
||||
if err == nil {
|
||||
t.Fatalf("AuthServerLdap validated invalid credentials.")
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ func benchmarkOldParallelReads(b *testing.B, params sqldb.ConnParams, parallelCo
|
|||
func BenchmarkParallelShortQueries(b *testing.B) {
|
||||
th := &testHandler{}
|
||||
|
||||
authServer := &AuthServerNone{ClearText: false}
|
||||
authServer := &AuthServerNone{}
|
||||
|
||||
l, err := NewListener("tcp", ":0", authServer, th)
|
||||
if err != nil {
|
||||
|
|
|
@ -168,72 +168,53 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32) {
|
|||
}
|
||||
}
|
||||
|
||||
// See what method the client used.
|
||||
renegotiateWithClearText := false
|
||||
switch authMethod {
|
||||
case mysqlNativePassword:
|
||||
// This is what the server started with. Let's use it if we can.
|
||||
if !l.authServer.UseClearText() {
|
||||
userData, err := l.authServer.ValidateHash(salt, user, authResponse)
|
||||
if err != nil {
|
||||
c.writeErrorPacketFromError(err)
|
||||
return
|
||||
}
|
||||
c.User = user
|
||||
c.UserData = userData
|
||||
// We're good.
|
||||
break
|
||||
}
|
||||
// See what auth method the AuthServer wants to use for that user.
|
||||
authServerMethod, err := l.authServer.AuthMethod(user)
|
||||
if err != nil {
|
||||
c.writeErrorPacketFromError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Our AuthServer cannot use mysql_native_password, it
|
||||
// needs the real password. Let's request that.
|
||||
renegotiateWithClearText = true
|
||||
case mysqlClearPassword:
|
||||
// Client sent us a clear text password. Let's use it if we can.
|
||||
if !l.AllowClearTextWithoutTLS && c.Capabilities&CapabilityClientSSL == 0 {
|
||||
c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Cannot use clear text authentication over non-SSL connections.")
|
||||
return
|
||||
}
|
||||
userData, err := l.authServer.ValidateClearText(user, string(authResponse))
|
||||
// Compare with what the client sent back.
|
||||
switch {
|
||||
case authServerMethod == MysqlNativePassword && authMethod == MysqlNativePassword:
|
||||
// Both server and client want to use MysqlNativePassword:
|
||||
// the negotiation can be completed right away, using the
|
||||
// ValidateHash() method.
|
||||
userData, err := l.authServer.ValidateHash(salt, user, authResponse)
|
||||
if err != nil {
|
||||
c.writeErrorPacketFromError(err)
|
||||
return
|
||||
}
|
||||
c.User = user
|
||||
c.UserData = userData
|
||||
break
|
||||
default:
|
||||
// Client decided to use something we don't understand.
|
||||
// Let's try again with clear text password.
|
||||
renegotiateWithClearText = true
|
||||
}
|
||||
|
||||
// If we need to re-negotiate with clear text, do it.
|
||||
if renegotiateWithClearText {
|
||||
// Check error conditions.
|
||||
case authServerMethod == MysqlNativePassword:
|
||||
// The server really wants to use MysqlNativePassword,
|
||||
// but the client returned a result for something else:
|
||||
// not sure this can happen, so not supporting this now.
|
||||
c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Client asked for auth %v, but server wants auth mysql_native_password", authMethod)
|
||||
return
|
||||
|
||||
default:
|
||||
// The server wants to use something else, re-negotiate.
|
||||
|
||||
// The negotiation happens in clear text. Let's check we can.
|
||||
if !l.AllowClearTextWithoutTLS && c.Capabilities&CapabilityClientSSL == 0 {
|
||||
c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Cannot use clear text authentication over non-SSL connections.")
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.writeAuthSwitchRequest(mysqlClearPassword, nil); err != nil {
|
||||
// Switch our auth method to what the server wants.
|
||||
// Note: for now don't support the extra data.
|
||||
if err := c.writeAuthSwitchRequest(authServerMethod, nil); err != nil {
|
||||
log.Errorf("Error write auth switch packet for client %v: %v", c.ConnectionID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// The client is supposed to just send the data in a single packet.
|
||||
// It is a zero-terminated string.
|
||||
data, err := c.readEphemeralPacket()
|
||||
if err != nil {
|
||||
log.Warningf("Error reading auth switch response packet from client %v: %v", c.ConnectionID, err)
|
||||
return
|
||||
}
|
||||
password, pos, ok := readNullString(data, 0)
|
||||
if !ok || pos != len(data) {
|
||||
c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Error parsing packet with password: %v", data)
|
||||
return
|
||||
}
|
||||
userData, err := l.authServer.ValidateClearText(user, password)
|
||||
// Then hand over the rest of the negotiation to the
|
||||
// auth server.
|
||||
userData, err := l.authServer.Negotiate(c, user)
|
||||
if err != nil {
|
||||
c.writeErrorPacketFromError(err)
|
||||
return
|
||||
|
@ -242,7 +223,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32) {
|
|||
c.UserData = userData
|
||||
}
|
||||
|
||||
// Send an OK packet.
|
||||
// Negotiation worked, send OK packet.
|
||||
if err := c.writeOKPacket(0, 0, c.StatusFlags, 0); err != nil {
|
||||
log.Errorf("Cannot write OK packet: %v", err)
|
||||
return
|
||||
|
@ -332,7 +313,7 @@ func (c *Conn) writeHandshakeV10(serverVersion string, authServer AuthServer, en
|
|||
1 + // length of auth plugin data
|
||||
10 + // reserved (0)
|
||||
13 + // auth-plugin-data
|
||||
lenNullString(mysqlNativePassword) // auth-plugin-name
|
||||
lenNullString(MysqlNativePassword) // auth-plugin-name
|
||||
|
||||
data := c.startEphemeralPacket(length)
|
||||
pos := 0
|
||||
|
@ -346,17 +327,8 @@ func (c *Conn) writeHandshakeV10(serverVersion string, authServer AuthServer, en
|
|||
// Add connectionID in.
|
||||
pos = writeUint32(data, pos, c.ConnectionID)
|
||||
|
||||
// Generate the salt if needed, put 8 bytes in.
|
||||
var salt []byte
|
||||
var err error
|
||||
if authServer.UseClearText() {
|
||||
// salt will end up being unused, but we can't send
|
||||
// just zero, as the client will still use it, and
|
||||
// that may leak crypto information.
|
||||
salt, err = newSalt()
|
||||
} else {
|
||||
salt, err = authServer.Salt()
|
||||
}
|
||||
// Generate the salt, put 8 bytes in.
|
||||
salt, err := authServer.Salt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -391,7 +363,7 @@ func (c *Conn) writeHandshakeV10(serverVersion string, authServer AuthServer, en
|
|||
pos++
|
||||
|
||||
// Copy authPluginName. We always start with mysql_native_password.
|
||||
pos = writeNullString(data, pos, mysqlNativePassword)
|
||||
pos = writeNullString(data, pos, MysqlNativePassword)
|
||||
|
||||
// Sanity check.
|
||||
if pos != len(data) {
|
||||
|
@ -504,7 +476,7 @@ func (l *Listener) parseClientHandshakePacket(c *Conn, firstTime bool, data []by
|
|||
}
|
||||
|
||||
// authMethod (with default)
|
||||
authMethod := mysqlNativePassword
|
||||
authMethod := MysqlNativePassword
|
||||
if clientFlags&CapabilityClientPluginAuth != 0 {
|
||||
authMethod, pos, ok = readNullString(data, pos)
|
||||
if !ok {
|
||||
|
|
|
@ -276,7 +276,7 @@ func TestClearTextServer(t *testing.T) {
|
|||
Password: "password1",
|
||||
UserData: "userData1",
|
||||
}
|
||||
authServer.ClearText = true
|
||||
authServer.Method = MysqlClearPassword
|
||||
l, err := NewListener("tcp", ":0", authServer, th)
|
||||
if err != nil {
|
||||
t.Fatalf("NewListener failed: %v", err)
|
||||
|
|
Загрузка…
Ссылка в новой задаче