Add ability to configure username for Redis cluster connections (#2381)
* Initial attempt. * Add CHANGELOG entry. * Drop commented-out Sentinel test. --------- Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk>
This commit is contained in:
		
							parent
							
								
									4c2bf5a2fe
								
							
						
					
					
						commit
						f3dbca600f
					
				|  | @ -8,6 +8,7 @@ | ||||||
| 
 | 
 | ||||||
| ## Changes since v7.5.1 | ## Changes since v7.5.1 | ||||||
| 
 | 
 | ||||||
|  | - [#2381](https://github.com/oauth2-proxy/oauth2-proxy/pull/2381) Allow username authentication to Redis cluster (@rossigee) | ||||||
| - [#2345](https://github.com/oauth2-proxy/oauth2-proxy/pull/2345) Log error details when failed loading CSRF cookie (@charvadzo) | - [#2345](https://github.com/oauth2-proxy/oauth2-proxy/pull/2345) Log error details when failed loading CSRF cookie (@charvadzo) | ||||||
| - [#2128](https://github.com/oauth2-proxy/oauth2-proxy/pull/2128) Update dependencies (@vllvll) | - [#2128](https://github.com/oauth2-proxy/oauth2-proxy/pull/2128) Update dependencies (@vllvll) | ||||||
| - [#2269](https://github.com/oauth2-proxy/oauth2-proxy/pull/2269) Added Azure China (and other air gaped cloud) support (@mblaschke) | - [#2269](https://github.com/oauth2-proxy/oauth2-proxy/pull/2269) Added Azure China (and other air gaped cloud) support (@mblaschke) | ||||||
|  |  | ||||||
|  | @ -144,16 +144,17 @@ func NewFlagSet() *pflag.FlagSet { | ||||||
| 	flagSet.String("ready-path", "/ready", "the ready endpoint that can be used for deep health checks") | 	flagSet.String("ready-path", "/ready", "the ready endpoint that can be used for deep health checks") | ||||||
| 	flagSet.String("session-store-type", "cookie", "the session storage provider to use") | 	flagSet.String("session-store-type", "cookie", "the session storage provider to use") | ||||||
| 	flagSet.Bool("session-cookie-minimal", false, "strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only)") | 	flagSet.Bool("session-cookie-minimal", false, "strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only)") | ||||||
| 	flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])") | 	flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://[USER[:PASSWORD]@]HOST[:PORT])") | ||||||
|  | 	flagSet.String("redis-username", "", "Redis username. Applicable for Redis configurations where ACL has been configured. Will override any username set in `--redis-connection-url`") | ||||||
| 	flagSet.String("redis-password", "", "Redis password. Applicable for all Redis configurations. Will override any password set in `--redis-connection-url`") | 	flagSet.String("redis-password", "", "Redis password. Applicable for all Redis configurations. Will override any password set in `--redis-connection-url`") | ||||||
| 	flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature") | 	flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature") | ||||||
| 	flagSet.String("redis-sentinel-password", "", "Redis sentinel password. Used only for sentinel connection; any redis node passwords need to use `--redis-password`") | 	flagSet.String("redis-sentinel-password", "", "Redis sentinel password. Used only for sentinel connection; any redis node passwords need to use `--redis-password`") | ||||||
| 	flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel") | 	flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel") | ||||||
| 	flagSet.String("redis-ca-path", "", "Redis custom CA path") | 	flagSet.String("redis-ca-path", "", "Redis custom CA path") | ||||||
| 	flagSet.Bool("redis-insecure-skip-tls-verify", false, "Use insecure TLS connection to redis") | 	flagSet.Bool("redis-insecure-skip-tls-verify", false, "Use insecure TLS connection to redis") | ||||||
| 	flagSet.StringSlice("redis-sentinel-connection-urls", []string{}, "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel") | 	flagSet.StringSlice("redis-sentinel-connection-urls", []string{}, "List of Redis sentinel connection URLs (eg redis://[USER[:PASSWORD]@]HOST[:PORT]). Used in conjunction with --redis-use-sentinel") | ||||||
| 	flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature") | 	flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature") | ||||||
| 	flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster") | 	flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://[USER[:PASSWORD]@]HOST[:PORT]). Used in conjunction with --redis-use-cluster") | ||||||
| 	flagSet.Int("redis-connection-idle-timeout", 0, "Redis connection idle timeout seconds, if Redis timeout option is non-zero, the --redis-connection-idle-timeout must be less then Redis timeout option") | 	flagSet.Int("redis-connection-idle-timeout", 0, "Redis connection idle timeout seconds, if Redis timeout option is non-zero, the --redis-connection-idle-timeout must be less then Redis timeout option") | ||||||
| 	flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)") | 	flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)") | ||||||
| 	flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints") | 	flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints") | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ type CookieStoreOptions struct { | ||||||
| // RedisStoreOptions contains configuration options for the RedisSessionStore.
 | // RedisStoreOptions contains configuration options for the RedisSessionStore.
 | ||||||
| type RedisStoreOptions struct { | type RedisStoreOptions struct { | ||||||
| 	ConnectionURL          string   `flag:"redis-connection-url" cfg:"redis_connection_url"` | 	ConnectionURL          string   `flag:"redis-connection-url" cfg:"redis_connection_url"` | ||||||
|  | 	Username               string   `flag:"redis-username" cfg:"redis_username"` | ||||||
| 	Password               string   `flag:"redis-password" cfg:"redis_password"` | 	Password               string   `flag:"redis-password" cfg:"redis_password"` | ||||||
| 	UseSentinel            bool     `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"` | 	UseSentinel            bool     `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"` | ||||||
| 	SentinelPassword       string   `flag:"redis-sentinel-password" cfg:"redis_sentinel_password"` | 	SentinelPassword       string   `flag:"redis-sentinel-password" cfg:"redis_sentinel_password"` | ||||||
|  |  | ||||||
|  | @ -100,6 +100,13 @@ func buildSentinelClient(opts options.RedisStoreOptions) (Client, error) { | ||||||
| 		return nil, fmt.Errorf("could not parse redis urls: %v", err) | 		return nil, fmt.Errorf("could not parse redis urls: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if opts.Password != "" { | ||||||
|  | 		opt.Password = opts.Password | ||||||
|  | 	} | ||||||
|  | 	if opts.Username != "" { | ||||||
|  | 		opt.Username = opts.Username | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if err := setupTLSConfig(opts, opt); err != nil { | 	if err := setupTLSConfig(opts, opt); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -108,6 +115,7 @@ func buildSentinelClient(opts options.RedisStoreOptions) (Client, error) { | ||||||
| 		MasterName:       opts.SentinelMasterName, | 		MasterName:       opts.SentinelMasterName, | ||||||
| 		SentinelAddrs:    addrs, | 		SentinelAddrs:    addrs, | ||||||
| 		SentinelPassword: opts.SentinelPassword, | 		SentinelPassword: opts.SentinelPassword, | ||||||
|  | 		Username:         opts.Username, | ||||||
| 		Password:         opts.Password, | 		Password:         opts.Password, | ||||||
| 		TLSConfig:        opt.TLSConfig, | 		TLSConfig:        opt.TLSConfig, | ||||||
| 		ConnMaxIdleTime:  time.Duration(opts.IdleTimeout) * time.Second, | 		ConnMaxIdleTime:  time.Duration(opts.IdleTimeout) * time.Second, | ||||||
|  | @ -122,12 +130,20 @@ func buildClusterClient(opts options.RedisStoreOptions) (Client, error) { | ||||||
| 		return nil, fmt.Errorf("could not parse redis urls: %v", err) | 		return nil, fmt.Errorf("could not parse redis urls: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if opts.Password != "" { | ||||||
|  | 		opt.Password = opts.Password | ||||||
|  | 	} | ||||||
|  | 	if opts.Username != "" { | ||||||
|  | 		opt.Username = opts.Username | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if err := setupTLSConfig(opts, opt); err != nil { | 	if err := setupTLSConfig(opts, opt); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	client := redis.NewClusterClient(&redis.ClusterOptions{ | 	client := redis.NewClusterClient(&redis.ClusterOptions{ | ||||||
| 		Addrs:           addrs, | 		Addrs:           addrs, | ||||||
|  | 		Username:        opts.Username, | ||||||
| 		Password:        opts.Password, | 		Password:        opts.Password, | ||||||
| 		TLSConfig:       opt.TLSConfig, | 		TLSConfig:       opt.TLSConfig, | ||||||
| 		ConnMaxIdleTime: time.Duration(opts.IdleTimeout) * time.Second, | 		ConnMaxIdleTime: time.Duration(opts.IdleTimeout) * time.Second, | ||||||
|  | @ -146,6 +162,9 @@ func buildStandaloneClient(opts options.RedisStoreOptions) (Client, error) { | ||||||
| 	if opts.Password != "" { | 	if opts.Password != "" { | ||||||
| 		opt.Password = opts.Password | 		opt.Password = opts.Password | ||||||
| 	} | 	} | ||||||
|  | 	if opts.Username != "" { | ||||||
|  | 		opt.Username = opts.Username | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := setupTLSConfig(opts, opt); err != nil { | 	if err := setupTLSConfig(opts, opt); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ import ( | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | const redisUsername = "testuser" | ||||||
| const redisPassword = "0123456789abcdefghijklmnopqrstuv" | const redisPassword = "0123456789abcdefghijklmnopqrstuv" | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -231,6 +232,56 @@ var _ = Describe("Redis SessionStore Tests", func() { | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | 	Context("with a redis username and password", func() { | ||||||
|  | 		BeforeEach(func() { | ||||||
|  | 			mr.RequireUserAuth(redisUsername, redisPassword) | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		AfterEach(func() { | ||||||
|  | 			mr.RequireUserAuth("", "") | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		tests.RunSessionStoreTests( | ||||||
|  | 			func(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessionsapi.SessionStore, error) { | ||||||
|  | 				// Set the connection URL
 | ||||||
|  | 				opts.Type = options.RedisSessionStoreType | ||||||
|  | 				opts.Redis.ConnectionURL = "redis://" + redisUsername + "@" + mr.Addr() | ||||||
|  | 				opts.Redis.Password = redisPassword | ||||||
|  | 
 | ||||||
|  | 				// Capture the session store so that we can close the client
 | ||||||
|  | 				var err error | ||||||
|  | 				ss, err = NewRedisSessionStore(opts, cookieOpts) | ||||||
|  | 				return ss, err | ||||||
|  | 			}, | ||||||
|  | 			func(d time.Duration) error { | ||||||
|  | 				mr.FastForward(d) | ||||||
|  | 				return nil | ||||||
|  | 			}, | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 		Context("with cluster", func() { | ||||||
|  | 			tests.RunSessionStoreTests( | ||||||
|  | 				func(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessionsapi.SessionStore, error) { | ||||||
|  | 					clusterAddr := "redis://" + redisUsername + "@" + mr.Addr() | ||||||
|  | 					opts.Type = options.RedisSessionStoreType | ||||||
|  | 					opts.Redis.ClusterConnectionURLs = []string{clusterAddr} | ||||||
|  | 					opts.Redis.UseCluster = true | ||||||
|  | 					opts.Redis.Username = redisUsername | ||||||
|  | 					opts.Redis.Password = redisPassword | ||||||
|  | 
 | ||||||
|  | 					// Capture the session store so that we can close the client
 | ||||||
|  | 					var err error | ||||||
|  | 					ss, err = NewRedisSessionStore(opts, cookieOpts) | ||||||
|  | 					return ss, err | ||||||
|  | 				}, | ||||||
|  | 				func(d time.Duration) error { | ||||||
|  | 					mr.FastForward(d) | ||||||
|  | 					return nil | ||||||
|  | 				}, | ||||||
|  | 			) | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
| 	Context("with TLS connection", func() { | 	Context("with TLS connection", func() { | ||||||
| 		BeforeEach(func() { | 		BeforeEach(func() { | ||||||
| 			mr.Close() | 			mr.Close() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue