Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update redis lib, use redis-uri spec with db selection possibility #359

Merged
merged 1 commit into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/content/configuration/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ weight: 2
| --cors-credentials | credentials access control header (Access-Control-Allow-Credentials) | false | PROXY_CORS_CREDENTIALS
| --cors-max-age value | max age applied to cors headers (Access-Control-Max-Age) | 0s | PROXY_CORS_MAX_AGE
| --hostnames value | list of hostnames the service will respond to | |
| --store-url value | url for the storage subsystem, e.g redis://127.0.0.1:6379, file:///etc/tokens.file | | PROXY_STORE_URL
| --store-url value | url for the storage subsystem, e.g redis://user:secret@localhost:6379/0?protocol=3, only supported is redis usig redis uri spec | | PROXY_STORE_URL
| --encryption-key value | encryption key used to encryption the session state | | PROXY_ENCRYPTION_KEY
| --no-proxy value | do not proxy requests to upstream, useful for forward-auth usage (with nginx, traefik) | | PROXY_NO_PROXY
| --no-redirects | do not have back redirects when no authentication is present, 401 them | false | PROXY_NO_REDIRECTS
Expand Down
6 changes: 2 additions & 4 deletions docs/content/userguide/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -802,10 +802,8 @@ refresh the access token for you. The tokens themselves are kept either
as an encrypted (`--encryption-key=KEY`) cookie **(cookie name:
kc-state).** or a store **(still requires encryption key)**.

