mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 02:46:04 +01:00 
			
		
		
		
	Add Redis Sentinel Authentication Support (#19213)
Gitea was not able to supply any authentication parameters to it. So this brings support to do that, along with some light extraction of a couple of bits into some separate functions for easier testing. I looked at other libraries supporting similar RedisUri-style connection strings (e.g. Lettuce), but it looks like this type of configuration is beyond what would typically be done in a connection string. Since gitea doesn't have configuration options for manually specifying all this redis connection detail, I went ahead and just chose straightforward names for these new parameters.
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							1d332342db
						
					
				
				
					commit
					a2c20a6cab
				
			| @@ -6,10 +6,13 @@ package nosql | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
|  | ||||
| 	"github.com/go-redis/redis/v8" | ||||
| ) | ||||
|  | ||||
| @@ -59,8 +62,59 @@ func (m *Manager) GetRedisClient(connection string) redis.UniversalClient { | ||||
| 		name: []string{connection, uri.String()}, | ||||
| 	} | ||||
|  | ||||
| 	opts := getRedisOptions(uri) | ||||
| 	tlsConfig := getRedisTLSOptions(uri) | ||||
|  | ||||
| 	clientName := uri.Query().Get("clientname") | ||||
|  | ||||
| 	if len(clientName) > 0 { | ||||
| 		client.name = append(client.name, clientName) | ||||
| 	} | ||||
|  | ||||
| 	switch uri.Scheme { | ||||
| 	case "redis+sentinels": | ||||
| 		fallthrough | ||||
| 	case "rediss+sentinel": | ||||
| 		opts.TLSConfig = tlsConfig | ||||
| 		fallthrough | ||||
| 	case "redis+sentinel": | ||||
| 		client.UniversalClient = redis.NewFailoverClient(opts.Failover()) | ||||
| 	case "redis+clusters": | ||||
| 		fallthrough | ||||
| 	case "rediss+cluster": | ||||
| 		opts.TLSConfig = tlsConfig | ||||
| 		fallthrough | ||||
| 	case "redis+cluster": | ||||
| 		client.UniversalClient = redis.NewClusterClient(opts.Cluster()) | ||||
| 	case "redis+socket": | ||||
| 		simpleOpts := opts.Simple() | ||||
| 		simpleOpts.Network = "unix" | ||||
| 		simpleOpts.Addr = path.Join(uri.Host, uri.Path) | ||||
| 		client.UniversalClient = redis.NewClient(simpleOpts) | ||||
| 	case "rediss": | ||||
| 		opts.TLSConfig = tlsConfig | ||||
| 		fallthrough | ||||
| 	case "redis": | ||||
| 		client.UniversalClient = redis.NewClient(opts.Simple()) | ||||
| 	default: | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for _, name := range client.name { | ||||
| 		m.RedisConnections[name] = client | ||||
| 	} | ||||
|  | ||||
| 	client.count++ | ||||
|  | ||||
| 	return client | ||||
| } | ||||
|  | ||||
| // getRedisOptions pulls various configuration options based on the RedisUri format and converts them to go-redis's | ||||
| // UniversalOptions fields. This function explicitly excludes fields related to TLS configuration, which is | ||||
| // conditionally attached to this options struct before being converted to the specific type for the redis scheme being | ||||
| // used, and only in scenarios where TLS is applicable (e.g. rediss://, redis+clusters://). | ||||
| func getRedisOptions(uri *url.URL) *redis.UniversalOptions { | ||||
| 	opts := &redis.UniversalOptions{} | ||||
| 	tlsConfig := &tls.Config{} | ||||
|  | ||||
| 	// Handle username/password | ||||
| 	if password, ok := uri.User.Password(); ok { | ||||
| @@ -131,75 +185,54 @@ func (m *Manager) GetRedisClient(connection string) redis.UniversalClient { | ||||
| 			fallthrough | ||||
| 		case "mastername": | ||||
| 			opts.MasterName = v[0] | ||||
| 		case "skipverify": | ||||
| 			fallthrough | ||||
| 		case "insecureskipverify": | ||||
| 			insecureSkipVerify, _ := strconv.ParseBool(v[0]) | ||||
| 			tlsConfig.InsecureSkipVerify = insecureSkipVerify | ||||
| 		case "clientname": | ||||
| 			client.name = append(client.name, v[0]) | ||||
| 		case "sentinelusername": | ||||
| 			opts.SentinelUsername = v[0] | ||||
| 		case "sentinelpassword": | ||||
| 			opts.SentinelPassword = v[0] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	switch uri.Scheme { | ||||
| 	case "redis+sentinels": | ||||
| 		fallthrough | ||||
| 	case "rediss+sentinel": | ||||
| 		opts.TLSConfig = tlsConfig | ||||
| 		fallthrough | ||||
| 	case "redis+sentinel": | ||||
| 		if uri.Host != "" { | ||||
| 			opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) | ||||
| 		} | ||||
| 		if uri.Path != "" { | ||||
| 			if db, err := strconv.Atoi(uri.Path[1:]); err == nil { | ||||
| 				opts.DB = db | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		client.UniversalClient = redis.NewFailoverClient(opts.Failover()) | ||||
| 	case "redis+clusters": | ||||
| 		fallthrough | ||||
| 	case "rediss+cluster": | ||||
| 		opts.TLSConfig = tlsConfig | ||||
| 		fallthrough | ||||
| 	case "redis+cluster": | ||||
| 		if uri.Host != "" { | ||||
| 			opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) | ||||
| 		} | ||||
| 		if uri.Path != "" { | ||||
| 			if db, err := strconv.Atoi(uri.Path[1:]); err == nil { | ||||
| 				opts.DB = db | ||||
| 			} | ||||
| 		} | ||||
| 		client.UniversalClient = redis.NewClusterClient(opts.Cluster()) | ||||
| 	case "redis+socket": | ||||
| 		simpleOpts := opts.Simple() | ||||
| 		simpleOpts.Network = "unix" | ||||
| 		simpleOpts.Addr = path.Join(uri.Host, uri.Path) | ||||
| 		client.UniversalClient = redis.NewClient(simpleOpts) | ||||
| 	case "rediss": | ||||
| 		opts.TLSConfig = tlsConfig | ||||
| 		fallthrough | ||||
| 	case "redis": | ||||
| 		if uri.Host != "" { | ||||
| 			opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) | ||||
| 		} | ||||
| 		if uri.Path != "" { | ||||
| 			if db, err := strconv.Atoi(uri.Path[1:]); err == nil { | ||||
| 				opts.DB = db | ||||
| 			} | ||||
| 		} | ||||
| 		client.UniversalClient = redis.NewClient(opts.Simple()) | ||||
| 	default: | ||||
| 		return nil | ||||
| 	if uri.Host != "" { | ||||
| 		opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...) | ||||
| 	} | ||||
|  | ||||
| 	for _, name := range client.name { | ||||
| 		m.RedisConnections[name] = client | ||||
| 	// A redis connection string uses the path section of the URI in two different ways. In a TCP-based connection, the | ||||
| 	// path will be a database index to automatically have the client SELECT. In a Unix socket connection, it will be the | ||||
| 	// file path. We only want to try to coerce this to the database index when we're not expecting a file path so that | ||||
| 	// the error log stays clean. | ||||
| 	if uri.Path != "" && uri.Scheme != "redis+socket" { | ||||
| 		if db, err := strconv.Atoi(uri.Path[1:]); err == nil { | ||||
| 			opts.DB = db | ||||
| 		} else { | ||||
| 			log.Error("Provided database identifier '%s' is not a valid integer. Gitea will ignore this option.", uri.Path) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	client.count++ | ||||
|  | ||||
| 	return client | ||||
| 	return opts | ||||
| } | ||||
|  | ||||
| // getRedisTlsOptions parses RedisUri TLS configuration parameters and converts them to the go TLS configuration | ||||
| // equivalent fields. | ||||
| func getRedisTLSOptions(uri *url.URL) *tls.Config { | ||||
| 	tlsConfig := &tls.Config{} | ||||
|  | ||||
| 	skipverify := uri.Query().Get("skipverify") | ||||
|  | ||||
| 	if len(skipverify) > 0 { | ||||
| 		skipverify, err := strconv.ParseBool(skipverify) | ||||
| 		if err != nil { | ||||
| 			tlsConfig.InsecureSkipVerify = skipverify | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	insecureskipverify := uri.Query().Get("insecureskipverify") | ||||
|  | ||||
| 	if len(insecureskipverify) > 0 { | ||||
| 		insecureskipverify, err := strconv.ParseBool(insecureskipverify) | ||||
| 		if err != nil { | ||||
| 			tlsConfig.InsecureSkipVerify = insecureskipverify | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return tlsConfig | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user