1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-05-27 23:08:10 +02:00

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>
This commit is contained in:
Yan Yao 2020-02-06 09:59:12 -08:00 committed by GitHub
parent 3ae261031e
commit 18d20364a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 33 additions and 9 deletions

View File

@ -8,6 +8,7 @@
## Changes since v5.0.0 ## 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) - [#353](https://github.com/pusher/oauth2_proxy/pull/353) Fix login page fragment handling after soft reload on Firefox (@ffdybuster)
# v5.0.0 # v5.0.0

View File

@ -1,6 +1,6 @@
# #
# Autocompletion for oauth2_proxy # Autocompletion for oauth2_proxy
# #
# To install this, copy/move this file to /etc/bash.completion.d/ # 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" # 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}) ) COMPREPLY=( $(compgen -W "google azure facebook github keycloak gitlab linkedin login.gov digitalocean" -- ${cur}) )
return 0 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 return 0
;; ;;
esac esac

View File

@ -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 | | | `-pubjwk-url` | string | JWK pubkey access endpoint: required by login.gov | |
| `-redeem-url` | string | Token redemption endpoint | | | `-redeem-url` | string | Token redemption endpoint | |
| `-redirect-url` | string | the OAuth Redirect URL. ie: `"https://internalapp.yourcompany.com/oauth2/callback"` | | | `-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-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-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-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 | | `-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` | bool | Log requests | true |
| `-request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) | | `-request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) |

View File

@ -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 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` `--redis-use-sentinel=true` flag, as well as configure the flags `--redis-sentinel-master-name`
and `--redis-sentinel-connection-urls` appropriately. 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.

View File

@ -26,6 +26,7 @@ func main() {
jwtIssuers := StringArray{} jwtIssuers := StringArray{}
googleGroups := StringArray{} googleGroups := StringArray{}
redisSentinelConnectionURLs := StringArray{} redisSentinelConnectionURLs := StringArray{}
redisClusterConnectionURLs := StringArray{}
config := flagSet.String("config", "", "path to config file") config := flagSet.String("config", "", "path to config file")
showVersion := flagSet.Bool("version", false, "print version string") showVersion := flagSet.Bool("version", false, "print version string")
@ -96,6 +97,8 @@ func main() {
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.Var(&redisSentinelConnectionURLs, "redis-sentinel-connection-urls", "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel") 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.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") flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")

View File

@ -27,6 +27,8 @@ type RedisStoreOptions struct {
UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"` 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"` 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"` 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"` 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"` RedisInsecureTLS bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"`
} }

View File

@ -33,19 +33,19 @@ type TicketData struct {
type SessionStore struct { type SessionStore struct {
CookieCipher *encryption.Cipher CookieCipher *encryption.Cipher
CookieOptions *options.CookieOptions CookieOptions *options.CookieOptions
Client *redis.Client Cmdable redis.Cmdable
} }
// NewRedisSessionStore initialises a new instance of the SessionStore from // NewRedisSessionStore initialises a new instance of the SessionStore from
// the configuration given // the configuration given
func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) { func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
client, err := newRedisClient(opts.RedisStoreOptions) cmdable, err := newRedisCmdable(opts.RedisStoreOptions)
if err != nil { if err != nil {
return nil, fmt.Errorf("error constructing redis client: %v", err) return nil, fmt.Errorf("error constructing redis client: %v", err)
} }
rs := &SessionStore{ rs := &SessionStore{
Client: client, Cmdable: cmdable,
CookieCipher: opts.Cipher, CookieCipher: opts.Cipher,
CookieOptions: cookieOpts, 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 { if opts.UseSentinel {
client := redis.NewFailoverClient(&redis.FailoverOptions{ client := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: opts.SentinelMasterName, MasterName: opts.SentinelMasterName,
@ -62,6 +66,13 @@ func newRedisClient(opts options.RedisStoreOptions) (*redis.Client, error) {
return client, nil return client, nil
} }
if opts.UseCluster {
client := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: opts.ClusterConnectionURLs,
})
return client, nil
}
opt, err := redis.ParseURL(opts.RedisConnectionURL) opt, err := redis.ParseURL(opts.RedisConnectionURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse redis url: %s", err) 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 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 { if err != nil {
return nil, err 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 // If there's an issue decoding the ticket, ignore it
ticket, _ := decodeTicket(store.CookieOptions.CookieName, val) ticket, _ := decodeTicket(store.CookieOptions.CookieName, val)
if ticket != nil { 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 { if err != nil {
return fmt.Errorf("error clearing cookie from redis: %s", err) 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)) stream.XORKeyStream(ciphertext, []byte(value))
handle := ticket.asHandle(store.CookieOptions.CookieName) 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 { if err != nil {
return "", err return "", err
} }