diff --git a/CHANGELOG.md b/CHANGELOG.md index 299355d3..ca3c8817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ## Changes since v5.0.0 +- [#363](https://github.com/pusher/oauth2_proxy/pull/363) Extension of Redis Session Store to Support Redis Cluster (@yan-dblinf) - [#353](https://github.com/pusher/oauth2_proxy/pull/353) Fix login page fragment handling after soft reload on Firefox (@ffdybuster) # v5.0.0 diff --git a/contrib/oauth2_proxy_autocomplete.sh b/contrib/oauth2_proxy_autocomplete.sh index 0c590f46..0d2f395d 100644 --- a/contrib/oauth2_proxy_autocomplete.sh +++ b/contrib/oauth2_proxy_autocomplete.sh @@ -1,6 +1,6 @@ # # Autocompletion for oauth2_proxy -# +# # To install this, copy/move this file to /etc/bash.completion.d/ # or add a line to your ~/.bashrc | ~/.bash_profile that says ". /path/to/oauth2_proxy/contrib/oauth2_proxy_autocomplete.sh" # @@ -20,7 +20,7 @@ _oauth2_proxy() { COMPREPLY=( $(compgen -W "google azure facebook github keycloak gitlab linkedin login.gov digitalocean" -- ${cur}) ) return 0 ;; - -@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|gitlab-group|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url)) + -@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|gitlab-group|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|redist-cluster-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url)) return 0 ;; esac diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 1c5cd613..e2a7deb9 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -84,9 +84,11 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example | `-pubjwk-url` | string | JWK pubkey access endpoint: required by login.gov | | | `-redeem-url` | string | Token redemption endpoint | | | `-redirect-url` | string | the OAuth Redirect URL. ie: `"https://internalapp.yourcompany.com/oauth2/callback"` | | +| `-redis-cluster-connection-urls` | string \| list | List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with `--redis-use-cluster` | | | `-redis-connection-url` | string | URL of redis server for redis session storage (eg: `redis://HOST[:PORT]`) | | | `-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 (eg `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 | | `-redis-use-sentinel` | bool | Connect to redis via sentinels. Must set `--redis-sentinel-master-name` and `--redis-sentinel-connection-urls` to use this feature | false | | `-request-logging` | bool | Log requests | true | | `-request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) | diff --git a/docs/configuration/sessions.md b/docs/configuration/sessions.md index 305c6c60..0cf9fa6f 100644 --- a/docs/configuration/sessions.md +++ b/docs/configuration/sessions.md @@ -65,3 +65,8 @@ When using the redis store, specify `--session-store-type=redis` as well as the You may also configure the store for Redis Sentinel. In this case, you will want to use the `--redis-use-sentinel=true` flag, as well as configure the flags `--redis-sentinel-master-name` and `--redis-sentinel-connection-urls` appropriately. + +Redis Cluster is available to be the backend store as well. To leverage it, you will need to set the +`--redis-use-cluster=true` flag, and configure the flags `--redis-cluster-connection-urls` appropriately. + +Note that flags `--redis-use-sentinel=true` and `--redis-use-cluster=true` are mutually exclusive. diff --git a/main.go b/main.go index e4e4bc76..b05f1467 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ func main() { jwtIssuers := StringArray{} googleGroups := StringArray{} redisSentinelConnectionURLs := StringArray{} + redisClusterConnectionURLs := StringArray{} config := flagSet.String("config", "", "path to config file") showVersion := flagSet.Bool("version", false, "print version string") @@ -96,6 +97,8 @@ func main() { flagSet.String("redis-ca-path", "", "Redis custom CA path") flagSet.Bool("redis-insecure-skip-tls-verify", false, "Use insecure TLS connection to redis") flagSet.Var(&redisSentinelConnectionURLs, "redis-sentinel-connection-urls", "List of Redis sentinel connection URLs (eg redis://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.Var(&redisClusterConnectionURLs, "redis-cluster-connection-urls", "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster") flagSet.String("logging-filename", "", "File to log requests to, empty for stdout") flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation") diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index dbbb1cba..9a2abd77 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -27,6 +27,8 @@ type RedisStoreOptions struct { UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"` SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"` SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"` + UseCluster bool `flag:"redis-use-cluster" cfg:"redis_use_cluster" env:"OAUTH2_PROXY_REDIS_USE_CLUSTER"` + ClusterConnectionURLs []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls" env:"OAUTH2_PROXY_REDIS_CLUSTER_CONNECTION_URLS"` RedisCAPath string `flag:"redis-ca-path" cfg:"redis_ca_path" env:"OAUTH2_PROXY_REDIS_CA_PATH"` RedisInsecureTLS bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"` } diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index fdeebf6b..0d3d361c 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -33,19 +33,19 @@ type TicketData struct { type SessionStore struct { CookieCipher *encryption.Cipher CookieOptions *options.CookieOptions - Client *redis.Client + Cmdable redis.Cmdable } // NewRedisSessionStore initialises a new instance of the SessionStore from // the configuration given func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) { - client, err := newRedisClient(opts.RedisStoreOptions) + cmdable, err := newRedisCmdable(opts.RedisStoreOptions) if err != nil { return nil, fmt.Errorf("error constructing redis client: %v", err) } rs := &SessionStore{ - Client: client, + Cmdable: cmdable, CookieCipher: opts.Cipher, CookieOptions: cookieOpts, } @@ -53,7 +53,11 @@ func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cook } -func newRedisClient(opts options.RedisStoreOptions) (*redis.Client, error) { +func newRedisCmdable(opts options.RedisStoreOptions) (redis.Cmdable, error) { + if opts.UseSentinel && opts.UseCluster { + return nil, fmt.Errorf("options redis-use-sentinel and redis-use-cluster are mutually exclusive") + } + if opts.UseSentinel { client := redis.NewFailoverClient(&redis.FailoverOptions{ MasterName: opts.SentinelMasterName, @@ -62,6 +66,13 @@ func newRedisClient(opts options.RedisStoreOptions) (*redis.Client, error) { return client, nil } + if opts.UseCluster { + client := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: opts.ClusterConnectionURLs, + }) + return client, nil + } + opt, err := redis.ParseURL(opts.RedisConnectionURL) if err != nil { return nil, fmt.Errorf("unable to parse redis url: %s", err) @@ -152,7 +163,7 @@ func (store *SessionStore) loadSessionFromString(value string) (*sessions.Sessio return nil, err } - result, err := store.Client.Get(ticket.asHandle(store.CookieOptions.CookieName)).Result() + result, err := store.Cmdable.Get(ticket.asHandle(store.CookieOptions.CookieName)).Result() if err != nil { return nil, err } @@ -203,7 +214,7 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro // If there's an issue decoding the ticket, ignore it ticket, _ := decodeTicket(store.CookieOptions.CookieName, val) if ticket != nil { - _, err := store.Client.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result() + _, err := store.Cmdable.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result() if err != nil { return fmt.Errorf("error clearing cookie from redis: %s", err) } @@ -243,7 +254,7 @@ func (store *SessionStore) storeValue(value string, expiration time.Duration, re stream.XORKeyStream(ciphertext, []byte(value)) handle := ticket.asHandle(store.CookieOptions.CookieName) - err = store.Client.Set(handle, ciphertext, expiration).Err() + err = store.Cmdable.Set(handle, ciphertext, expiration).Err() if err != nil { return "", err }