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

Add support for MySQL 8.0 and support for TLS/SSL for both Server and Client #312

Merged
merged 28 commits into from
Dec 21, 2018

Conversation

michael2008
Copy link
Contributor

This PR:

  • Added support for MySQL 8.0 for the Client: starting from MySQL 8.0.4, MySQL uses 'caching_sha2_password' as default authentication method, which caused the connection to fail. This PR fixed the problem by supporting three mainstream auth methods 'mysql_native_password', 'caching_sha2_password', and 'sha256_password', which covers a wide range of MySQL versions from MySQL 5.5 to MySQL 8.0.
  • Added support of new auth methods for the Server: the server now accepts 'mysql_native_password', 'caching_sha2_password', and 'sha256_password' as auth methods. Other old and deprecated auth methods are not supported.
  • Supports TLS/SSL for the Client and the Server: the new design maintains compatibility with the previous releases. Customizations are optional.

Since the upgrade of auth methods affects the Client and Server design, I made lots of changes to the code while trying to reuse the old code. Some minor existing bugs are fixed too. For instance, the buffer reader for the net.Conn is now removed because it causes the SSL handshake to fail.

Changing and refactoring can be a bad thing though as they can introduce new bugs. However, I tried my best to make the old tests pass and added new feature tests for different MySQL versions using docker compose to increase testing coverage. For now all test cases passed.

@siddontang
Copy link
Collaborator

Great work, thank you @michael2008

@siddontang
Copy link
Collaborator

Hi @michael2008

seem you have supported two features - 8.0 and TLS here, can we split into two PRs?

@michael2008
Copy link
Contributor Author

@siddontang About two PRs, a bit difficult. The reason to supporting TLS/SSL is that it is required for sha256_password and caching_sha2_password authentication. Basically MySQL supports two ways to send the client password to the server: one is to send as plain text via an established TLS channel; the other one is to send as encrypted text using a public key retrieved from the server. So the support of TLS connection may be taken as the side-effect of supporting new authentication methods. However, for the users, it seems that you can use TLS at the very beginning of connecting to the server.

@IANTHEREAL
Copy link
Collaborator

Great work @michael2008, we would review and test it

@michael2008
Copy link
Contributor Author

@GregoryIan I am do more tests with clients other than go-sql-driver. There are still some incompatibilities. I am working on them. I will give you feedback when it's done.

@michael2008
Copy link
Contributor Author

@GregoryIan Hi, more tests are done. Now the Server is more compatible with MySQL server regarding the authentication part. Tested using the native MySQL Client (both version 5.7.23 and 8.0.12) against the Server. Tests all passed and showed promising results.

I think my works are almost done here. If you guys should find any problems, please let me know and I will fix them.

}

// encode length of the auth plugin data
var authRespLEIBuf [9]byte
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does LEI mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's Length-Encoded-Integer(LEI), I will add comment here

