From a74b766be5b3b3451e9bb8baf1680758bb90af2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Eeden?= Date: Mon, 29 Nov 2021 11:33:52 +0100 Subject: [PATCH] cherry pick #29738 to release-5.3 Signed-off-by: ti-srebot --- server/conn.go | 69 ++++++++++++- server/conn_test.go | 230 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 292 insertions(+), 7 deletions(-) diff --git a/server/conn.go b/server/conn.go index 49a42fe54bb94..dd4c6311b3d5c 100644 --- a/server/conn.go +++ b/server/conn.go @@ -211,6 +211,9 @@ func (cc *clientConn) String() string { // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest // https://bugs.mysql.com/bug.php?id=93044 func (cc *clientConn) authSwitchRequest(ctx context.Context, plugin string) ([]byte, error) { + failpoint.Inject("FakeAuthSwitch", func() { + failpoint.Return([]byte(plugin), nil) + }) enclen := 1 + len(plugin) + 1 + len(cc.salt) + 1 data := cc.alloc.AllocWithLen(4, enclen) data = append(data, mysql.AuthSwitchRequest) // switch request @@ -718,17 +721,15 @@ func (cc *clientConn) handleAuthPlugin(ctx context.Context, resp *handshakeRespo switch resp.AuthPlugin { case mysql.AuthCachingSha2Password: - resp.Auth, err = cc.authSha(ctx) - if err != nil { - return err - } case mysql.AuthNativePassword: case mysql.AuthSocket: default: logutil.Logger(ctx).Warn("Unknown Auth Plugin", zap.String("plugin", resp.AuthPlugin)) } } else { + // MySQL 5.1 and older clients don't support authentication plugins. logutil.Logger(ctx).Warn("Client without Auth Plugin support; Please upgrade client") +<<<<<<< HEAD if cc.ctx == nil { err := cc.openSession() if err != nil { @@ -742,20 +743,29 @@ func (cc *clientConn) handleAuthPlugin(ctx context.Context, resp *handshakeRespo if userplugin != mysql.AuthNativePassword && userplugin != "" { return errNotSupportedAuthMode } +======= + _, err := cc.checkAuthPlugin(ctx, resp) + if err != nil { + return err + } +>>>>>>> 6756bd2ea... server: Combined fix for authentication issues (#29738) resp.AuthPlugin = mysql.AuthNativePassword } return nil } +// authSha implements the caching_sha2_password specific part of the protocol. func (cc *clientConn) authSha(ctx context.Context) ([]byte, error) { const ( ShaCommand = 1 - RequestRsaPubKey = 2 + RequestRsaPubKey = 2 // Not supported yet, only TLS is supported as secure channel. FastAuthOk = 3 FastAuthFail = 4 ) + // Currently we always send a "FastAuthFail" as the cached part of the protocol isn't implemented yet. + // This triggers the client to send the full response. err := cc.writePacket([]byte{0, 0, 0, 0, ShaCommand, FastAuthFail}) if err != nil { logutil.Logger(ctx).Error("authSha packet write failed", zap.Error(err)) @@ -854,10 +864,28 @@ func (cc *clientConn) checkAuthPlugin(ctx context.Context, authPlugin *string) ( } } +<<<<<<< HEAD userplugin, err := cc.ctx.AuthPluginForUser(&auth.UserIdentity{Username: cc.user, Hostname: cc.peerHost}) +======= + authData := resp.Auth + hasPassword := "YES" + if len(authData) == 0 { + hasPassword = "NO" + } + host, _, err := cc.PeerHost(hasPassword) if err != nil { return nil, err } + userplugin, err := cc.ctx.AuthPluginForUser(&auth.UserIdentity{Username: cc.user, Hostname: host}) + failpoint.Inject("FakeUser", func(val failpoint.Value) { + userplugin = val.(string) + }) +>>>>>>> 6756bd2ea... server: Combined fix for authentication issues (#29738) + if err != nil { + // This happens if the account doesn't exist + logutil.Logger(ctx).Warn("Failed to get authentication method for user", + zap.String("user", cc.user), zap.String("host", host)) + } if userplugin == mysql.AuthSocket { *authPlugin = mysql.AuthSocket user, err := user.LookupId(fmt.Sprint(cc.socketCredUID)) @@ -867,9 +895,25 @@ func (cc *clientConn) checkAuthPlugin(ctx context.Context, authPlugin *string) ( return []byte(user.Username), nil } if len(userplugin) == 0 { +<<<<<<< HEAD logutil.Logger(ctx).Warn("No user plugin set, assuming MySQL Native Password", zap.String("user", cc.user), zap.String("host", cc.peerHost)) *authPlugin = mysql.AuthNativePassword +======= + // No user plugin set, assuming MySQL Native Password + // This happens if the account doesn't exist or if the account doesn't have + // a password set. + if resp.AuthPlugin != mysql.AuthNativePassword { + if resp.Capability&mysql.ClientPluginAuth > 0 { + resp.AuthPlugin = mysql.AuthNativePassword + authData, err := cc.authSwitchRequest(ctx, mysql.AuthNativePassword) + if err != nil { + return nil, err + } + return authData, nil + } + } +>>>>>>> 6756bd2ea... server: Combined fix for authentication issues (#29738) return nil, nil } @@ -878,6 +922,7 @@ func (cc *clientConn) checkAuthPlugin(ctx context.Context, authPlugin *string) ( // or if the authentication method send by the server doesn't match the authentication // method send by the client (*authPlugin) then we need to switch the authentication // method to match the one configured for that specific user. +<<<<<<< HEAD if (cc.authPlugin != userplugin) || (cc.authPlugin != *authPlugin) { authData, err := cc.authSwitchRequest(ctx, userplugin) if err != nil { @@ -885,6 +930,20 @@ func (cc *clientConn) checkAuthPlugin(ctx context.Context, authPlugin *string) ( } *authPlugin = userplugin return authData, nil +======= + if (cc.authPlugin != userplugin) || (cc.authPlugin != resp.AuthPlugin) { + if resp.Capability&mysql.ClientPluginAuth > 0 { + authData, err := cc.authSwitchRequest(ctx, userplugin) + if err != nil { + return nil, err + } + resp.AuthPlugin = userplugin + return authData, nil + } else if userplugin != mysql.AuthNativePassword { + // MySQL 5.1 and older don't support authentication plugins yet + return nil, errNotSupportedAuthMode + } +>>>>>>> 6756bd2ea... server: Combined fix for authentication issues (#29738) } return nil, nil diff --git a/server/conn_test.go b/server/conn_test.go index dc50900e41624..82e068b16dba2 100644 --- a/server/conn_test.go +++ b/server/conn_test.go @@ -905,10 +905,18 @@ func TestHandleAuthPlugin(t *testing.T) { drv := NewTiDBDriver(store) srv, err := NewServer(cfg, drv) require.NoError(t, err) + ctx := context.Background() + // 5.7 or newer client trying to authenticate with mysql_native_password cc := &clientConn{ connectionID: 1, alloc: arena.NewAllocator(1024), +<<<<<<< HEAD +======= + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", +>>>>>>> 6756bd2ea... server: Combined fix for authentication issues (#29738) pkt: &packetIO{ bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), }, @@ -916,14 +924,232 @@ func TestHandleAuthPlugin(t *testing.T) { server: srv, user: "root", } - ctx := context.Background() resp := handshakeResponse41{ Capability: mysql.ClientProtocol41 | mysql.ClientPluginAuth, + AuthPlugin: mysql.AuthNativePassword, + } + err = cc.handleAuthPlugin(ctx, &resp) + require.NoError(t, err) + + // 8.0 or newer client trying to authenticate with caching_sha2_password + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/FakeAuthSwitch", "return(1)")) + cc = &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + } + resp = handshakeResponse41{ + Capability: mysql.ClientProtocol41 | mysql.ClientPluginAuth, + AuthPlugin: mysql.AuthCachingSha2Password, + } + err = cc.handleAuthPlugin(ctx, &resp) + require.NoError(t, err) + require.Equal(t, resp.Auth, []byte(mysql.AuthNativePassword)) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/FakeAuthSwitch")) + + // MySQL 5.1 or older client, without authplugin support + cc = &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + } + resp = handshakeResponse41{ + Capability: mysql.ClientProtocol41, + } + err = cc.handleAuthPlugin(ctx, &resp) + require.NoError(t, err) + + // === Target account has mysql_native_password === + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/FakeUser", "return(\"mysql_native_password\")")) + + // 5.7 or newer client trying to authenticate with mysql_native_password + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/FakeAuthSwitch", "return(1)")) + cc = &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + } + resp = handshakeResponse41{ + Capability: mysql.ClientProtocol41 | mysql.ClientPluginAuth, + AuthPlugin: mysql.AuthNativePassword, + } + err = cc.handleAuthPlugin(ctx, &resp) + require.NoError(t, err) + require.Equal(t, resp.Auth, []byte(mysql.AuthNativePassword)) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/FakeAuthSwitch")) + + // 8.0 or newer client trying to authenticate with caching_sha2_password + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/FakeAuthSwitch", "return(1)")) + cc = &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + } + resp = handshakeResponse41{ + Capability: mysql.ClientProtocol41 | mysql.ClientPluginAuth, + AuthPlugin: mysql.AuthCachingSha2Password, + } + err = cc.handleAuthPlugin(ctx, &resp) + require.NoError(t, err) + require.Equal(t, resp.Auth, []byte(mysql.AuthNativePassword)) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/FakeAuthSwitch")) + + // MySQL 5.1 or older client, without authplugin support + cc = &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + } + resp = handshakeResponse41{ + Capability: mysql.ClientProtocol41, + } + err = cc.handleAuthPlugin(ctx, &resp) + require.NoError(t, err) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/FakeUser")) + + // === Target account has caching_sha2_password === + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/FakeUser", "return(\"caching_sha2_password\")")) + + // 5.7 or newer client trying to authenticate with mysql_native_password + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/FakeAuthSwitch", "return(1)")) + cc = &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + } + resp = handshakeResponse41{ + Capability: mysql.ClientProtocol41 | mysql.ClientPluginAuth, + AuthPlugin: mysql.AuthNativePassword, } err = cc.handleAuthPlugin(ctx, &resp) require.NoError(t, err) + require.Equal(t, resp.Auth, []byte(mysql.AuthCachingSha2Password)) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/FakeAuthSwitch")) - resp.Capability = mysql.ClientProtocol41 + // 8.0 or newer client trying to authenticate with caching_sha2_password + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/FakeAuthSwitch", "return(1)")) + cc = &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + } + resp = handshakeResponse41{ + Capability: mysql.ClientProtocol41 | mysql.ClientPluginAuth, + AuthPlugin: mysql.AuthCachingSha2Password, + } err = cc.handleAuthPlugin(ctx, &resp) require.NoError(t, err) + require.Equal(t, resp.Auth, []byte(mysql.AuthCachingSha2Password)) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/FakeAuthSwitch")) + + // MySQL 5.1 or older client, without authplugin support + cc = &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + collation: mysql.DefaultCollationID, + peerHost: "localhost", + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + } + resp = handshakeResponse41{ + Capability: mysql.ClientProtocol41, + } + err = cc.handleAuthPlugin(ctx, &resp) + require.Error(t, err) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/FakeUser")) +} +<<<<<<< HEAD +======= + +func TestAuthPlugin2(t *testing.T) { + + t.Parallel() + + store, clean := testkit.CreateMockStore(t) + defer clean() + + cfg := newTestConfig() + cfg.Socket = "" + cfg.Port = 0 + cfg.Status.StatusPort = 0 + + drv := NewTiDBDriver(store) + srv, err := NewServer(cfg, drv) + require.NoError(t, err) + + cc := &clientConn{ + connectionID: 1, + alloc: arena.NewAllocator(1024), + chunkAlloc: chunk.NewAllocator(), + pkt: &packetIO{ + bufWriter: bufio.NewWriter(bytes.NewBuffer(nil)), + }, + server: srv, + user: "root", + } + ctx := context.Background() + se, _ := session.CreateSession4Test(store) + tc := &TiDBContext{ + Session: se, + stmts: make(map[int]*TiDBStatement), + } + cc.ctx = tc + + resp := handshakeResponse41{ + Capability: mysql.ClientProtocol41 | mysql.ClientPluginAuth, + } + + cc.isUnixSocket = true + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/FakeAuthSwitch", "return(1)")) + respAuthSwitch, err := cc.checkAuthPlugin(ctx, &resp) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/FakeAuthSwitch")) + require.Equal(t, respAuthSwitch, []byte(mysql.AuthNativePassword)) + require.NoError(t, err) + } +>>>>>>> 6756bd2ea... server: Combined fix for authentication issues (#29738)