At present the only store options supported are
[Redis](https://github.com/antirez/redis) and

To enable a local Redis store use `redis://[USER:PASSWORD@]HOST:PORT`.
To enable a local Redis store use `redis://user:secret@localhost:6379/0?protocol=3`.
See [redis-uri](https://github.com/redis/redis-specifications/blob/master/uri/redis.txt) specification
In both cases, the refresh token is encrypted before being placed into
the store.

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/garyburd/redigo v1.6.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
Expand Down Expand Up @@ -76,6 +77,7 @@ require (
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/redis/go-redis/v9 v9.2.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
Expand Down Expand Up @@ -201,6 +203,8 @@ github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuR
github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg=
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rs/cors v1.7.1-0.20201213214713-f9bce55a4e61 h1:ShPDRmLRZM1dY///S8mO6XIvcf8zzJ5bTnEsPbHCGxY=
Expand Down
5 changes: 3 additions & 2 deletions pkg/keycloak/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/gogatekeeper/gatekeeper/pkg/config/core"
"github.com/gogatekeeper/gatekeeper/pkg/constant"
"github.com/gogatekeeper/gatekeeper/pkg/utils"
redis "github.com/redis/go-redis/v9"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -237,7 +238,7 @@ type Config struct {
Hostnames []string `json:"hostnames" usage:"list of hostnames the service will respond to" yaml:"hostnames"`

// Store is a url for a store resource, used to hold the refresh tokens
StoreURL string `env:"STORE_URL" json:"store-url" usage:"url for the storage subsystem, e.g redis://127.0.0.1:6379, file:///etc/tokens.file" yaml:"store-url"`
StoreURL string `env:"STORE_URL" json:"store-url" usage:"url for the storage subsystem, e.g redis://user:secret@localhost:6379/0?protocol=3, only supported is redis usig redis uri spec" yaml:"store-url"`
// EncryptionKey is the encryption key used to encrypt the refresh token
EncryptionKey string `env:"ENCRYPTION_KEY" json:"encryption-key" usage:"encryption key used to encryption the session state" yaml:"encryption-key"`

Expand Down Expand Up @@ -830,7 +831,7 @@ func (r *Config) isSecureCookieValid() error {

func (r *Config) isStoreURLValid() error {
if r.StoreURL != "" {
if _, err := url.ParseRequestURI(r.StoreURL); err != nil {
if _, err := redis.ParseURL(r.StoreURL); err != nil {
return fmt.Errorf("the store url is invalid, error: %s", err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/keycloak/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1741,7 +1741,7 @@ func TestIsStoreURLValid(t *testing.T) {
{
Name: "ValidIsStoreURL",
Config: &Config{
StoreURL: "boltdb:////tmp/test.boltdb",
StoreURL: "redis://user:secret@localhost:6379/4?protocol=3",
},
Valid: true,
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/keycloak/proxy/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func (r *OauthProxy) oauthCallbackHandler(writer http.ResponseWriter, req *http.

switch {
case r.useStore():
if err = r.StoreRefreshToken(rawAccessToken, encrypted, oidcTokensCookiesExp); err != nil {
if err = r.StoreRefreshToken(req.Context(), rawAccessToken, encrypted, oidcTokensCookiesExp); err != nil {
scope.Logger.Error(
"failed to save the refresh token in the store",
zap.Error(err),
Expand Down Expand Up @@ -476,7 +476,7 @@ func (r *OauthProxy) loginHandler(writer http.ResponseWriter, req *http.Request)

switch r.useStore() {
case true:
if err = r.StoreRefreshToken(token.AccessToken, refreshToken, expiration); err != nil {
if err = r.StoreRefreshToken(req.Context(), token.AccessToken, refreshToken, expiration); err != nil {
scope.Logger.Warn(
"failed to save the refresh token in the store",
zap.Error(err),
Expand Down Expand Up @@ -628,7 +628,7 @@ func (r *OauthProxy) logoutHandler(writer http.ResponseWriter, req *http.Request
// step: check if the user has a state session and if so revoke it
if r.useStore() {
go func() {
if err = r.DeleteRefreshToken(user.RawToken); err != nil {
if err = r.DeleteRefreshToken(req.Context(), user.RawToken); err != nil {
scope.Logger.Error(
"unable to remove the refresh token from store",
zap.Error(err),
Expand Down Expand Up @@ -824,7 +824,7 @@ func (r *OauthProxy) retrieveRefreshToken(req *http.Request, user *UserContext)

switch r.useStore() {
case true:
token, err = r.GetRefreshToken(user.RawToken)
token, err = r.GetRefreshToken(req.Context(), user.RawToken)
default:
token, err = utils.GetRefreshTokenFromCookie(req, r.Config.CookieRefreshName)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/keycloak/proxy/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,11 +425,11 @@ func (r *OauthProxy) authenticationMiddleware() func(http.Handler) http.Handler

if r.useStore() {
go func(old, newToken string, encrypted string) {
if err := r.DeleteRefreshToken(old); err != nil {
if err := r.DeleteRefreshToken(req.Context(), old); err != nil {
scope.Logger.Error("failed to remove old token", zap.Error(err))
}

if err := r.StoreRefreshToken(newToken, encrypted, refreshExpiresIn); err != nil {
if err := r.StoreRefreshToken(req.Context(), newToken, encrypted, refreshExpiresIn); err != nil {
scope.Logger.Error("failed to store refresh token", zap.Error(err))
return
}
Expand Down
13 changes: 7 additions & 6 deletions pkg/keycloak/proxy/stores.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package proxy

import (
"context"
"time"

"github.com/gogatekeeper/gatekeeper/pkg/apperrors"
Expand All @@ -30,14 +31,14 @@ func (r *OauthProxy) useStore() bool {
}

// StoreRefreshToken the token to the store
func (r *OauthProxy) StoreRefreshToken(token string, value string, expiration time.Duration) error {
return r.Store.Set(utils.GetHashKey(token), value, expiration)
func (r *OauthProxy) StoreRefreshToken(ctx context.Context, token string, value string, expiration time.Duration) error {
return r.Store.Set(ctx, utils.GetHashKey(token), value, expiration)
}

// Get retrieves a token from the store, the key we are using here is the access token
func (r *OauthProxy) GetRefreshToken(token string) (string, error) {
func (r *OauthProxy) GetRefreshToken(ctx context.Context, token string) (string, error) {
// step: the key is the access token
val, err := r.Store.Get(utils.GetHashKey(token))
val, err := r.Store.Get(ctx, utils.GetHashKey(token))

if err != nil {
return val, err
Expand All @@ -50,8 +51,8 @@ func (r *OauthProxy) GetRefreshToken(token string) (string, error) {
}

// DeleteRefreshToken removes a key from the store
func (r *OauthProxy) DeleteRefreshToken(token string) error {
if err := r.Store.Delete(utils.GetHashKey(token)); err != nil {
func (r *OauthProxy) DeleteRefreshToken(ctx context.Context, token string) error {
if err := r.Store.Delete(ctx, utils.GetHashKey(token)); err != nil {
r.Log.Error("unable to delete token", zap.Error(err))

return err
Expand Down
11 changes: 6 additions & 5 deletions pkg/storage/storage.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package storage

import (
"context"
"fmt"
"net/url"
"time"
Expand All @@ -10,13 +11,13 @@ import (
// the default practice of a encrypted cookie
type Storage interface {
// Set the token to the store
Set(string, string, time.Duration) error
Set(context.Context, string, string, time.Duration) error
// Get retrieves a token from the store
Get(string) (string, error)
Get(context.Context, string) (string, error)
// Exists checks if key exists in store
Exists(string) (bool, error)
Exists(context.Context, string) (bool, error)
// Delete removes a key from the store
Delete(string) error
Delete(context.Context, string) error
// Close is used to close off any resources
Close() error
}
Expand All @@ -34,7 +35,7 @@ func CreateStorage(location string) (Storage, error) {

switch uri.Scheme {
case "redis":
store, err = newRedisStore(uri)
store, err = newRedisStore(location)
default:
return nil, fmt.Errorf("unsupport store: %s", uri.Scheme)
}
Expand Down
48 changes: 19 additions & 29 deletions pkg/storage/store_redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ limitations under the License.
package storage

import (
"net/url"
"context"
"time"

redis "gopkg.in/redis.v4"
redis "github.com/redis/go-redis/v9"
)

var _ Storage = (*RedisStore)(nil)
Expand All @@ -29,47 +29,37 @@ type RedisStore struct {
}

// newRedisStore creates a new redis store
func newRedisStore(location *url.URL) (Storage, error) {
// step: get any password
password := ""
if location.User != nil {
password, _ = location.User.Password()
func newRedisStore(url string) (Storage, error) {
opts, err := redis.ParseURL(url)
if err != nil {
return nil, err
}

// step: parse the url notation
client := redis.NewClient(&redis.Options{
Addr: location.Host,
DB: 0,
Password: password,
})

return RedisStore{
Client: client,
}, nil
client := redis.NewClient(opts)
return RedisStore{Client: client}, nil
}

// Set adds a token to the store
func (r RedisStore) Set(key, value string, expiration time.Duration) error {
if err := r.Client.Set(key, value, expiration); err.Err() != nil {
func (r RedisStore) Set(ctx context.Context, key, value string, expiration time.Duration) error {
if err := r.Client.Set(ctx, key, value, expiration); err.Err() != nil {
return err.Err()
}

return nil
}

// Checks if key exists in store
func (r RedisStore) Exists(key string) (bool, error) {
result := r.Client.Exists(key)
if result.Err() != nil {
return false, result.Err()
func (r RedisStore) Exists(ctx context.Context, key string) (bool, error) {
val, err := r.Client.Exists(ctx, key).Result()
if err != nil {
return false, err
}

return result.Val(), nil
return val > 0, nil
}

// Get retrieves a token from the store
func (r RedisStore) Get(key string) (string, error) {
result := r.Client.Get(key)
func (r RedisStore) Get(ctx context.Context, key string) (string, error) {
result := r.Client.Get(ctx, key)
if result.Err() != nil {
return "", result.Err()
}
Expand All @@ -78,8 +68,8 @@ func (r RedisStore) Get(key string) (string, error) {
}

// Delete remove the key
func (r RedisStore) Delete(key string) error {
return r.Client.Del(key).Err()
func (r RedisStore) Delete(ctx context.Context, key string) error {
return r.Client.Del(ctx, key).Err()
}

// Close closes of any open resources
Expand Down
2 changes: 1 addition & 1 deletion pkg/testsuite/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1500,7 +1500,7 @@ func TestAccessTokenEncryption(t *testing.T) {
conf.Verbose = true
conf.EnableLogging = true
conf.EncryptionKey = testEncryptionKey
conf.StoreURL = fmt.Sprintf("redis://%s", redisServer.Addr())
conf.StoreURL = fmt.Sprintf("redis://%s/2", redisServer.Addr())
},
ExecutionSettings: []fakeRequest{
{
Expand Down
Loading