mirror of
https://github.com/redis/go-redis.git
synced 2025-09-05 20:24:00 +03:00
Address the final 3 CodeQL security alerts for 'Insecure TLS configuration': **Root Cause**: CodeQL detected that setting or would result in , which is insecure (TLS version 0). **Security Fix**: - When or is specified, don't set the TLS version at all - let Go use its secure defaults - Only set explicit TLS versions when they are >= TLS 1.2 (secure) - Applied fix consistently across all client types **Files Fixed**: - options.go (lines 609, 620) - Single client - osscluster.go (lines 336, 350) - Cluster client - sentinel.go (lines 446, 460) - Sentinel client **Security Behavior**: - → Don't set MinVersion (Go default: secure) - → Error: insecure, minimum TLS 1.2 required - → Set explicit secure version - Same logic applies to **Test Coverage**: - Added test case for behavior - Verified all security validation tests pass - Confirmed no regression in functionality This resolves all remaining CodeQL security alerts while maintaining secure defaults and clear error messages for insecure configurations.
684 lines
20 KiB
Go
684 lines
20 KiB
Go
package redis
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9/auth"
|
|
"github.com/redis/go-redis/v9/internal/pool"
|
|
"github.com/redis/go-redis/v9/internal/proto"
|
|
)
|
|
|
|
// Limiter is the interface of a rate limiter or a circuit breaker.
|
|
type Limiter interface {
|
|
// Allow returns nil if operation is allowed or an error otherwise.
|
|
// If operation is allowed client must ReportResult of the operation
|
|
// whether it is a success or a failure.
|
|
Allow() error
|
|
// ReportResult reports the result of the previously allowed operation.
|
|
// nil indicates a success, non-nil error usually indicates a failure.
|
|
ReportResult(result error)
|
|
}
|
|
|
|
// Options keeps the settings to set up redis connection.
|
|
type Options struct {
|
|
|
|
// Network type, either tcp or unix.
|
|
//
|
|
// default: is tcp.
|
|
Network string
|
|
|
|
// Addr is the address formated as host:port
|
|
Addr string
|
|
|
|
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
|
|
ClientName string
|
|
|
|
// Dialer creates new network connection and has priority over
|
|
// Network and Addr options.
|
|
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
|
|
// Hook that is called when new connection is established.
|
|
OnConnect func(ctx context.Context, cn *Conn) error
|
|
|
|
// Protocol 2 or 3. Use the version to negotiate RESP version with redis-server.
|
|
//
|
|
// default: 3.
|
|
Protocol int
|
|
|
|
// Username is used to authenticate the current connection
|
|
// with one of the connections defined in the ACL list when connecting
|
|
// to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
|
|
Username string
|
|
|
|
// Password is an optional password. Must match the password specified in the
|
|
// `requirepass` server configuration option (if connecting to a Redis 5.0 instance, or lower),
|
|
// or the User Password when connecting to a Redis 6.0 instance, or greater,
|
|
// that is using the Redis ACL system.
|
|
Password string
|
|
|
|
// CredentialsProvider allows the username and password to be updated
|
|
// before reconnecting. It should return the current username and password.
|
|
CredentialsProvider func() (username string, password string)
|
|
|
|
// CredentialsProviderContext is an enhanced parameter of CredentialsProvider,
|
|
// done to maintain API compatibility. In the future,
|
|
// there might be a merge between CredentialsProviderContext and CredentialsProvider.
|
|
// There will be a conflict between them; if CredentialsProviderContext exists, we will ignore CredentialsProvider.
|
|
CredentialsProviderContext func(ctx context.Context) (username string, password string, err error)
|
|
|
|
// StreamingCredentialsProvider is used to retrieve the credentials
|
|
// for the connection from an external source. Those credentials may change
|
|
// during the connection lifetime. This is useful for managed identity
|
|
// scenarios where the credentials are retrieved from an external source.
|
|
//
|
|
// Currently, this is a placeholder for the future implementation.
|
|
StreamingCredentialsProvider auth.StreamingCredentialsProvider
|
|
|
|
// DB is the database to be selected after connecting to the server.
|
|
DB int
|
|
|
|
// MaxRetries is the maximum number of retries before giving up.
|
|
// -1 (not 0) disables retries.
|
|
//
|
|
// default: 3 retries
|
|
MaxRetries int
|
|
|
|
// MinRetryBackoff is the minimum backoff between each retry.
|
|
// -1 disables backoff.
|
|
//
|
|
// default: 8 milliseconds
|
|
MinRetryBackoff time.Duration
|
|
|
|
// MaxRetryBackoff is the maximum backoff between each retry.
|
|
// -1 disables backoff.
|
|
// default: 512 milliseconds;
|
|
MaxRetryBackoff time.Duration
|
|
|
|
// DialTimeout for establishing new connections.
|
|
//
|
|
// default: 5 seconds
|
|
DialTimeout time.Duration
|
|
|
|
// ReadTimeout for socket reads. If reached, commands will fail
|
|
// with a timeout instead of blocking. Supported values:
|
|
//
|
|
// - `-1` - no timeout (block indefinitely).
|
|
// - `-2` - disables SetReadDeadline calls completely.
|
|
//
|
|
// default: 3 seconds
|
|
ReadTimeout time.Duration
|
|
|
|
// WriteTimeout for socket writes. If reached, commands will fail
|
|
// with a timeout instead of blocking. Supported values:
|
|
//
|
|
// - `-1` - no timeout (block indefinitely).
|
|
// - `-2` - disables SetWriteDeadline calls completely.
|
|
//
|
|
// default: 3 seconds
|
|
WriteTimeout time.Duration
|
|
|
|
// ContextTimeoutEnabled controls whether the client respects context timeouts and deadlines.
|
|
// See https://redis.uptrace.dev/guide/go-redis-debugging.html#timeouts
|
|
ContextTimeoutEnabled bool
|
|
|
|
// ReadBufferSize is the size of the bufio.Reader buffer for each connection.
|
|
// Larger buffers can improve performance for commands that return large responses.
|
|
// Smaller buffers can improve memory usage for larger pools.
|
|
//
|
|
// default: 256KiB (262144 bytes)
|
|
ReadBufferSize int
|
|
|
|
// WriteBufferSize is the size of the bufio.Writer buffer for each connection.
|
|
// Larger buffers can improve performance for large pipelines and commands with many arguments.
|
|
// Smaller buffers can improve memory usage for larger pools.
|
|
//
|
|
// default: 256KiB (262144 bytes)
|
|
WriteBufferSize int
|
|
|
|
// PoolFIFO type of connection pool.
|
|
//
|
|
// - true for FIFO pool
|
|
// - false for LIFO pool.
|
|
//
|
|
// Note that FIFO has slightly higher overhead compared to LIFO,
|
|
// but it helps closing idle connections faster reducing the pool size.
|
|
PoolFIFO bool
|
|
|
|
// PoolSize is the base number of socket connections.
|
|
// Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS.
|
|
// If there is not enough connections in the pool, new connections will be allocated in excess of PoolSize,
|
|
// you can limit it through MaxActiveConns
|
|
//
|
|
// default: 10 * runtime.GOMAXPROCS(0)
|
|
PoolSize int
|
|
|
|
// PoolTimeout is the amount of time client waits for connection if all connections
|
|
// are busy before returning an error.
|
|
//
|
|
// default: ReadTimeout + 1 second
|
|
PoolTimeout time.Duration
|
|
|
|
// MinIdleConns is the minimum number of idle connections which is useful when establishing
|
|
// new connection is slow. The idle connections are not closed by default.
|
|
//
|
|
// default: 0
|
|
MinIdleConns int
|
|
|
|
// MaxIdleConns is the maximum number of idle connections.
|
|
// The idle connections are not closed by default.
|
|
//
|
|
// default: 0
|
|
MaxIdleConns int
|
|
|
|
// MaxActiveConns is the maximum number of connections allocated by the pool at a given time.
|
|
// When zero, there is no limit on the number of connections in the pool.
|
|
// If the pool is full, the next call to Get() will block until a connection is released.
|
|
MaxActiveConns int
|
|
|
|
// ConnMaxIdleTime is the maximum amount of time a connection may be idle.
|
|
// Should be less than server's timeout.
|
|
//
|
|
// Expired connections may be closed lazily before reuse.
|
|
// If d <= 0, connections are not closed due to a connection's idle time.
|
|
// -1 disables idle timeout check.
|
|
//
|
|
// default: 30 minutes
|
|
ConnMaxIdleTime time.Duration
|
|
|
|
// ConnMaxLifetime is the maximum amount of time a connection may be reused.
|
|
//
|
|
// Expired connections may be closed lazily before reuse.
|
|
// If <= 0, connections are not closed due to a connection's age.
|
|
//
|
|
// default: 0
|
|
ConnMaxLifetime time.Duration
|
|
|
|
// TLSConfig to use. When set, TLS will be negotiated.
|
|
TLSConfig *tls.Config
|
|
|
|
// Limiter interface used to implement circuit breaker or rate limiter.
|
|
Limiter Limiter
|
|
|
|
// readOnly enables read only queries on slave/follower nodes.
|
|
readOnly bool
|
|
|
|
// DisableIndentity - Disable set-lib on connect.
|
|
//
|
|
// default: false
|
|
//
|
|
// Deprecated: Use DisableIdentity instead.
|
|
DisableIndentity bool
|
|
|
|
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
|
//
|
|
// default: false
|
|
DisableIdentity bool
|
|
|
|
// Add suffix to client name. Default is empty.
|
|
// IdentitySuffix - add suffix to client name.
|
|
IdentitySuffix string
|
|
|
|
// UnstableResp3 enables Unstable mode for Redis Search module with RESP3.
|
|
// When unstable mode is enabled, the client will use RESP3 protocol and only be able to use RawResult
|
|
UnstableResp3 bool
|
|
|
|
// FailingTimeoutSeconds is the timeout in seconds for marking a cluster node as failing.
|
|
// When a node is marked as failing, it will be avoided for this duration.
|
|
// Default is 15 seconds.
|
|
FailingTimeoutSeconds int
|
|
}
|
|
|
|
func (opt *Options) init() {
|
|
if opt.Addr == "" {
|
|
opt.Addr = "localhost:6379"
|
|
}
|
|
if opt.Network == "" {
|
|
if strings.HasPrefix(opt.Addr, "/") {
|
|
opt.Network = "unix"
|
|
} else {
|
|
opt.Network = "tcp"
|
|
}
|
|
}
|
|
if opt.Protocol < 2 {
|
|
opt.Protocol = 3
|
|
}
|
|
if opt.DialTimeout == 0 {
|
|
opt.DialTimeout = 5 * time.Second
|
|
}
|
|
if opt.Dialer == nil {
|
|
opt.Dialer = NewDialer(opt)
|
|
}
|
|
if opt.PoolSize == 0 {
|
|
opt.PoolSize = 10 * runtime.GOMAXPROCS(0)
|
|
}
|
|
if opt.ReadBufferSize == 0 {
|
|
opt.ReadBufferSize = proto.DefaultBufferSize
|
|
}
|
|
if opt.WriteBufferSize == 0 {
|
|
opt.WriteBufferSize = proto.DefaultBufferSize
|
|
}
|
|
switch opt.ReadTimeout {
|
|
case -2:
|
|
opt.ReadTimeout = -1
|
|
case -1:
|
|
opt.ReadTimeout = 0
|
|
case 0:
|
|
opt.ReadTimeout = 3 * time.Second
|
|
}
|
|
switch opt.WriteTimeout {
|
|
case -2:
|
|
opt.WriteTimeout = -1
|
|
case -1:
|
|
opt.WriteTimeout = 0
|
|
case 0:
|
|
opt.WriteTimeout = opt.ReadTimeout
|
|
}
|
|
if opt.PoolTimeout == 0 {
|
|
if opt.ReadTimeout > 0 {
|
|
opt.PoolTimeout = opt.ReadTimeout + time.Second
|
|
} else {
|
|
opt.PoolTimeout = 30 * time.Second
|
|
}
|
|
}
|
|
if opt.ConnMaxIdleTime == 0 {
|
|
opt.ConnMaxIdleTime = 30 * time.Minute
|
|
}
|
|
|
|
switch opt.MaxRetries {
|
|
case -1:
|
|
opt.MaxRetries = 0
|
|
case 0:
|
|
opt.MaxRetries = 3
|
|
}
|
|
switch opt.MinRetryBackoff {
|
|
case -1:
|
|
opt.MinRetryBackoff = 0
|
|
case 0:
|
|
opt.MinRetryBackoff = 8 * time.Millisecond
|
|
}
|
|
switch opt.MaxRetryBackoff {
|
|
case -1:
|
|
opt.MaxRetryBackoff = 0
|
|
case 0:
|
|
opt.MaxRetryBackoff = 512 * time.Millisecond
|
|
}
|
|
}
|
|
|
|
func (opt *Options) clone() *Options {
|
|
clone := *opt
|
|
return &clone
|
|
}
|
|
|
|
// NewDialer returns a function that will be used as the default dialer
|
|
// when none is specified in Options.Dialer.
|
|
func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, error) {
|
|
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
netDialer := &net.Dialer{
|
|
Timeout: opt.DialTimeout,
|
|
KeepAlive: 5 * time.Minute,
|
|
}
|
|
if opt.TLSConfig == nil {
|
|
return netDialer.DialContext(ctx, network, addr)
|
|
}
|
|
return tls.DialWithDialer(netDialer, network, addr, opt.TLSConfig)
|
|
}
|
|
}
|
|
|
|
// ParseURL parses a URL into Options that can be used to connect to Redis.
|
|
// Scheme is required.
|
|
// There are two connection types: by tcp socket and by unix socket.
|
|
// Tcp connection:
|
|
//
|
|
// redis://<user>:<password>@<host>:<port>/<db_number>
|
|
//
|
|
// Unix connection:
|
|
//
|
|
// unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
|
|
//
|
|
// Most Option fields can be set using query parameters, with the following restrictions:
|
|
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
|
// - only scalar type fields are supported (bool, int, time.Duration)
|
|
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
|
// additionally a plain integer as value (i.e. without unit) is interpreted as seconds
|
|
// - to disable a duration field, use value less than or equal to 0; to use the default
|
|
// value, leave the value blank or remove the parameter
|
|
// - only the last value is interpreted if a parameter is given multiple times
|
|
// - fields "network", "addr", "username" and "password" can only be set using other
|
|
// URL attributes (scheme, host, userinfo, resp.), query parameters using these
|
|
// names will be treated as unknown parameters
|
|
// - unknown parameter names will result in an error
|
|
// - use "skip_verify=true" to ignore TLS certificate validation
|
|
// - for rediss:// URLs, additional TLS parameters are supported:
|
|
// - tls_cert_file and tls_key_file: paths to client certificate and key files
|
|
// - tls_min_version and tls_max_version: TLS version constraints (minimum TLS 1.2: 771, TLS 1.3: 772)
|
|
// - tls_server_name: override server name for certificate validation
|
|
//
|
|
// Examples:
|
|
//
|
|
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
|
|
// is equivalent to:
|
|
// &Options{
|
|
// Network: "tcp",
|
|
// Addr: "localhost:6789",
|
|
// DB: 1, // path "/3" was overridden by "&db=1"
|
|
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
|
// ReadTimeout: 6 * time.Second,
|
|
// MaxRetries: 2,
|
|
// }
|
|
func ParseURL(redisURL string) (*Options, error) {
|
|
u, err := url.Parse(redisURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch u.Scheme {
|
|
case "redis", "rediss":
|
|
return setupTCPConn(u)
|
|
case "unix":
|
|
return setupUnixConn(u)
|
|
default:
|
|
return nil, fmt.Errorf("redis: invalid URL scheme: %s", u.Scheme)
|
|
}
|
|
}
|
|
|
|
func setupTCPConn(u *url.URL) (*Options, error) {
|
|
o := &Options{Network: "tcp"}
|
|
|
|
o.Username, o.Password = getUserPassword(u)
|
|
|
|
h, p := getHostPortWithDefaults(u)
|
|
o.Addr = net.JoinHostPort(h, p)
|
|
|
|
f := strings.FieldsFunc(u.Path, func(r rune) bool {
|
|
return r == '/'
|
|
})
|
|
switch len(f) {
|
|
case 0:
|
|
o.DB = 0
|
|
case 1:
|
|
var err error
|
|
if o.DB, err = strconv.Atoi(f[0]); err != nil {
|
|
return nil, fmt.Errorf("redis: invalid database number: %q", f[0])
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("redis: invalid URL path: %s", u.Path)
|
|
}
|
|
|
|
if u.Scheme == "rediss" {
|
|
o.TLSConfig = &tls.Config{
|
|
ServerName: h,
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
}
|
|
|
|
return setupConnParams(u, o)
|
|
}
|
|
|
|
// getHostPortWithDefaults is a helper function that splits the url into
|
|
// a host and a port. If the host is missing, it defaults to localhost
|
|
// and if the port is missing, it defaults to 6379.
|
|
func getHostPortWithDefaults(u *url.URL) (string, string) {
|
|
host, port, err := net.SplitHostPort(u.Host)
|
|
if err != nil {
|
|
host = u.Host
|
|
}
|
|
if host == "" {
|
|
host = "localhost"
|
|
}
|
|
if port == "" {
|
|
port = "6379"
|
|
}
|
|
return host, port
|
|
}
|
|
|
|
func setupUnixConn(u *url.URL) (*Options, error) {
|
|
o := &Options{
|
|
Network: "unix",
|
|
}
|
|
|
|
if strings.TrimSpace(u.Path) == "" { // path is required with unix connection
|
|
return nil, errors.New("redis: empty unix socket path")
|
|
}
|
|
o.Addr = u.Path
|
|
o.Username, o.Password = getUserPassword(u)
|
|
return setupConnParams(u, o)
|
|
}
|
|
|
|
type queryOptions struct {
|
|
q url.Values
|
|
err error
|
|
}
|
|
|
|
func (o *queryOptions) has(name string) bool {
|
|
return len(o.q[name]) > 0
|
|
}
|
|
|
|
func (o *queryOptions) string(name string) string {
|
|
vs := o.q[name]
|
|
if len(vs) == 0 {
|
|
return ""
|
|
}
|
|
delete(o.q, name) // enable detection of unknown parameters
|
|
return vs[len(vs)-1]
|
|
}
|
|
|
|
func (o *queryOptions) strings(name string) []string {
|
|
vs := o.q[name]
|
|
delete(o.q, name)
|
|
return vs
|
|
}
|
|
|
|
func (o *queryOptions) int(name string) int {
|
|
s := o.string(name)
|
|
if s == "" {
|
|
return 0
|
|
}
|
|
i, err := strconv.Atoi(s)
|
|
if err == nil {
|
|
return i
|
|
}
|
|
if o.err == nil {
|
|
o.err = fmt.Errorf("redis: invalid %s number: %s", name, err)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (o *queryOptions) duration(name string) time.Duration {
|
|
s := o.string(name)
|
|
if s == "" {
|
|
return 0
|
|
}
|
|
// try plain number first
|
|
if i, err := strconv.Atoi(s); err == nil {
|
|
if i <= 0 {
|
|
// disable timeouts
|
|
return -1
|
|
}
|
|
return time.Duration(i) * time.Second
|
|
}
|
|
dur, err := time.ParseDuration(s)
|
|
if err == nil {
|
|
return dur
|
|
}
|
|
if o.err == nil {
|
|
o.err = fmt.Errorf("redis: invalid %s duration: %w", name, err)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (o *queryOptions) bool(name string) bool {
|
|
switch s := o.string(name); s {
|
|
case "true", "1":
|
|
return true
|
|
case "false", "0", "":
|
|
return false
|
|
default:
|
|
if o.err == nil {
|
|
o.err = fmt.Errorf("redis: invalid %s boolean: expected true/false/1/0 or an empty string, got %q", name, s)
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (o *queryOptions) remaining() []string {
|
|
if len(o.q) == 0 {
|
|
return nil
|
|
}
|
|
keys := make([]string, 0, len(o.q))
|
|
for k := range o.q {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|
|
|
|
// setupConnParams converts query parameters in u to option value in o.
|
|
func setupConnParams(u *url.URL, o *Options) (*Options, error) {
|
|
q := queryOptions{q: u.Query()}
|
|
|
|
// compat: a future major release may use q.int("db")
|
|
if tmp := q.string("db"); tmp != "" {
|
|
db, err := strconv.Atoi(tmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("redis: invalid database number: %w", err)
|
|
}
|
|
o.DB = db
|
|
}
|
|
|
|
o.Protocol = q.int("protocol")
|
|
o.ClientName = q.string("client_name")
|
|
o.MaxRetries = q.int("max_retries")
|
|
o.MinRetryBackoff = q.duration("min_retry_backoff")
|
|
o.MaxRetryBackoff = q.duration("max_retry_backoff")
|
|
o.DialTimeout = q.duration("dial_timeout")
|
|
o.ReadTimeout = q.duration("read_timeout")
|
|
o.WriteTimeout = q.duration("write_timeout")
|
|
o.PoolFIFO = q.bool("pool_fifo")
|
|
o.PoolSize = q.int("pool_size")
|
|
o.PoolTimeout = q.duration("pool_timeout")
|
|
o.MinIdleConns = q.int("min_idle_conns")
|
|
o.MaxIdleConns = q.int("max_idle_conns")
|
|
o.MaxActiveConns = q.int("max_active_conns")
|
|
if q.has("conn_max_idle_time") {
|
|
o.ConnMaxIdleTime = q.duration("conn_max_idle_time")
|
|
} else {
|
|
o.ConnMaxIdleTime = q.duration("idle_timeout")
|
|
}
|
|
if q.has("conn_max_lifetime") {
|
|
o.ConnMaxLifetime = q.duration("conn_max_lifetime")
|
|
} else {
|
|
o.ConnMaxLifetime = q.duration("max_conn_age")
|
|
}
|
|
|
|
if u.Scheme == "rediss" {
|
|
tlsCertFile := q.string("tls_cert_file")
|
|
tlsKeyFile := q.string("tls_key_file")
|
|
|
|
if (tlsCertFile == "") != (tlsKeyFile == "") {
|
|
return nil, fmt.Errorf("redis: tls_cert_file and tls_key_file URL parameters must be both set or both omitted")
|
|
}
|
|
|
|
if tlsCertFile != "" {
|
|
cert, certLoadErr := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile)
|
|
if certLoadErr != nil {
|
|
return nil, fmt.Errorf("redis: error loading TLS certificate: %w", certLoadErr)
|
|
}
|
|
|
|
o.TLSConfig.Certificates = []tls.Certificate{cert}
|
|
}
|
|
|
|
if q.has("tls_min_version") {
|
|
minVer := q.int("tls_min_version")
|
|
if minVer < 0 || minVer > 65535 {
|
|
return nil, fmt.Errorf("redis: invalid tls_min_version: %d (must be between 0 and 65535)", minVer)
|
|
}
|
|
// Handle TLS version setting securely
|
|
if minVer == 0 {
|
|
// Don't set MinVersion, let Go use its secure default
|
|
} else if minVer < int(tls.VersionTLS12) {
|
|
return nil, fmt.Errorf("redis: tls_min_version %d is insecure (minimum allowed is TLS 1.2: %d)", minVer, tls.VersionTLS12)
|
|
} else {
|
|
o.TLSConfig.MinVersion = uint16(minVer)
|
|
}
|
|
}
|
|
if q.has("tls_max_version") {
|
|
maxVer := q.int("tls_max_version")
|
|
if maxVer < 0 || maxVer > 65535 {
|
|
return nil, fmt.Errorf("redis: invalid tls_max_version: %d (must be between 0 and 65535)", maxVer)
|
|
}
|
|
// Handle TLS max version setting securely
|
|
if maxVer == 0 {
|
|
// Don't set MaxVersion, let Go use its secure default
|
|
} else if maxVer < int(tls.VersionTLS12) {
|
|
return nil, fmt.Errorf("redis: tls_max_version %d is insecure (minimum allowed is TLS 1.2: %d)", maxVer, tls.VersionTLS12)
|
|
} else {
|
|
o.TLSConfig.MaxVersion = uint16(maxVer)
|
|
}
|
|
}
|
|
|
|
tlsServerName := q.string("tls_server_name")
|
|
if tlsServerName != "" {
|
|
// we explicitly check for this query parameter, so we don't overwrite
|
|
// the default server name (the hostname of the Redis server) if it's
|
|
// not given
|
|
o.TLSConfig.ServerName = tlsServerName
|
|
}
|
|
}
|
|
if q.err != nil {
|
|
return nil, q.err
|
|
}
|
|
if o.TLSConfig != nil && q.has("skip_verify") {
|
|
o.TLSConfig.InsecureSkipVerify = q.bool("skip_verify")
|
|
}
|
|
|
|
// any parameters left?
|
|
if r := q.remaining(); len(r) > 0 {
|
|
return nil, fmt.Errorf("redis: unexpected option: %s", strings.Join(r, ", "))
|
|
}
|
|
|
|
return o, nil
|
|
}
|
|
|
|
func getUserPassword(u *url.URL) (string, string) {
|
|
var user, password string
|
|
if u.User != nil {
|
|
user = u.User.Username()
|
|
if p, ok := u.User.Password(); ok {
|
|
password = p
|
|
}
|
|
}
|
|
return user, password
|
|
}
|
|
|
|
func newConnPool(
|
|
opt *Options,
|
|
dialer func(ctx context.Context, network, addr string) (net.Conn, error),
|
|
) *pool.ConnPool {
|
|
return pool.NewConnPool(&pool.Options{
|
|
Dialer: func(ctx context.Context) (net.Conn, error) {
|
|
return dialer(ctx, opt.Network, opt.Addr)
|
|
},
|
|
PoolFIFO: opt.PoolFIFO,
|
|
PoolSize: opt.PoolSize,
|
|
PoolTimeout: opt.PoolTimeout,
|
|
DialTimeout: opt.DialTimeout,
|
|
MinIdleConns: opt.MinIdleConns,
|
|
MaxIdleConns: opt.MaxIdleConns,
|
|
MaxActiveConns: opt.MaxActiveConns,
|
|
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
|
ConnMaxLifetime: opt.ConnMaxLifetime,
|
|
ReadBufferSize: opt.ReadBufferSize,
|
|
WriteBufferSize: opt.WriteBufferSize,
|
|
})
|
|
}
|