diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2946bc..5361b2be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ## Changes since v6.0.0 +- [#714](https://github.com/oauth2-proxy/oauth2-proxy/pull/714) Support passwords with Redis session stores (@NickMeves) - [#719](https://github.com/oauth2-proxy/oauth2-proxy/pull/719) Add Gosec fixes to areas that are intermittently flagged on PRs (@NickMeves) - [#718](https://github.com/oauth2-proxy/oauth2-proxy/pull/718) Allow Logging to stdout with separate Error Log Channel - [#690](https://github.com/oauth2-proxy/oauth2-proxy/pull/690) Address GoSec security findings & remediate (@NickMeves) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 92bc9cd6..d51699f5 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -99,6 +99,8 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example | `--redirect-url` | string | the OAuth Redirect URL, e.g. `"https://internalapp.yourcompany.com/oauth2/callback"` | | | `--redis-cluster-connection-urls` | string \| list | List of Redis cluster connection URLs (e.g. `redis://HOST[:PORT]`). Used in conjunction with `--redis-use-cluster` | | | `--redis-connection-url` | string | URL of redis server for redis session storage (e.g. `redis://HOST[:PORT]`) | | +| `--redis-password` | string | Redis password. Applicable for all Redis configurations. Will override any password set in `--redis-connection-url` | | +| `--redis-sentinel-password` | string | Redis sentinel password. Used only for sentinel connection; any redis node passwords need to use `--redis-password` | | | `--redis-sentinel-master-name` | string | Redis sentinel master name. Used in conjunction with `--redis-use-sentinel` | | | `--redis-sentinel-connection-urls` | string \| list | List of Redis sentinel connection URLs (e.g. `redis://HOST[:PORT]`). Used in conjunction with `--redis-use-sentinel` | | | `--redis-use-cluster` | bool | Connect to redis cluster. Must set `--redis-cluster-connection-urls` to use this feature | false | diff --git a/pkg/apis/options/options.go b/pkg/apis/options/options.go index e37dc63b..3ec1baaa 100644 --- a/pkg/apis/options/options.go +++ b/pkg/apis/options/options.go @@ -230,7 +230,9 @@ func NewFlagSet() *pflag.FlagSet { 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-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") diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 70482ee5..9b36d79e 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -23,7 +23,9 @@ type CookieStoreOptions struct { // RedisStoreOptions contains configuration options for the RedisSessionStore. type RedisStoreOptions struct { ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url"` + 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"` SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name"` SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls"` UseCluster bool `flag:"redis-use-cluster" cfg:"redis_use_cluster"` diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 4a2ceb2f..10d99347 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -88,8 +88,10 @@ func buildSentinelClient(opts options.RedisStoreOptions) (Client, error) { return nil, fmt.Errorf("could not parse redis urls: %v", err) } client := redis.NewFailoverClient(&redis.FailoverOptions{ - MasterName: opts.SentinelMasterName, - SentinelAddrs: addrs, + MasterName: opts.SentinelMasterName, + SentinelAddrs: addrs, + SentinelPassword: opts.SentinelPassword, + Password: opts.Password, }) return newClient(client), nil } @@ -101,7 +103,8 @@ func buildClusterClient(opts options.RedisStoreOptions) (Client, error) { return nil, fmt.Errorf("could not parse redis urls: %v", err) } client := redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: addrs, + Addrs: addrs, + Password: opts.Password, }) return newClusterClient(client), nil } @@ -114,6 +117,10 @@ func buildStandaloneClient(opts options.RedisStoreOptions) (Client, error) { return nil, fmt.Errorf("unable to parse redis url: %s", err) } + if opts.Password != "" { + opt.Password = opts.Password + } + if opts.InsecureSkipTLSVerify { opt.TLSConfig.InsecureSkipVerify = true } diff --git a/pkg/sessions/redis/redis_store_test.go b/pkg/sessions/redis/redis_store_test.go index e5adc875..12daaab6 100644 --- a/pkg/sessions/redis/redis_store_test.go +++ b/pkg/sessions/redis/redis_store_test.go @@ -18,6 +18,8 @@ import ( . "github.com/onsi/gomega" ) +const redisPassword = "0123456789abcdefghijklmnopqrstuv" + func TestSessionStore(t *testing.T) { logger.SetOutput(GinkgoWriter) @@ -127,4 +129,87 @@ var _ = Describe("Redis SessionStore Tests", func() { }, ) }) + + Context("with a redis password", func() { + BeforeEach(func() { + mr.RequireAuth(redisPassword) + }) + + AfterEach(func() { + mr.RequireAuth("") + }) + + tests.RunSessionStoreTests( + func(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessionsapi.SessionStore, error) { + // Set the connection URL + opts.Type = options.RedisSessionStoreType + opts.Redis.ConnectionURL = "redis://" + 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 sentinel", func() { + var ms *minisentinel.Sentinel + + BeforeEach(func() { + ms = minisentinel.NewSentinel(mr) + Expect(ms.Start()).To(Succeed()) + }) + + AfterEach(func() { + ms.Close() + }) + + tests.RunSessionStoreTests( + func(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessionsapi.SessionStore, error) { + // Set the sentinel connection URL + sentinelAddr := "redis://" + ms.Addr() + opts.Type = options.RedisSessionStoreType + opts.Redis.SentinelConnectionURLs = []string{sentinelAddr} + opts.Redis.UseSentinel = true + opts.Redis.SentinelMasterName = ms.MasterInfo().Name + 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://" + mr.Addr() + opts.Type = options.RedisSessionStoreType + opts.Redis.ClusterConnectionURLs = []string{clusterAddr} + opts.Redis.UseCluster = true + 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 + }, + ) + }) + }) })