From ad744fc76f345d3878a5ab2652400fe35eae682c Mon Sep 17 00:00:00 2001 From: lance6716 Date: Fri, 11 Nov 2022 20:48:58 +0800 Subject: [PATCH 1/7] A way to register preferred TLS config Signed-off-by: lance6716 --- dsn.go | 8 +++++-- dsn_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++ packets.go | 2 +- utils.go | 32 ++++++++++++++++++++++++---- 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/dsn.go b/dsn.go index a306d66a3..3605012f7 100644 --- a/dsn.go +++ b/dsn.go @@ -47,6 +47,7 @@ type Config struct { pubKey *rsa.PublicKey // Server public key TLSConfig string // TLS configuration name tls *tls.Config // TLS configuration + tlsIsPreferred bool // TLS is preferred, not a must Timeout time.Duration // Dial timeout ReadTimeout time.Duration // I/O read timeout WriteTimeout time.Duration // I/O write timeout @@ -124,10 +125,13 @@ func (cfg *Config) normalize() error { // don't set anything case "true": cfg.tls = &tls.Config{} - case "skip-verify", "preferred": + case "skip-verify": cfg.tls = &tls.Config{InsecureSkipVerify: true} + case "preferred": + cfg.tls = &tls.Config{InsecureSkipVerify: true} + cfg.tlsIsPreferred = true default: - cfg.tls = getTLSConfigClone(cfg.TLSConfig) + cfg.tls, cfg.tlsIsPreferred = getTLSConfigCloneAndPreferred(cfg.TLSConfig) if cfg.tls == nil { return errors.New("invalid value / unknown config name: " + cfg.TLSConfig) } diff --git a/dsn_test.go b/dsn_test.go index fc6eea9c8..fd2ca84df 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -221,6 +221,66 @@ func TestDSNWithCustomTLS(t *testing.T) { } } +func TestRegisterPreferredTLSConfig(t *testing.T) { + tlsMust := "tls-must" + tlsPreferred := "tls-preferred" + tlsCfg := tls.Config{ServerName: tlsMust} + tlsCfg2 := tls.Config{ServerName: tlsPreferred} + + err := RegisterTLSConfig(tlsMust, &tlsCfg) + if err != nil { + t.Error(err.Error()) + } + defer DeregisterTLSConfig(tlsMust) + err = RegisterPreferredTLSConfig(tlsPreferred, &tlsCfg2) + if err != nil { + t.Error(err.Error()) + } + defer DeregisterTLSConfig(tlsPreferred) + + checkCfgAndPreferred := func(key, expectedName string, expectedPreferred bool) { + cfg, preferred := getTLSConfigCloneAndPreferred(key) + if cfg.ServerName != expectedName { + t.Errorf("did not get the correct TLS ServerName (%s): %s.", expectedName, cfg.ServerName) + } + if expectedPreferred && !preferred { + t.Errorf("this should not be a preferred TLS config: %s", key) + } + if !expectedPreferred && preferred { + t.Errorf("this should be a preferred TLS config: %s", key) + } + } + + checkCfgAndPreferred(tlsMust, tlsMust, false) + checkCfgAndPreferred(tlsPreferred, tlsPreferred, true) + + // test RegisterTLSConfig overwrites existing config and preferred + tlsAnother := "tls-another" + tlsCfg3 := tls.Config{ServerName: tlsAnother} + // register the name tlsPreferred in non-preferred mode! + err = RegisterTLSConfig(tlsPreferred, &tlsCfg3) + if err != nil { + t.Error(err.Error()) + } + checkCfgAndPreferred(tlsPreferred, tlsAnother, false) + + // overwrite it back to preferred + err = RegisterPreferredTLSConfig(tlsPreferred, &tlsCfg3) + if err != nil { + t.Error(err.Error()) + } + checkCfgAndPreferred(tlsPreferred, tlsAnother, true) + + DeregisterTLSConfig(tlsPreferred) + cfg, preferred := getTLSConfigCloneAndPreferred(tlsPreferred) + if cfg != nil { + t.Error("found TLS config after deregister") + } + if preferred { + t.Error("preferred should be false after deregister") + } +} + func TestDSNTLSConfig(t *testing.T) { expectedServerName := "example.com" dsn := "tcp(example.com:1234)/?tls=true" diff --git a/packets.go b/packets.go index 003584c25..be129a6ed 100644 --- a/packets.go +++ b/packets.go @@ -223,7 +223,7 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro return nil, "", ErrOldProtocol } if mc.flags&clientSSL == 0 && mc.cfg.tls != nil { - if mc.cfg.TLSConfig == "preferred" { + if mc.cfg.tlsIsPreferred { mc.cfg.tls = nil } else { return nil, "", ErrNoTLS diff --git a/utils.go b/utils.go index 15dbd8d16..d91025f1d 100644 --- a/utils.go +++ b/utils.go @@ -25,8 +25,9 @@ import ( // Registry for custom tls.Configs var ( - tlsConfigLock sync.RWMutex - tlsConfigRegistry map[string]*tls.Config + tlsConfigLock sync.RWMutex + tlsConfigRegistry map[string]*tls.Config + tlsConfigPreferred map[string]struct{} ) // RegisterTLSConfig registers a custom tls.Config to be used with sql.Open. @@ -55,6 +56,17 @@ var ( // }) // db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom") func RegisterTLSConfig(key string, config *tls.Config) error { + return registerTLSConfig(key, config, false) +} + +// RegisterPreferredTLSConfig is like a RegisterTLSConfig, but when the MySQL +// server does not support TLS the driver will try to connect without TLS. +// It can be used as a customized "preferred" TLS configuration in the DSN. +func RegisterPreferredTLSConfig(key string, config *tls.Config) error { + return registerTLSConfig(key, config, true) +} + +func registerTLSConfig(key string, config *tls.Config, preferred bool) error { if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" { return fmt.Errorf("key '%s' is reserved", key) } @@ -63,8 +75,16 @@ func RegisterTLSConfig(key string, config *tls.Config) error { if tlsConfigRegistry == nil { tlsConfigRegistry = make(map[string]*tls.Config) } - tlsConfigRegistry[key] = config + + if preferred { + if tlsConfigPreferred == nil { + tlsConfigPreferred = make(map[string]struct{}) + } + tlsConfigPreferred[key] = struct{}{} + } else { + delete(tlsConfigPreferred, key) + } tlsConfigLock.Unlock() return nil } @@ -75,14 +95,18 @@ func DeregisterTLSConfig(key string) { if tlsConfigRegistry != nil { delete(tlsConfigRegistry, key) } + if tlsConfigPreferred != nil { + delete(tlsConfigPreferred, key) + } tlsConfigLock.Unlock() } -func getTLSConfigClone(key string) (config *tls.Config) { +func getTLSConfigCloneAndPreferred(key string) (config *tls.Config, preferred bool) { tlsConfigLock.RLock() if v, ok := tlsConfigRegistry[key]; ok { config = v.Clone() } + _, preferred = tlsConfigPreferred[key] tlsConfigLock.RUnlock() return } From 636b5b085819a62c7f204692fe96c9c7f6a645ae Mon Sep 17 00:00:00 2001 From: lance6716 Date: Fri, 11 Nov 2022 21:05:46 +0800 Subject: [PATCH 2/7] add myself to AUTHORS Signed-off-by: lance6716 --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 50b9593f0..051327519 100644 --- a/AUTHORS +++ b/AUTHORS @@ -61,6 +61,7 @@ Kamil Dziedzic Kei Kamikawa Kevin Malachowski Kieron Woodhouse +Lance Tian Lennart Rudolph Leonardo YongUk Kim Linh Tran Tuan From 2a5ac9bf83fdb9c79a4ee2d88396fa167d6c985b Mon Sep 17 00:00:00 2001 From: lance6716 Date: Sun, 13 Nov 2022 12:00:07 +0800 Subject: [PATCH 3/7] Revert "A way to register preferred TLS config" This reverts commit ad744fc76f345d3878a5ab2652400fe35eae682c. --- dsn.go | 8 ++----- dsn_test.go | 60 ----------------------------------------------------- packets.go | 2 +- utils.go | 32 ++++------------------------ 4 files changed, 7 insertions(+), 95 deletions(-) diff --git a/dsn.go b/dsn.go index 3605012f7..a306d66a3 100644 --- a/dsn.go +++ b/dsn.go @@ -47,7 +47,6 @@ type Config struct { pubKey *rsa.PublicKey // Server public key TLSConfig string // TLS configuration name tls *tls.Config // TLS configuration - tlsIsPreferred bool // TLS is preferred, not a must Timeout time.Duration // Dial timeout ReadTimeout time.Duration // I/O read timeout WriteTimeout time.Duration // I/O write timeout @@ -125,13 +124,10 @@ func (cfg *Config) normalize() error { // don't set anything case "true": cfg.tls = &tls.Config{} - case "skip-verify": + case "skip-verify", "preferred": cfg.tls = &tls.Config{InsecureSkipVerify: true} - case "preferred": - cfg.tls = &tls.Config{InsecureSkipVerify: true} - cfg.tlsIsPreferred = true default: - cfg.tls, cfg.tlsIsPreferred = getTLSConfigCloneAndPreferred(cfg.TLSConfig) + cfg.tls = getTLSConfigClone(cfg.TLSConfig) if cfg.tls == nil { return errors.New("invalid value / unknown config name: " + cfg.TLSConfig) } diff --git a/dsn_test.go b/dsn_test.go index fd2ca84df..fc6eea9c8 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -221,66 +221,6 @@ func TestDSNWithCustomTLS(t *testing.T) { } } -func TestRegisterPreferredTLSConfig(t *testing.T) { - tlsMust := "tls-must" - tlsPreferred := "tls-preferred" - tlsCfg := tls.Config{ServerName: tlsMust} - tlsCfg2 := tls.Config{ServerName: tlsPreferred} - - err := RegisterTLSConfig(tlsMust, &tlsCfg) - if err != nil { - t.Error(err.Error()) - } - defer DeregisterTLSConfig(tlsMust) - err = RegisterPreferredTLSConfig(tlsPreferred, &tlsCfg2) - if err != nil { - t.Error(err.Error()) - } - defer DeregisterTLSConfig(tlsPreferred) - - checkCfgAndPreferred := func(key, expectedName string, expectedPreferred bool) { - cfg, preferred := getTLSConfigCloneAndPreferred(key) - if cfg.ServerName != expectedName { - t.Errorf("did not get the correct TLS ServerName (%s): %s.", expectedName, cfg.ServerName) - } - if expectedPreferred && !preferred { - t.Errorf("this should not be a preferred TLS config: %s", key) - } - if !expectedPreferred && preferred { - t.Errorf("this should be a preferred TLS config: %s", key) - } - } - - checkCfgAndPreferred(tlsMust, tlsMust, false) - checkCfgAndPreferred(tlsPreferred, tlsPreferred, true) - - // test RegisterTLSConfig overwrites existing config and preferred - tlsAnother := "tls-another" - tlsCfg3 := tls.Config{ServerName: tlsAnother} - // register the name tlsPreferred in non-preferred mode! - err = RegisterTLSConfig(tlsPreferred, &tlsCfg3) - if err != nil { - t.Error(err.Error()) - } - checkCfgAndPreferred(tlsPreferred, tlsAnother, false) - - // overwrite it back to preferred - err = RegisterPreferredTLSConfig(tlsPreferred, &tlsCfg3) - if err != nil { - t.Error(err.Error()) - } - checkCfgAndPreferred(tlsPreferred, tlsAnother, true) - - DeregisterTLSConfig(tlsPreferred) - cfg, preferred := getTLSConfigCloneAndPreferred(tlsPreferred) - if cfg != nil { - t.Error("found TLS config after deregister") - } - if preferred { - t.Error("preferred should be false after deregister") - } -} - func TestDSNTLSConfig(t *testing.T) { expectedServerName := "example.com" dsn := "tcp(example.com:1234)/?tls=true" diff --git a/packets.go b/packets.go index be129a6ed..003584c25 100644 --- a/packets.go +++ b/packets.go @@ -223,7 +223,7 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro return nil, "", ErrOldProtocol } if mc.flags&clientSSL == 0 && mc.cfg.tls != nil { - if mc.cfg.tlsIsPreferred { + if mc.cfg.TLSConfig == "preferred" { mc.cfg.tls = nil } else { return nil, "", ErrNoTLS diff --git a/utils.go b/utils.go index d91025f1d..15dbd8d16 100644 --- a/utils.go +++ b/utils.go @@ -25,9 +25,8 @@ import ( // Registry for custom tls.Configs var ( - tlsConfigLock sync.RWMutex - tlsConfigRegistry map[string]*tls.Config - tlsConfigPreferred map[string]struct{} + tlsConfigLock sync.RWMutex + tlsConfigRegistry map[string]*tls.Config ) // RegisterTLSConfig registers a custom tls.Config to be used with sql.Open. @@ -56,17 +55,6 @@ var ( // }) // db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom") func RegisterTLSConfig(key string, config *tls.Config) error { - return registerTLSConfig(key, config, false) -} - -// RegisterPreferredTLSConfig is like a RegisterTLSConfig, but when the MySQL -// server does not support TLS the driver will try to connect without TLS. -// It can be used as a customized "preferred" TLS configuration in the DSN. -func RegisterPreferredTLSConfig(key string, config *tls.Config) error { - return registerTLSConfig(key, config, true) -} - -func registerTLSConfig(key string, config *tls.Config, preferred bool) error { if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" { return fmt.Errorf("key '%s' is reserved", key) } @@ -75,16 +63,8 @@ func registerTLSConfig(key string, config *tls.Config, preferred bool) error { if tlsConfigRegistry == nil { tlsConfigRegistry = make(map[string]*tls.Config) } - tlsConfigRegistry[key] = config - if preferred { - if tlsConfigPreferred == nil { - tlsConfigPreferred = make(map[string]struct{}) - } - tlsConfigPreferred[key] = struct{}{} - } else { - delete(tlsConfigPreferred, key) - } + tlsConfigRegistry[key] = config tlsConfigLock.Unlock() return nil } @@ -95,18 +75,14 @@ func DeregisterTLSConfig(key string) { if tlsConfigRegistry != nil { delete(tlsConfigRegistry, key) } - if tlsConfigPreferred != nil { - delete(tlsConfigPreferred, key) - } tlsConfigLock.Unlock() } -func getTLSConfigCloneAndPreferred(key string) (config *tls.Config, preferred bool) { +func getTLSConfigClone(key string) (config *tls.Config) { tlsConfigLock.RLock() if v, ok := tlsConfigRegistry[key]; ok { config = v.Clone() } - _, preferred = tlsConfigPreferred[key] tlsConfigLock.RUnlock() return } From 372d17b82f61c26d2073762e2c9de67c97a73a43 Mon Sep 17 00:00:00 2001 From: lance6716 Date: Sun, 13 Nov 2022 12:14:46 +0800 Subject: [PATCH 4/7] change to another impl Signed-off-by: lance6716 --- auth.go | 4 ++-- auth_test.go | 10 +++++----- dsn.go | 50 ++++++++++++++++++++++++++++++++++---------------- dsn_test.go | 46 +++++++++++++++++++++++----------------------- packets.go | 12 ++++++------ 5 files changed, 70 insertions(+), 52 deletions(-) diff --git a/auth.go b/auth.go index 26f8723f5..1ff203e57 100644 --- a/auth.go +++ b/auth.go @@ -275,7 +275,7 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) { } // unlike caching_sha2_password, sha256_password does not accept // cleartext password on unix transport. - if mc.cfg.tls != nil { + if mc.cfg.TLS != nil { // write cleartext auth packet return append([]byte(mc.cfg.Passwd), 0), nil } @@ -351,7 +351,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error { } case cachingSha2PasswordPerformFullAuthentication: - if mc.cfg.tls != nil || mc.cfg.Net == "unix" { + if mc.cfg.TLS != nil || mc.cfg.Net == "unix" { // write cleartext auth packet err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0)) if err != nil { diff --git a/auth_test.go b/auth_test.go index 3bce7fe22..3ce0ea6e0 100644 --- a/auth_test.go +++ b/auth_test.go @@ -291,7 +291,7 @@ func TestAuthFastCachingSHA256PasswordFullSecure(t *testing.T) { // Hack to make the caching_sha2_password plugin believe that the connection // is secure - mc.cfg.tls = &tls.Config{InsecureSkipVerify: true} + mc.cfg.TLS = &tls.Config{InsecureSkipVerify: true} // check written auth response authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 @@ -663,7 +663,7 @@ func TestAuthFastSHA256PasswordSecure(t *testing.T) { // hack to make the caching_sha2_password plugin believe that the connection // is secure - mc.cfg.tls = &tls.Config{InsecureSkipVerify: true} + mc.cfg.TLS = &tls.Config{InsecureSkipVerify: true} authData := []byte{6, 81, 96, 114, 14, 42, 50, 30, 76, 47, 1, 95, 126, 81, 62, 94, 83, 80, 52, 85} @@ -676,7 +676,7 @@ func TestAuthFastSHA256PasswordSecure(t *testing.T) { } // unset TLS config to prevent the actual establishment of a TLS wrapper - mc.cfg.tls = nil + mc.cfg.TLS = nil err = mc.writeHandshakeResponsePacket(authResp, plugin) if err != nil { @@ -866,7 +866,7 @@ func TestAuthSwitchCachingSHA256PasswordFullSecure(t *testing.T) { // Hack to make the caching_sha2_password plugin believe that the connection // is secure - mc.cfg.tls = &tls.Config{InsecureSkipVerify: true} + mc.cfg.TLS = &tls.Config{InsecureSkipVerify: true} // auth switch request conn.data = []byte{44, 0, 0, 2, 254, 99, 97, 99, 104, 105, 110, 103, 95, @@ -1299,7 +1299,7 @@ func TestAuthSwitchSHA256PasswordSecure(t *testing.T) { // Hack to make the caching_sha2_password plugin believe that the connection // is secure - mc.cfg.tls = &tls.Config{InsecureSkipVerify: true} + mc.cfg.TLS = &tls.Config{InsecureSkipVerify: true} // auth switch request conn.data = []byte{38, 0, 0, 2, 254, 115, 104, 97, 50, 53, 54, 95, 112, 97, diff --git a/dsn.go b/dsn.go index a306d66a3..57c66933a 100644 --- a/dsn.go +++ b/dsn.go @@ -46,13 +46,14 @@ type Config struct { ServerPubKey string // Server public key name pubKey *rsa.PublicKey // Server public key TLSConfig string // TLS configuration name - tls *tls.Config // TLS configuration + TLS *tls.Config // TLS configuration, its priority is higher than TLSConfig Timeout time.Duration // Dial timeout ReadTimeout time.Duration // I/O read timeout WriteTimeout time.Duration // I/O write timeout AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE AllowCleartextPasswords bool // Allows the cleartext client side plugin + AllowFallbackToNoTLS bool // Allows fallback to unencrypted connection if server does not support TLS AllowNativePasswords bool // Allows the native password authentication method AllowOldPasswords bool // Allows the old insecure password method CheckConnLiveness bool // Check connections for liveness before using them @@ -77,8 +78,8 @@ func NewConfig() *Config { func (cfg *Config) Clone() *Config { cp := *cfg - if cp.tls != nil { - cp.tls = cfg.tls.Clone() + if cp.TLS != nil { + cp.TLS = cfg.TLS.Clone() } if len(cp.Params) > 0 { cp.Params = make(map[string]string, len(cfg.Params)) @@ -119,24 +120,29 @@ func (cfg *Config) normalize() error { cfg.Addr = ensureHavePort(cfg.Addr) } - switch cfg.TLSConfig { - case "false", "": - // don't set anything - case "true": - cfg.tls = &tls.Config{} - case "skip-verify", "preferred": - cfg.tls = &tls.Config{InsecureSkipVerify: true} - default: - cfg.tls = getTLSConfigClone(cfg.TLSConfig) - if cfg.tls == nil { - return errors.New("invalid value / unknown config name: " + cfg.TLSConfig) + if cfg.TLS == nil { + switch cfg.TLSConfig { + case "false", "": + // don't set anything + case "true": + cfg.TLS = &tls.Config{} + case "skip-verify": + cfg.TLS = &tls.Config{InsecureSkipVerify: true} + case "preferred": + cfg.TLS = &tls.Config{InsecureSkipVerify: true} + cfg.AllowFallbackToNoTLS = true + default: + cfg.TLS = getTLSConfigClone(cfg.TLSConfig) + if cfg.TLS == nil { + return errors.New("invalid value / unknown config name: " + cfg.TLSConfig) + } } } - if cfg.tls != nil && cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify { + if cfg.TLS != nil && cfg.TLS.ServerName == "" && !cfg.TLS.InsecureSkipVerify { host, _, err := net.SplitHostPort(cfg.Addr) if err == nil { - cfg.tls.ServerName = host + cfg.TLS.ServerName = host } } @@ -204,6 +210,10 @@ func (cfg *Config) FormatDSN() string { writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true") } + if cfg.AllowFallbackToNoTLS { + writeDSNParam(&buf, &hasParam, "allowFallbackToNoTLS", "true") + } + if !cfg.AllowNativePasswords { writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false") } @@ -391,6 +401,14 @@ func parseDSNParams(cfg *Config, params string) (err error) { return errors.New("invalid bool value: " + value) } + // Allow fallback to unencrypted connection if server does not support TLS + case "allowFallbackToNoTLS": + var isBool bool + cfg.AllowFallbackToNoTLS, isBool = readBool(value) + if !isBool { + return errors.New("invalid bool value: " + value) + } + // Use native password authentication case "allowNativePasswords": var isBool bool diff --git a/dsn_test.go b/dsn_test.go index fc6eea9c8..721618c8c 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -42,8 +42,8 @@ var testDSNs = []struct { "user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true", &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true}, }, { - "user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0", - &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false, CheckConnLiveness: false}, + "user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0&allowFallbackToNoTLS=true", + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowFallbackToNoTLS: true, AllowNativePasswords: false, CheckConnLiveness: false}, }, { "user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", &Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true}, @@ -82,7 +82,7 @@ func TestDSNParser(t *testing.T) { } // pointer not static - cfg.tls = nil + cfg.TLS = nil if !reflect.DeepEqual(cfg, tst.out) { t.Errorf("%d. ParseDSN(%q) mismatch:\ngot %+v\nwant %+v", i, tst.in, cfg, tst.out) @@ -118,7 +118,7 @@ func TestDSNReformat(t *testing.T) { t.Error(err.Error()) continue } - cfg1.tls = nil // pointer not static + cfg1.TLS = nil // pointer not static res1 := fmt.Sprintf("%+v", cfg1) dsn2 := cfg1.FormatDSN() @@ -127,7 +127,7 @@ func TestDSNReformat(t *testing.T) { t.Error(err.Error()) continue } - cfg2.tls = nil // pointer not static + cfg2.TLS = nil // pointer not static res2 := fmt.Sprintf("%+v", cfg2) if res1 != res2 { @@ -203,7 +203,7 @@ func TestDSNWithCustomTLS(t *testing.T) { if err != nil { t.Error(err.Error()) - } else if cfg.tls.ServerName != name { + } else if cfg.TLS.ServerName != name { t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, tst) } @@ -214,7 +214,7 @@ func TestDSNWithCustomTLS(t *testing.T) { if err != nil { t.Error(err.Error()) - } else if cfg.tls.ServerName != name { + } else if cfg.TLS.ServerName != name { t.Errorf("did not get the correct ServerName (%s) parsing DSN (%s).", name, tst) } else if tlsCfg.ServerName != "" { t.Errorf("tlsCfg was mutated ServerName (%s) should be empty parsing DSN (%s).", name, tst) @@ -229,11 +229,11 @@ func TestDSNTLSConfig(t *testing.T) { if err != nil { t.Error(err.Error()) } - if cfg.tls == nil { + if cfg.TLS == nil { t.Error("cfg.tls should not be nil") } - if cfg.tls.ServerName != expectedServerName { - t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName) + if cfg.TLS.ServerName != expectedServerName { + t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.TLS.ServerName) } dsn = "tcp(example.com)/?tls=true" @@ -241,11 +241,11 @@ func TestDSNTLSConfig(t *testing.T) { if err != nil { t.Error(err.Error()) } - if cfg.tls == nil { + if cfg.TLS == nil { t.Error("cfg.tls should not be nil") } - if cfg.tls.ServerName != expectedServerName { - t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.tls.ServerName) + if cfg.TLS.ServerName != expectedServerName { + t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.TLS.ServerName) } } @@ -262,7 +262,7 @@ func TestDSNWithCustomTLSQueryEscape(t *testing.T) { if err != nil { t.Error(err.Error()) - } else if cfg.tls.ServerName != name { + } else if cfg.TLS.ServerName != name { t.Errorf("did not get the correct TLS ServerName (%s) parsing DSN (%s).", name, dsn) } } @@ -335,12 +335,12 @@ func TestCloneConfig(t *testing.T) { t.Errorf("Config.Clone did not create a separate config struct") } - if cfg2.tls.ServerName != expectedServerName { - t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.tls.ServerName) + if cfg2.TLS.ServerName != expectedServerName { + t.Errorf("cfg.tls.ServerName should be %q, got %q (host with port)", expectedServerName, cfg.TLS.ServerName) } - cfg2.tls.ServerName = "example2.com" - if cfg.tls.ServerName == cfg2.tls.ServerName { + cfg2.TLS.ServerName = "example2.com" + if cfg.TLS.ServerName == cfg2.TLS.ServerName { t.Errorf("changed cfg.tls.Server name should not propagate to original Config") } @@ -384,20 +384,20 @@ func TestNormalizeTLSConfig(t *testing.T) { cfg.normalize() - if cfg.tls == nil { + if cfg.TLS == nil { if tc.want != nil { t.Fatal("wanted a tls config but got nil instead") } return } - if cfg.tls.ServerName != tc.want.ServerName { + if cfg.TLS.ServerName != tc.want.ServerName { t.Errorf("tls.ServerName doesn't match (want: '%s', got: '%s')", - tc.want.ServerName, cfg.tls.ServerName) + tc.want.ServerName, cfg.TLS.ServerName) } - if cfg.tls.InsecureSkipVerify != tc.want.InsecureSkipVerify { + if cfg.TLS.InsecureSkipVerify != tc.want.InsecureSkipVerify { t.Errorf("tls.InsecureSkipVerify doesn't match (want: %T, got :%T)", - tc.want.InsecureSkipVerify, cfg.tls.InsecureSkipVerify) + tc.want.InsecureSkipVerify, cfg.TLS.InsecureSkipVerify) } }) } diff --git a/packets.go b/packets.go index 003584c25..9b844190b 100644 --- a/packets.go +++ b/packets.go @@ -222,9 +222,9 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro if mc.flags&clientProtocol41 == 0 { return nil, "", ErrOldProtocol } - if mc.flags&clientSSL == 0 && mc.cfg.tls != nil { - if mc.cfg.TLSConfig == "preferred" { - mc.cfg.tls = nil + if mc.flags&clientSSL == 0 && mc.cfg.TLS != nil { + if mc.cfg.AllowFallbackToNoTLS { + mc.cfg.TLS = nil } else { return nil, "", ErrNoTLS } @@ -292,7 +292,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string } // To enable TLS / SSL - if mc.cfg.tls != nil { + if mc.cfg.TLS != nil { clientFlags |= clientSSL } @@ -356,14 +356,14 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string // SSL Connection Request Packet // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest - if mc.cfg.tls != nil { + if mc.cfg.TLS != nil { // Send TLS / SSL request packet if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil { return err } // Switch to TLS - tlsConn := tls.Client(mc.netConn, mc.cfg.tls) + tlsConn := tls.Client(mc.netConn, mc.cfg.TLS) if err := tlsConn.Handshake(); err != nil { return err } From 9e2588b75909c1db698be62088a24b5a4e123ee2 Mon Sep 17 00:00:00 2001 From: lance6716 Date: Sun, 13 Nov 2022 12:21:42 +0800 Subject: [PATCH 5/7] add doc Signed-off-by: lance6716 --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index ded6e3b16..a0cb36b22 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,17 @@ Default: false `allowCleartextPasswords=true` allows using the [cleartext client side plugin](https://dev.mysql.com/doc/en/cleartext-pluggable-authentication.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network. + +##### `allowFallbackToNoTLS` + +``` +Type: bool +Valid Values: true, false +Default: false +``` + +`allowFallbackToNoTLS=true` acts like a `--ssl-mode=mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode) + ##### `allowNativePasswords` ``` From a58b468682298677a7a17c0ecf4555f23ae9f7bb Mon Sep 17 00:00:00 2001 From: lance6716 Date: Wed, 16 Nov 2022 19:39:34 +0800 Subject: [PATCH 6/7] Update README.md Co-authored-by: D3Hunter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0cb36b22..c847d88c5 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ Valid Values: true, false Default: false ``` -`allowFallbackToNoTLS=true` acts like a `--ssl-mode=mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode) +`allowFallbackToNoTLS=true` acts like a `--ssl-mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode) ##### `allowNativePasswords` From 4ced11522624f55d4d6bf0eb9e8f10d824d9de62 Mon Sep 17 00:00:00 2001 From: lance6716 Date: Tue, 22 Nov 2022 16:39:55 +0800 Subject: [PATCH 7/7] address comment Signed-off-by: lance6716 --- README.md | 4 ++-- dsn.go | 34 +++++++++++++++++----------------- dsn_test.go | 5 +++-- packets.go | 2 +- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c847d88c5..25de2e5aa 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Default: false `allowCleartextPasswords=true` allows using the [cleartext client side plugin](https://dev.mysql.com/doc/en/cleartext-pluggable-authentication.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network. -##### `allowFallbackToNoTLS` +##### `allowFallbackToPlaintext` ``` Type: bool @@ -166,7 +166,7 @@ Valid Values: true, false Default: false ``` -`allowFallbackToNoTLS=true` acts like a `--ssl-mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode) +`allowFallbackToPlaintext=true` acts like a `--ssl-mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode) ##### `allowNativePasswords` diff --git a/dsn.go b/dsn.go index 57c66933a..4b71aaab0 100644 --- a/dsn.go +++ b/dsn.go @@ -51,18 +51,18 @@ type Config struct { ReadTimeout time.Duration // I/O read timeout WriteTimeout time.Duration // I/O write timeout - AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE - AllowCleartextPasswords bool // Allows the cleartext client side plugin - AllowFallbackToNoTLS bool // Allows fallback to unencrypted connection if server does not support TLS - AllowNativePasswords bool // Allows the native password authentication method - AllowOldPasswords bool // Allows the old insecure password method - CheckConnLiveness bool // Check connections for liveness before using them - ClientFoundRows bool // Return number of matching rows instead of rows changed - ColumnsWithAlias bool // Prepend table alias to column names - InterpolateParams bool // Interpolate placeholders into query string - MultiStatements bool // Allow multiple statements in one query - ParseTime bool // Parse time values to time.Time - RejectReadOnly bool // Reject read-only connections + AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE + AllowCleartextPasswords bool // Allows the cleartext client side plugin + AllowFallbackToPlaintext bool // Allows fallback to unencrypted connection if server does not support TLS + AllowNativePasswords bool // Allows the native password authentication method + AllowOldPasswords bool // Allows the old insecure password method + CheckConnLiveness bool // Check connections for liveness before using them + ClientFoundRows bool // Return number of matching rows instead of rows changed + ColumnsWithAlias bool // Prepend table alias to column names + InterpolateParams bool // Interpolate placeholders into query string + MultiStatements bool // Allow multiple statements in one query + ParseTime bool // Parse time values to time.Time + RejectReadOnly bool // Reject read-only connections } // NewConfig creates a new Config and sets default values. @@ -130,7 +130,7 @@ func (cfg *Config) normalize() error { cfg.TLS = &tls.Config{InsecureSkipVerify: true} case "preferred": cfg.TLS = &tls.Config{InsecureSkipVerify: true} - cfg.AllowFallbackToNoTLS = true + cfg.AllowFallbackToPlaintext = true default: cfg.TLS = getTLSConfigClone(cfg.TLSConfig) if cfg.TLS == nil { @@ -210,8 +210,8 @@ func (cfg *Config) FormatDSN() string { writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true") } - if cfg.AllowFallbackToNoTLS { - writeDSNParam(&buf, &hasParam, "allowFallbackToNoTLS", "true") + if cfg.AllowFallbackToPlaintext { + writeDSNParam(&buf, &hasParam, "allowFallbackToPlaintext", "true") } if !cfg.AllowNativePasswords { @@ -402,9 +402,9 @@ func parseDSNParams(cfg *Config, params string) (err error) { } // Allow fallback to unencrypted connection if server does not support TLS - case "allowFallbackToNoTLS": + case "allowFallbackToPlaintext": var isBool bool - cfg.AllowFallbackToNoTLS, isBool = readBool(value) + cfg.AllowFallbackToPlaintext, isBool = readBool(value) if !isBool { return errors.New("invalid bool value: " + value) } diff --git a/dsn_test.go b/dsn_test.go index 721618c8c..41a6a29fa 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -42,8 +42,8 @@ var testDSNs = []struct { "user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true", &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true}, }, { - "user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0&allowFallbackToNoTLS=true", - &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowFallbackToNoTLS: true, AllowNativePasswords: false, CheckConnLiveness: false}, + "user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0&allowFallbackToPlaintext=true", + &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowFallbackToPlaintext: true, AllowNativePasswords: false, CheckConnLiveness: false}, }, { "user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", &Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true}, @@ -100,6 +100,7 @@ func TestDSNParserInvalid(t *testing.T) { "User:pass@tcp(1.2.3.4:3306)", // no trailing slash "net()/", // unknown default addr "user:pass@tcp(127.0.0.1:3306)/db/name", // invalid dbname + "user:password@/dbname?allowFallbackToPlaintext=PREFERRED", // wrong bool flag //"/dbname?arg=/some/unescaped/path", } diff --git a/packets.go b/packets.go index 9b844190b..ee05c95a8 100644 --- a/packets.go +++ b/packets.go @@ -223,7 +223,7 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro return nil, "", ErrOldProtocol } if mc.flags&clientSSL == 0 && mc.cfg.TLS != nil { - if mc.cfg.AllowFallbackToNoTLS { + if mc.cfg.AllowFallbackToPlaintext { mc.cfg.TLS = nil } else { return nil, "", ErrNoTLS