mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2026-10-18 22:41:04 +02:00
feat: readiness check (#1839)
* feat: readiness check * fix: no need for query param * docs: add a note * chore: move the readyness check to its own endpoint * docs(cr): add godoc Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk>
This commit is contained in:
co-authored by
Joel Speed
parent
8b77c97009
commit
f753ec1ca5
@@ -1,6 +1,7 @@
|
||||
package cookie
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -82,6 +83,12 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyConnection always return no-error, as there's no connection
|
||||
// in this store
|
||||
func (s *SessionStore) VerifyConnection(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// cookieForSession serializes a session state for storage in a cookie
|
||||
func (s *SessionStore) cookieForSession(ss *sessions.SessionState) ([]byte, error) {
|
||||
if s.Minimal && (ss.AccessToken != "" || ss.IDToken != "" || ss.RefreshToken != "") {
|
||||
|
||||
@@ -15,4 +15,5 @@ type Store interface {
|
||||
Load(context.Context, string) ([]byte, error)
|
||||
Clear(context.Context, string) error
|
||||
Lock(key string) sessions.Lock
|
||||
VerifyConnection(context.Context) error
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -90,3 +91,8 @@ func (m *Manager) Clear(rw http.ResponseWriter, req *http.Request) error {
|
||||
return m.Store.Clear(req.Context(), key)
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyConnection validates the underlying store is ready and connected
|
||||
func (m *Manager) VerifyConnection(ctx context.Context) error {
|
||||
return m.Store.VerifyConnection(ctx)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type Client interface {
|
||||
Lock(key string) sessions.Lock
|
||||
Set(ctx context.Context, key string, value []byte, expiration time.Duration) error
|
||||
Del(ctx context.Context, key string) error
|
||||
Ping(ctx context.Context) error
|
||||
}
|
||||
|
||||
var _ Client = (*client)(nil)
|
||||
@@ -44,6 +45,10 @@ func (c *client) Lock(key string) sessions.Lock {
|
||||
return NewLock(c.Client, key)
|
||||
}
|
||||
|
||||
func (c *client) Ping(ctx context.Context) error {
|
||||
return c.Client.Ping(ctx).Err()
|
||||
}
|
||||
|
||||
var _ Client = (*clusterClient)(nil)
|
||||
|
||||
type clusterClient struct {
|
||||
@@ -71,3 +76,7 @@ func (c *clusterClient) Del(ctx context.Context, key string) error {
|
||||
func (c *clusterClient) Lock(key string) sessions.Lock {
|
||||
return NewLock(c.ClusterClient, key)
|
||||
}
|
||||
|
||||
func (c *clusterClient) Ping(ctx context.Context) error {
|
||||
return c.ClusterClient.Ping(ctx).Err()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/redis"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Redis Client Tests", func() {
|
||||
Context("with basic client", func() {
|
||||
RunClientTests(func(mr *miniredis.Miniredis) options.RedisStoreOptions {
|
||||
return options.RedisStoreOptions{
|
||||
ConnectionURL: "redis://" + mr.Addr(),
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Context("with cluster client", func() {
|
||||
RunClientTests(func(mr *miniredis.Miniredis) options.RedisStoreOptions {
|
||||
return options.RedisStoreOptions{
|
||||
ClusterConnectionURLs: []string{"redis://" + mr.Addr()},
|
||||
UseCluster: true,
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
type getOptsFunc func(mr *miniredis.Miniredis) options.RedisStoreOptions
|
||||
|
||||
func RunClientTests(getOptsFunc getOptsFunc) {
|
||||
var mr *miniredis.Miniredis
|
||||
var client redis.Client
|
||||
var err error
|
||||
var key string
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
mr, err = miniredis.Run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
client, err = redis.NewRedisClient(getOptsFunc(mr))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
nonce, err := encryption.Nonce(32)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
key = base64.RawURLEncoding.EncodeToString(nonce)
|
||||
|
||||
ctx = context.Background()
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
if mr != nil {
|
||||
mr.Close()
|
||||
mr = nil
|
||||
}
|
||||
})
|
||||
|
||||
Context("when Get is called", func() {
|
||||
expectedValue := []byte("value")
|
||||
|
||||
BeforeEach(func() {
|
||||
client.Set(context.Background(), key, expectedValue, time.Duration(1*time.Minute))
|
||||
})
|
||||
|
||||
It("returns the saved value", func() {
|
||||
value, err := client.Get(ctx, key)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(value).To(Equal(value))
|
||||
})
|
||||
|
||||
It("does not return expired values", func() {
|
||||
mr.FastForward(5 * time.Minute)
|
||||
|
||||
_, err = client.Get(ctx, key)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns an error if value does not exist", func() {
|
||||
_, err = client.Get(ctx, "does-not-exists")
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("using Lock", func() {
|
||||
It("maintains the lock", func() {
|
||||
lock := client.Lock(key)
|
||||
|
||||
err = lock.Obtain(ctx, 1*time.Minute)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
isLocked, err := lock.Peek(ctx)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(isLocked).To(BeTrue())
|
||||
|
||||
err = lock.Release(ctx)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("reflects non-locked instance", func() {
|
||||
lock := client.Lock(key)
|
||||
|
||||
isLocked, err := lock.Peek(ctx)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(isLocked).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when Set is called", func() {
|
||||
expectedValue := []byte("value")
|
||||
|
||||
It("sets the expected value", func() {
|
||||
err = client.Set(ctx, key, expectedValue, 1*time.Minute)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
value, err := client.Get(ctx, key)
|
||||
Expect(value).To(Equal(expectedValue))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when Del is called", func() {
|
||||
It("does not return an error when key exists", func() {
|
||||
err = client.Set(ctx, key, []byte("dummy"), 1*time.Minute)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = client.Del(ctx, key)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = client.Get(ctx, key)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when Ping is called", func() {
|
||||
Context("when redis is up", func() {
|
||||
It("does not return an error", func() {
|
||||
err = client.Ping(ctx)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when redis is down", func() {
|
||||
It("returns an error", func() {
|
||||
mr.Close()
|
||||
mr = nil
|
||||
|
||||
err = client.Ping(ctx)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -70,6 +70,12 @@ func (store *SessionStore) Lock(key string) sessions.Lock {
|
||||
return store.Client.Lock(key)
|
||||
}
|
||||
|
||||
// VerifyConnection verifies the redis connection is valid and the
|
||||
// server is responsive
|
||||
func (store *SessionStore) VerifyConnection(ctx context.Context) error {
|
||||
return store.Client.Ping(ctx)
|
||||
}
|
||||
|
||||
// NewRedisClient makes a redis.Client (either standalone, sentinel aware, or
|
||||
// redis cluster)
|
||||
func NewRedisClient(opts options.RedisStoreOptions) (Client, error) {
|
||||
@@ -205,3 +211,5 @@ func parseRedisURLs(urls []string) ([]string, *redis.Options, error) {
|
||||
}
|
||||
return addrs, redisOptions, nil
|
||||
}
|
||||
|
||||
var _ persistence.Store = (*SessionStore)(nil)
|
||||
|
||||
@@ -2,20 +2,15 @@ package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Bose/minisentinel"
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/go-redis/redis/v9"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/persistence"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/tests"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
|
||||
@@ -25,28 +20,6 @@ import (
|
||||
|
||||
const redisPassword = "0123456789abcdefghijklmnopqrstuv"
|
||||
|
||||
// wrappedRedisLogger wraps a logger so that we can coerce the logger to
|
||||
// fit the expected signature for go-redis logging
|
||||
type wrappedRedisLogger struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
func (l *wrappedRedisLogger) Printf(_ context.Context, format string, v ...interface{}) {
|
||||
l.Logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
func TestSessionStore(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
|
||||
redisLogger := &wrappedRedisLogger{Logger: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile)}
|
||||
redisLogger.SetOutput(GinkgoWriter)
|
||||
redis.SetLogger(redisLogger)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Redis SessionStore")
|
||||
}
|
||||
|
||||
var (
|
||||
cert tls.Certificate
|
||||
caPath string
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-redis/redis/v9"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// wrappedRedisLogger wraps a logger so that we can coerce the logger to
|
||||
// fit the expected signature for go-redis logging
|
||||
type wrappedRedisLogger struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
func (l *wrappedRedisLogger) Printf(_ context.Context, format string, v ...interface{}) {
|
||||
l.Logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
func TestRedis(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
logger.SetErrOutput(GinkgoWriter)
|
||||
|
||||
redisLogger := &wrappedRedisLogger{Logger: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile)}
|
||||
redisLogger.SetOutput(GinkgoWriter)
|
||||
redis.SetLogger(redisLogger)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Redis")
|
||||
}
|
||||
@@ -65,6 +65,10 @@ func (s *MockStore) Lock(key string) sessions.Lock {
|
||||
return lock
|
||||
}
|
||||
|
||||
func (s *MockStore) VerifyConnection(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FastForward simulates the flow of time to test expirations
|
||||
func (s *MockStore) FastForward(duration time.Duration) {
|
||||
for _, mockLock := range s.lockCache {
|
||||
|
||||
@@ -485,6 +485,12 @@ func SessionStoreInterfaceTests(in *testInput) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("when VerifyConnection is called", func() {
|
||||
It("should return without an error", func() {
|
||||
Expect(in.ss().VerifyConnection(in.request.Context())).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func LoadSessionTests(in *testInput) {
|
||||
|
||||
Reference in New Issue
Block a user