diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d04ebf..2dd18d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ## 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) - [#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) diff --git a/pkg/apis/options/options.go b/pkg/apis/options/options.go index 77ba341..15a2df7 100644 --- a/pkg/apis/options/options.go +++ b/pkg/apis/options/options.go @@ -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("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.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.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-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel") flagSet.String("redis-ca-path", "", "Redis custom CA path") 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.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.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)") flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints") diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 188f0e4..c90c0ac 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -23,6 +23,7 @@ type CookieStoreOptions struct { // RedisStoreOptions contains configuration options for the RedisSessionStore. type RedisStoreOptions struct { 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"` UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"` SentinelPassword string `flag:"redis-sentinel-password" cfg:"redis_sentinel_password"` diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index b3b3ab1..e41a1e1 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -100,6 +100,13 @@ func buildSentinelClient(opts options.RedisStoreOptions) (Client, error) { 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 { return nil, err } @@ -108,6 +115,7 @@ func buildSentinelClient(opts options.RedisStoreOptions) (Client, error) { MasterName: opts.SentinelMasterName, SentinelAddrs: addrs, SentinelPassword: opts.SentinelPassword, + Username: opts.Username, Password: opts.Password, TLSConfig: opt.TLSConfig, 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) } + if opts.Password != "" { + opt.Password = opts.Password + } + if opts.Username != "" { + opt.Username = opts.Username + } + if err := setupTLSConfig(opts, opt); err != nil { return nil, err } client := redis.NewClusterClient(&redis.ClusterOptions{ Addrs: addrs, + Username: opts.Username, Password: opts.Password, TLSConfig: opt.TLSConfig, ConnMaxIdleTime: time.Duration(opts.IdleTimeout) * time.Second, @@ -146,6 +162,9 @@ func buildStandaloneClient(opts options.RedisStoreOptions) (Client, error) { if opts.Password != "" { opt.Password = opts.Password } + if opts.Username != "" { + opt.Username = opts.Username + } if err := setupTLSConfig(opts, opt); err != nil { return nil, err diff --git a/pkg/sessions/redis/redis_store_test.go b/pkg/sessions/redis/redis_store_test.go index 9357b80..ddc54fe 100644 --- a/pkg/sessions/redis/redis_store_test.go +++ b/pkg/sessions/redis/redis_store_test.go @@ -18,6 +18,7 @@ import ( . "github.com/onsi/gomega" ) +const redisUsername = "testuser" const redisPassword = "0123456789abcdefghijklmnopqrstuv" 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() { BeforeEach(func() { mr.Close()