Skip to content

Commit 8c57646

Browse files
committed
feat: extend TLS URL parameters to all client types
Add comprehensive TLS URL parameter support across all Redis client types: - Cluster Client (ParseClusterURL): Full TLS parameter support - Sentinel Client (ParseFailoverURL): Full TLS parameter support - Universal Client: Inherits support from underlying clients Supported parameters for all client types: - tls_cert_file and tls_key_file: Client certificate authentication - tls_min_version and tls_max_version: TLS version constraints - tls_server_name: Server name override for certificate validation - skip_verify: Skip certificate verification (existing parameter) Features: - Consistent API across all client types - Comprehensive test coverage for cluster client - Enhanced documentation for all client configurations - Proper error handling and validation This ensures users have the same TLS configuration capabilities regardless of which Redis client type they use, providing a consistent and complete TLS configuration experience.
1 parent 3ba4a9a commit 8c57646

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

osscluster.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ func (opt *ClusterOptions) init() {
216216
// URL attributes (scheme, host, userinfo, resp.), query parameters using these
217217
// names will be treated as unknown parameters
218218
// - unknown parameter names will result in an error
219+
// - use "skip_verify=true" to ignore TLS certificate validation
220+
// - for rediss:// URLs, additional TLS parameters are supported:
221+
// - tls_cert_file and tls_key_file: paths to client certificate and key files
222+
// - tls_min_version and tls_max_version: TLS version constraints (e.g., 771 for TLS 1.2)
223+
// - tls_server_name: override server name for certificate validation
219224
//
220225
// Example:
221226
//
@@ -298,6 +303,42 @@ func setupClusterQueryParams(u *url.URL, o *ClusterOptions) (*ClusterOptions, er
298303
if q.err != nil {
299304
return nil, q.err
300305
}
306+
if o.TLSConfig != nil && q.has("skip_verify") {
307+
o.TLSConfig.InsecureSkipVerify = q.bool("skip_verify")
308+
}
309+
310+
if u.Scheme == "rediss" {
311+
tlsCertFile := q.string("tls_cert_file")
312+
tlsKeyFile := q.string("tls_key_file")
313+
314+
if (tlsCertFile == "") != (tlsKeyFile == "") {
315+
return nil, fmt.Errorf("redis: tls_cert_file and tls_key_file URL parameters must be both set or both omitted")
316+
}
317+
318+
if tlsCertFile != "" {
319+
cert, certLoadErr := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile)
320+
if certLoadErr != nil {
321+
return nil, fmt.Errorf("redis: error loading TLS certificate: %w", certLoadErr)
322+
}
323+
324+
o.TLSConfig.Certificates = []tls.Certificate{cert}
325+
}
326+
327+
if q.has("tls_min_version") {
328+
o.TLSConfig.MinVersion = uint16(q.int("tls_min_version"))
329+
}
330+
if q.has("tls_max_version") {
331+
o.TLSConfig.MaxVersion = uint16(q.int("tls_max_version"))
332+
}
333+
334+
tlsServerName := q.string("tls_server_name")
335+
if tlsServerName != "" {
336+
// we explicitly check for this query parameter, so we don't overwrite
337+
// the default server name (the hostname of the Redis server) if it's
338+
// not given
339+
o.TLSConfig.ServerName = tlsServerName
340+
}
341+
}
301342

302343
// addr can be specified as many times as needed
303344
addrs := q.strings("addr")

osscluster_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,6 +1603,27 @@ var _ = Describe("ClusterClient timeout", func() {
16031603
})
16041604

16051605
var _ = Describe("ClusterClient ParseURL", func() {
1606+
certPem := []byte(`-----BEGIN CERTIFICATE-----
1607+
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
1608+
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
1609+
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
1610+
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
1611+
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
1612+
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
1613+
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
1614+
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
1615+
6MF9+Yw1Yy0t
1616+
-----END CERTIFICATE-----`)
1617+
keyPem := []byte(`-----BEGIN EC PRIVATE KEY-----
1618+
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
1619+
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
1620+
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
1621+
-----END EC PRIVATE KEY-----`)
1622+
testCert, err := tls.X509KeyPair(certPem, keyPem)
1623+
if err != nil {
1624+
panic(err)
1625+
}
1626+
16061627
cases := []struct {
16071628
test string
16081629
url string
@@ -1633,6 +1654,18 @@ var _ = Describe("ClusterClient ParseURL", func() {
16331654
test: "MultipleRedissURLs",
16341655
url: "rediss://localhost:123?addr=localhost:1234&addr=localhost:12345",
16351656
o: &redis.ClusterOptions{Addrs: []string{"localhost:123", "localhost:1234", "localhost:12345"}, TLSConfig: &tls.Config{ServerName: "localhost"}},
1657+
}, {
1658+
test: "RedissTLSParams",
1659+
url: "rediss://localhost:123?tls_server_name=abc&tls_min_version=1&tls_max_version=3&skip_verify=true",
1660+
o: &redis.ClusterOptions{Addrs: []string{"localhost:123"}, TLSConfig: &tls.Config{ServerName: "abc", MinVersion: 1, MaxVersion: 3, InsecureSkipVerify: true}},
1661+
}, {
1662+
test: "RedissTLSCert",
1663+
url: "rediss://localhost:123?tls_cert_file=./testdata/testcert.pem&tls_key_file=./testdata/testkey.pem",
1664+
o: &redis.ClusterOptions{Addrs: []string{"localhost:123"}, TLSConfig: &tls.Config{ServerName: "localhost", Certificates: []tls.Certificate{testCert}}},
1665+
}, {
1666+
test: "RedissSkipVerify",
1667+
url: "rediss://localhost:123?skip_verify=true",
1668+
o: &redis.ClusterOptions{Addrs: []string{"localhost:123"}, TLSConfig: &tls.Config{ServerName: "localhost", InsecureSkipVerify: true}},
16361669
}, {
16371670
test: "OnlyPassword",
16381671
url: "redis://:bar@localhost:123",

sentinel.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
302302
// names will be treated as unknown parameters
303303
// - unknown parameter names will result in an error
304304
// - use "skip_verify=true" to ignore TLS certificate validation
305+
// - for rediss:// URLs, additional TLS parameters are supported:
306+
// - tls_cert_file and tls_key_file: paths to client certificate and key files
307+
// - tls_min_version and tls_max_version: TLS version constraints (e.g., 771 for TLS 1.2)
308+
// - tls_server_name: override server name for certificate validation
305309
//
306310
// Example:
307311
//
@@ -413,6 +417,39 @@ func setupFailoverConnParams(u *url.URL, o *FailoverOptions) (*FailoverOptions,
413417
o.TLSConfig.InsecureSkipVerify = q.bool("skip_verify")
414418
}
415419

420+
if u.Scheme == "rediss" {
421+
tlsCertFile := q.string("tls_cert_file")
422+
tlsKeyFile := q.string("tls_key_file")
423+
424+
if (tlsCertFile == "") != (tlsKeyFile == "") {
425+
return nil, fmt.Errorf("redis: tls_cert_file and tls_key_file URL parameters must be both set or both omitted")
426+
}
427+
428+
if tlsCertFile != "" {
429+
cert, certLoadErr := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile)
430+
if certLoadErr != nil {
431+
return nil, fmt.Errorf("redis: error loading TLS certificate: %w", certLoadErr)
432+
}
433+
434+
o.TLSConfig.Certificates = []tls.Certificate{cert}
435+
}
436+
437+
if q.has("tls_min_version") {
438+
o.TLSConfig.MinVersion = uint16(q.int("tls_min_version"))
439+
}
440+
if q.has("tls_max_version") {
441+
o.TLSConfig.MaxVersion = uint16(q.int("tls_max_version"))
442+
}
443+
444+
tlsServerName := q.string("tls_server_name")
445+
if tlsServerName != "" {
446+
// we explicitly check for this query parameter, so we don't overwrite
447+
// the default server name (the hostname of the Redis server) if it's
448+
// not given
449+
o.TLSConfig.ServerName = tlsServerName
450+
}
451+
}
452+
416453
// any parameters left?
417454
if r := q.remaining(); len(r) > 0 {
418455
return nil, fmt.Errorf("redis: unexpected option: %s", strings.Join(r, ", "))

0 commit comments

Comments
 (0)