Skip to content

Commit 1c44c49

Browse files
committed
Add a new "tls-mode=preferred" DSN parameter
Separating "preferred" into its own parameter instead of making it a special value in the "tls=" parameter makes it possible to use custom TLS config with this mode. This is useful when clients don't need to authenticate servers using TLS but a server may or may not need to authenticate the client using TLS.
1 parent 972a708 commit 1c44c49

File tree

5 files changed

+61
-1
lines changed

5 files changed

+61
-1
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Daniel Montoya <dsmontoyam at gmail.com>
2525
Daniel Nichter <nil at codenode.com>
2626
Daniël van Eeden <git at myname.nl>
2727
Dave Protasowski <dprotaso at gmail.com>
28+
David Weitzman <dweitzman at gmail.com>
2829
DisposaBoy <disposaboy at dby.me>
2930
Egor Smolyakov <egorsmkv at gmail.com>
3031
Evan Shaw <evan at vendhq.com>

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,17 @@ Default: false
335335
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. This is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
336336

337337

338+
##### `tls-mode`
339+
340+
```
341+
Type: string
342+
Valid Values: preferred
343+
Default: <none>
344+
```
345+
346+
Use `tls-mode=preferred` to opt-in to TLS / SSL only with servers that support it. `preferred` does not authenticate the server but allows servers to optionally authenticate clients. The [`tls`](#tls) DSN parameter allows customizing the TLS config.
347+
348+
338349
##### `writeTimeout`
339350

340351
```

dsn.go

+20
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type Config struct {
4646
pubKey *rsa.PublicKey // Server public key
4747
TLSConfig string // TLS configuration name
4848
tls *tls.Config // TLS configuration
49+
TLSOptional bool // Allows non-TLS for servers that don't have TLS capability
4950
Timeout time.Duration // Dial timeout
5051
ReadTimeout time.Duration // I/O read timeout
5152
WriteTimeout time.Duration // I/O write timeout
@@ -287,6 +288,10 @@ func (cfg *Config) FormatDSN() string {
287288
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
288289
}
289290

291+
if cfg.TLSOptional && cfg.TLSConfig != "preferred" {
292+
buf.WriteString("&tls-mode=preferred")
293+
}
294+
290295
if cfg.WriteTimeout > 0 {
291296
if hasParam {
292297
buf.WriteString("&writeTimeout=")
@@ -550,6 +555,18 @@ func parseDSNParams(cfg *Config, params string) (err error) {
550555
return
551556
}
552557

558+
// TLS enforcement settings
559+
case "tls-mode":
560+
switch value {
561+
case "preferred":
562+
cfg.TLSOptional = true
563+
if cfg.tls == nil {
564+
cfg.tls = &tls.Config{}
565+
}
566+
default:
567+
return errors.New("invalid value / unknown tls-mode: " + value)
568+
}
569+
553570
// TLS-Encryption
554571
case "tls":
555572
boolValue, isBool := readBool(value)
@@ -563,6 +580,9 @@ func parseDSNParams(cfg *Config, params string) (err error) {
563580
} else if vl := strings.ToLower(value); vl == "skip-verify" || vl == "preferred" {
564581
cfg.TLSConfig = vl
565582
cfg.tls = &tls.Config{InsecureSkipVerify: true}
583+
if vl == "preferred" {
584+
cfg.TLSOptional = true
585+
}
566586
} else {
567587
name, err := url.QueryUnescape(value)
568588
if err != nil {

dsn_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ var testDSNs = []struct {
3535
}, {
3636
"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
3737
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"},
38+
}, {
39+
"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=preferred",
40+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "preferred", TLSOptional: true},
41+
}, {
42+
"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true&tls-mode=preferred",
43+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true", TLSOptional: true},
3844
}, {
3945
"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
4046
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
@@ -246,6 +252,28 @@ func TestDSNTLSConfig(t *testing.T) {
246252
if cfg.tls.ServerName != expectedServerName {
247253
t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.tls.ServerName)
248254
}
255+
256+
dsn = "tcp(example.com)/?tls-mode=invalid"
257+
_, err = ParseDSN(dsn)
258+
wantError := "invalid value / unknown tls-mode: invalid"
259+
if err == nil || err.Error() != wantError {
260+
t.Errorf("ParseDSN(%s). Got error: %v. Want error: %v", dsn, err, wantError)
261+
}
262+
263+
dsn = "tcp(example.com)/?tls-mode=preferred"
264+
cfg, err = ParseDSN(dsn)
265+
if err != nil {
266+
t.Error(err.Error())
267+
}
268+
if cfg.tls == nil {
269+
t.Error("cfg.tls should not be nil")
270+
}
271+
if cfg.tls.ServerName != expectedServerName {
272+
t.Errorf("cfg.tls.ServerName should be %q, got %q (host without port)", expectedServerName, cfg.tls.ServerName)
273+
}
274+
if !cfg.TLSOptional {
275+
t.Error("cfg.TLSOptional should be true")
276+
}
249277
}
250278

251279
func TestDSNWithCustomTLSQueryEscape(t *testing.T) {

packets.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
194194
return nil, "", ErrOldProtocol
195195
}
196196
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
197-
if mc.cfg.TLSConfig == "preferred" {
197+
if mc.cfg.TLSOptional {
198198
mc.cfg.tls = nil
199199
} else {
200200
return nil, "", ErrNoTLS

0 commit comments

Comments
 (0)