git.iko.soy/typeable/oauth2-proxy
on master help sign in
Extension of Redis Session Store to Support Redis Cluster (#363)

* Extend the redis session store to support redis cluster * rename function newRedisClient to newRedisCmdable * update docs about redis cluster as session store * update autocomplete script with redis cluster options * add check about conflict between option redis-use-sentinel and redis-use-cluster * update change log * Update docs/configuration/sessions.md Co-Authored-By: Joel Speed <Joel.speed@hotmail.co.uk> * Update pkg/sessions/redis/redis_store.go Co-Authored-By: Joel Speed <Joel.speed@hotmail.co.uk> * add the dropped option back Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk>

commit18d20364a8155de1d225205e7c32d38663131c7f authorYan Yao <34801405+yan-dblinf@users.noreply.github.com> dateThu 6 Feb 2020, 09:59 (7 years ago) parent3ae2610
7 files changed+33−9
CHANGELOG.md+1 contrib/oauth2_proxy_autocomplete.sh+2−2 docs/configuration/configuration.md+2 docs/configuration/sessions.md+5 main.go+3 pkg/apis/options/sessions.go+2 pkg/sessions/redis/redis_store.go+18−7
CHANGELOG.md+1
@@ -8,6 +8,7 @@
88
99## Changes since v5.0.0
1010
11+- [#363](https://github.com/pusher/oauth2_proxy/pull/363) Extension of Redis Session Store to Support Redis Cluster (@yan-dblinf)
1112- [#353](https://github.com/pusher/oauth2_proxy/pull/353) Fix login page fragment handling after soft reload on Firefox (@ffdybuster)
1213
1314# v5.0.0
contrib/oauth2_proxy_autocomplete.sh+2−2
@@ -1,6 +1,6 @@
11#
22# Autocompletion for oauth2_proxy
3−#
3+#
44# To install this, copy/move this file to /etc/bash.completion.d/
55# or add a line to your ~/.bashrc | ~/.bash_profile that says ". /path/to/oauth2_proxy/contrib/oauth2_proxy_autocomplete.sh"
66#
@@ -20,7 +20,7 @@ _oauth2_proxy() {
2020 COMPREPLY=( $(compgen -W "google azure facebook github keycloak gitlab linkedin login.gov digitalocean" -- ${cur}) )
2121 return 0
2222 ;;
23− -@(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))
23+ -@(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))
2424 return 0
2525 ;;
2626 esac
docs/configuration/configuration.md+2
@@ -84,9 +84,11 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
8484| `-pubjwk-url` | string | JWK pubkey access endpoint: required by login.gov | |
8585| `-redeem-url` | string | Token redemption endpoint | |
8686| `-redirect-url` | string | the OAuth Redirect URL. ie: `"https://internalapp.yourcompany.com/oauth2/callback"` | |
87+| `-redis-cluster-connection-urls` | string \| list | List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with `--redis-use-cluster` | |
8788| `-redis-connection-url` | string | URL of redis server for redis session storage (eg: `redis://HOST[:PORT]`) | |
8889| `-redis-sentinel-master-name` | string | Redis sentinel master name. Used in conjunction with `--redis-use-sentinel` | |
8990| `-redis-sentinel-connection-urls` | string \| list | List of Redis sentinel connection URLs (eg `redis://HOST[:PORT]`). Used in conjunction with `--redis-use-sentinel` | |
91+| `-redis-use-cluster` | bool | Connect to redis cluster. Must set `--redis-cluster-connection-urls` to use this feature | false |
9092| `-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 |
9193| `-request-logging` | bool | Log requests | true |
9294| `-request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) |
docs/configuration/sessions.md+5
@@ -65,3 +65,8 @@ When using the redis store, specify `--session-store-type=redis` as well as the
6565You may also configure the store for Redis Sentinel. In this case, you will want to use the
6666`--redis-use-sentinel=true` flag, as well as configure the flags `--redis-sentinel-master-name`
6767and `--redis-sentinel-connection-urls` appropriately.
68+
69+Redis Cluster is available to be the backend store as well. To leverage it, you will need to set the
70+`--redis-use-cluster=true` flag, and configure the flags `--redis-cluster-connection-urls` appropriately.
71+
72+Note that flags `--redis-use-sentinel=true` and `--redis-use-cluster=true` are mutually exclusive.
main.go+3
@@ -26,6 +26,7 @@ func main() {
2626 jwtIssuers := StringArray{}
2727 googleGroups := StringArray{}
2828 redisSentinelConnectionURLs := StringArray{}
29+ redisClusterConnectionURLs := StringArray{}
2930
3031 config := flagSet.String("config", "", "path to config file")
3132 showVersion := flagSet.Bool("version", false, "print version string")
@@ -96,6 +97,8 @@ func main() {
9697 flagSet.String("redis-ca-path", "", "Redis custom CA path")
9798 flagSet.Bool("redis-insecure-skip-tls-verify", false, "Use insecure TLS connection to redis")
9899 flagSet.Var(&redisSentinelConnectionURLs, "redis-sentinel-connection-urls", "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel")
100+ flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature")
101+ flagSet.Var(&redisClusterConnectionURLs, "redis-cluster-connection-urls", "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster")
99102
100103 flagSet.String("logging-filename", "", "File to log requests to, empty for stdout")
101104 flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")
pkg/apis/options/sessions.go+2
@@ -27,6 +27,8 @@ type RedisStoreOptions struct {
2727 UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"`
2828 SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"`
2929 SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"`
30+ UseCluster bool `flag:"redis-use-cluster" cfg:"redis_use_cluster" env:"OAUTH2_PROXY_REDIS_USE_CLUSTER"`
31+ ClusterConnectionURLs []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls" env:"OAUTH2_PROXY_REDIS_CLUSTER_CONNECTION_URLS"`
3032 RedisCAPath string `flag:"redis-ca-path" cfg:"redis_ca_path" env:"OAUTH2_PROXY_REDIS_CA_PATH"`
3133 RedisInsecureTLS bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"`
3234}
pkg/sessions/redis/redis_store.go+18−7
@@ -33,19 +33,19 @@ type TicketData struct {
3333type SessionStore struct {
3434 CookieCipher *encryption.Cipher
3535 CookieOptions *options.CookieOptions
36− Client *redis.Client
36+ Cmdable redis.Cmdable
3737}
3838
3939// NewRedisSessionStore initialises a new instance of the SessionStore from
4040// the configuration given
4141func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
42− client, err := newRedisClient(opts.RedisStoreOptions)
42+ cmdable, err := newRedisCmdable(opts.RedisStoreOptions)
4343 if err != nil {
4444 return nil, fmt.Errorf("error constructing redis client: %v", err)
4545 }
4646
4747 rs := &SessionStore{
48− Client: client,
48+ Cmdable: cmdable,
4949 CookieCipher: opts.Cipher,
5050 CookieOptions: cookieOpts,
5151 }
@@ -53,7 +53,11 @@ func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cook
5353
5454}
5555
56−func newRedisClient(opts options.RedisStoreOptions) (*redis.Client, error) {
56+func newRedisCmdable(opts options.RedisStoreOptions) (redis.Cmdable, error) {
57+ if opts.UseSentinel && opts.UseCluster {
58+ return nil, fmt.Errorf("options redis-use-sentinel and redis-use-cluster are mutually exclusive")
59+ }
60+
5761 if opts.UseSentinel {
5862 client := redis.NewFailoverClient(&redis.FailoverOptions{
5963 MasterName: opts.SentinelMasterName,
@@ -62,6 +66,13 @@ func newRedisClient(opts options.RedisStoreOptions) (*redis.Client, error) {
6266 return client, nil
6367 }
6468
69+ if opts.UseCluster {
70+ client := redis.NewClusterClient(&redis.ClusterOptions{
71+ Addrs: opts.ClusterConnectionURLs,
72+ })
73+ return client, nil
74+ }
75+
6576 opt, err := redis.ParseURL(opts.RedisConnectionURL)
6677 if err != nil {
6778 return nil, fmt.Errorf("unable to parse redis url: %s", err)
@@ -152,7 +163,7 @@ func (store *SessionStore) loadSessionFromString(value string) (*sessions.Sessio
152163 return nil, err
153164 }
154165
155− result, err := store.Client.Get(ticket.asHandle(store.CookieOptions.CookieName)).Result()
166+ result, err := store.Cmdable.Get(ticket.asHandle(store.CookieOptions.CookieName)).Result()
156167 if err != nil {
157168 return nil, err
158169 }
@@ -203,7 +214,7 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
203214 // If there's an issue decoding the ticket, ignore it
204215 ticket, _ := decodeTicket(store.CookieOptions.CookieName, val)
205216 if ticket != nil {
206− _, err := store.Client.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result()
217+ _, err := store.Cmdable.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result()
207218 if err != nil {
208219 return fmt.Errorf("error clearing cookie from redis: %s", err)
209220 }
@@ -243,7 +254,7 @@ func (store *SessionStore) storeValue(value string, expiration time.Duration, re
243254 stream.XORKeyStream(ciphertext, []byte(value))
244255
245256 handle := ticket.asHandle(store.CookieOptions.CookieName)
246− err = store.Client.Set(handle, ciphertext, expiration).Err()
257+ err = store.Cmdable.Set(handle, ciphertext, expiration).Err()
247258 if err != nil {
248259 return "", err
249260 }