client/auth.go Outdated
func (c *Conn) writeAuthHandshake() error {
if c.authPluginName != "mysql_native_password" && c.authPluginName != "caching_sha2_password" && c.authPluginName != "sha256_password" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you extract to a function like authPluginAllowed(name)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

@@ -5,11 +5,16 @@ import (
"crypto/tls"
"encoding/binary"

"fmt"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the above blank line to group the standard libraries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

client/auth.go Outdated
@@ -50,9 +61,8 @@ func (c *Conn) readInitialHandshake() error {

c.status = binary.LittleEndian.Uint16(data[pos : pos+2])
pos += 2

//capability flags (upper 2 bytes)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// capability

leave a space here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added space before comments

return nil, true, nil
}
if c.tlsConfig != nil || c.proto == "unix" {
// write cleartext auth packet
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clear text?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reference for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see here: https://dev.mysql.com/doc/refman/8.0/en/sha256-pluggable-authentication.html


If the connection is secure, an RSA key pair is unnecessary and is not used. This applies to encrypted connections that use TLS. The password is sent as cleartext but cannot be snooped because the connection is secure.

If the connection is not secure, and an RSA key pair is available, the connection remains unencrypted. This applies to unencrypted connections without TLS. RSA is used only for password exchange between client and server, to prevent password snooping. When the server receives the encrypted password, it decrypts it. A scramble is used in the encryption to prevent repeat attacks.

If a secure connection is not used and RSA encryption is not available, the connection attempt fails because the password cannot be sent without being exposed as cleartext.```

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added ref as comment in the code

}

return nil
}

// generate auth response data according to auth plugin
func (c *Conn) genAuthResponse(authData []byte) ([]byte, bool, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which does bool mean here? can you add a comment here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. Sure, the bool determines whether to add a \NUL to the end of data. That is quite tricky actually because mysql dev doc is very unclear about the message endings. The rules here are interpreted from MySQL server response packets. Besides, the MySQL implementation does not quite follow their docs in some response messages.

see: https://dev.mysql.com/doc/internals/en/string.html


// the certificates are in go-mysql/docker/resources

var caPem = []byte(`-----BEGIN CERTIFICATE-----
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find that many places use the same keys, can we use move them to one place to reduce the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move to the siddontang/mysql folder? but I think it's a bit confusing since the keys are for testing purpose only and the mysql folder is an actual working code base and if we do move the keys there, we have to expose them outside the package and that for users will be an unnecessary leak.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to create a test_util mod which only the tests can use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok that will do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

already fixed

client/resp.go Outdated
}

// handle caching_sha2_password
if c.authPluginName == "caching_sha2_password" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to use const for all plugin names to avoid a typo. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all replace in Client and Server


s.db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s", *testUser, *testPassword, *testAddr, *testDB))
s.db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=%s", *testUser, *testPassword, *testAddr, *testDB, s.tlsPara))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must we use tls for the test now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, it's controlled by the test configs. some tests set this value to 'false' to use non-tls connections and for TLS tests the value is set to 'skip-verify'

@siddontang
Copy link
Collaborator

PTAL @GregoryIan @holys

This PR is awesome, I can't wait to merge it, but I know we need detailed reviews and hope we can merge it next week. :-)

@michael2008 can you give us your email? we can keep in touch later.

@michael2008
Copy link
Contributor Author

@siddontang Sure, I will send you an email. Love to collaborate with you guys. BTW, PingCap is really great and you guys are awesome. You've contributed a lot to the open source community.

@IANTHEREAL
Copy link
Collaborator

sorry to have waited so long, we would focus on it next week @michael2008 @siddontang

@@ -109,6 +112,18 @@ func (c *Conn) Ping() error {
return nil
}

// use default SSL
// pass to options when connect
func (c *Conn) UseSSL(insecureSkipVerify bool) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about insecureVerify? while I call UseSSL((true), I think it need to verify

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func UseSSL() means we use secure link. For golang TLS, the default 'insecureSkipVerify=false' requires certificates verification for security reasons. If one needs to use self-signed certs, the 'insecureSkipVerify' must be explicitly set to true. So I think it better be the same as golang crypto lib.

See https://golang.org/pkg/crypto/tls/#Config

@IANTHEREAL
Copy link
Collaborator

IANTHEREAL commented Oct 27, 2018

seem some wrong with table map event in 8.0.12 MySQL. can u solve it ? @michael2008
table structure
CREATE TABLE bank2_accounts ( id int(11) NOT NULL, balance int(11) NOT NULL, name varchar(32) DEFAULT NULL, remark varchar(128) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY byName (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
error logs
Header &replication.EventHeader{Timestamp:0x5bd3c052, EventType:0x13, ServerID:0x3, EventSize:0x48, LogPos:0x87d, Flags:0x0}, Data ">\x00\x00\x00\x00\x00\x01\x00\x04test\x00\x0ebank2_accounts\x00\x04\x03\x03\x0f\x0f\x04\x80\x00\x00\x02\f\x01\x01\x00\x02\x03\xfc\xff\x00", Err: EOF github.com/siddontang/go-mysql/replication/binlogsyncer.go:704:

@siddontang
Copy link
Collaborator

hi @GregoryIan

can we fix it by ourselves?

@michael2008
Copy link
Contributor Author

seem some wrong with table map event in 8.0.12 MySQL. can u solve it ? @michael2008
table structure
CREATE TABLE bank2_accounts ( id int(11) NOT NULL, balance int(11) NOT NULL, name varchar(32) DEFAULT NULL, remark varchar(128) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY byName (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
error logs
Header &replication.EventHeader{Timestamp:0x5bd3c052, EventType:0x13, ServerID:0x3, EventSize:0x48, LogPos:0x87d, Flags:0x0}, Data ">\x00\x00\x00\x00\x00\x01\x00\x04test\x00\x0ebank2_accounts\x00\x04\x03\x03\x0f\x0f\x04\x80\x00\x00\x02\f\x01\x01\x00\x02\x03\xfc\xff\x00", Err: EOF github.com/siddontang/go-mysql/replication/binlogsyncer.go:704:

I think this is a different issue from MySQL Authentication. More of a binlog format changing in MySQL 8. Should we open a new issue for this?

@IANTHEREAL
Copy link
Collaborator

ok, we can merge it now, we would fix it later @michael2008 @siddontang

@siddontang
Copy link
Collaborator

can you solve the conflict? @michael2008

Copy link
Contributor Author

@michael2008 michael2008 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolve conflict and merge error

@michael2008
Copy link
Contributor Author

can you solve the conflict? @michael2008

@siddontang conflict resolved

@siddontang siddontang merged commit 8b47113 into go-mysql-org:master Dec 21, 2018
@Ryan-Git Ryan-Git mentioned this pull request Dec 30, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants