diff --git a/Makefile b/Makefile index 69501878c..cba0c776f 100644 --- a/Makefile +++ b/Makefile @@ -327,6 +327,7 @@ test-e2e-contribs: $(CONTRIBS_E2E) ## Run e2e tests for external integrations .PHONY: test-e2e-contrib-kcp test-e2e-contrib-kcp: $(DEX_BINARY) $(CONTRIBS_E2E): + rm -rf .kcp mkdir .kcp $(MAKE) run-kcp &>.kcp/kcp.log & KCP_PID=$$!; \ trap 'kill -TERM $$KCP_PID; rm -rf .kcp' TERM INT EXIT && \ diff --git a/backend/auth/handler.go b/backend/auth/handler.go index f2a282edf..95ac395c3 100644 --- a/backend/auth/handler.go +++ b/backend/auth/handler.go @@ -120,7 +120,7 @@ func (ah *AuthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request) { ah.respondWithError(w, authReq.ClientType, "failed to generate PKCE", http.StatusInternalServerError) return } - if err := ah.sessionStore.SavePKCEVerifier(authReq.SessionID, verifier); err != nil { + if err := ah.sessionStore.SavePKCEVerifier(r.Context(), authReq.SessionID, verifier); err != nil { logger.Error(err, "failed to store PKCE verifier") ah.respondWithError(w, authReq.ClientType, "failed to store PKCE verifier", http.StatusInternalServerError) return @@ -205,7 +205,7 @@ func (ah *AuthHandler) HandleCallback(w http.ResponseWriter, r *http.Request) { ctx = context.WithValue(ctx, oauth2.HTTPClient, client) } - verifier, err := ah.sessionStore.LoadAndDeletePKCEVerifier(authCode.SessionID) + verifier, err := ah.sessionStore.LoadAndDeletePKCEVerifier(r.Context(), authCode.SessionID) if err != nil || verifier == "" { logger.Error(err, "PKCE verifier not found for session; cannot exchange code", "sessionID", authCode.SessionID) msg := "PKCE verifier not found. If you run multiple backend instances, use a shared session store (e.g. Redis) so the instance handling the callback can read the verifier stored at authorize time." @@ -228,7 +228,7 @@ func (ah *AuthHandler) HandleCallback(w http.ResponseWriter, r *http.Request) { return } // Set session expiration and store in middleware - err = ah.sessionStore.Save(sessionState) + err = ah.sessionStore.Save(r.Context(), sessionState) if err != nil { logger.Error(err, "failed to save session state") ah.respondWithError(w, authCode.ClientType, "failed to save session state", http.StatusInternalServerError) diff --git a/backend/auth/middleware.go b/backend/auth/middleware.go index 2274db519..cb64e5cf9 100644 --- a/backend/auth/middleware.go +++ b/backend/auth/middleware.go @@ -186,7 +186,7 @@ func (am *AuthMiddleware) verifyState(next http.Handler) http.Handler { return } - if !am.isValidSession(state.SessionID) { + if !am.isValidSession(r.Context(), state.SessionID) { logger.V(2).Info("Session expired or invalid", "sessionID", state.SessionID) writeErrorResponse(w, http.StatusUnauthorized, kubebindv1alpha2.ErrorCodeAuthenticationFailed, "Authentication required", "Session has expired or is invalid") return @@ -201,8 +201,8 @@ func (am *AuthMiddleware) verifyState(next http.Handler) http.Handler { } // isValidSession checks if a session ID exists and hasn't expired -func (am *AuthMiddleware) isValidSession(sessionID string) bool { - sessionInfo, err := am.sessionStore.Load(sessionID) +func (am *AuthMiddleware) isValidSession(ctx context.Context, sessionID string) bool { + sessionInfo, err := am.sessionStore.Load(ctx, sessionID) if err != nil { return false } diff --git a/backend/http/handler.go b/backend/http/handler.go index 990755036..2df6394f4 100644 --- a/backend/http/handler.go +++ b/backend/http/handler.go @@ -112,6 +112,7 @@ func NewHandler( mgr *kubernetes.Manager, frontend string, tokenExpiry time.Duration, + sessionStore session.Store, ) (*handler, error) { // Create JWT service for CLI authentication jwtService, err := auth.NewJWTService("kube-bind-backend") @@ -119,8 +120,6 @@ func NewHandler( return nil, fmt.Errorf("failed to create JWT service: %w", err) } - sessionStore := session.NewInMemoryStore() - // Create auth middleware for request authentication authMiddleware := auth.NewAuthMiddleware(jwtService, cookieSigningKey, cookieEncryptionKey, mgr, sessionStore) diff --git a/backend/options/options.go b/backend/options/options.go index aa9551790..a4e14565f 100644 --- a/backend/options/options.go +++ b/backend/options/options.go @@ -33,10 +33,11 @@ import ( ) type Options struct { - Logs *logs.Options - OIDC *OIDC - Cookie *Cookie - Serve *Serve + Logs *logs.Options + OIDC *OIDC + Cookie *Cookie + Serve *Serve + Session *Session ProviderKcp *providerkcp.Options @@ -81,10 +82,11 @@ type ExtraOptions struct { } type completedOptions struct { - Logs *logs.Options - OIDC *OIDC - Cookie *Cookie - Serve *Serve + Logs *logs.Options + OIDC *OIDC + Cookie *Cookie + Serve *Serve + Session *Session // Provider specific options ProviderKcp *providerkcp.CompletedOptions @@ -106,6 +108,7 @@ func NewOptions() *Options { OIDC: NewOIDC(), Cookie: NewCookie(), Serve: NewServe(), + Session: NewSession(), ProviderKcp: providerkcp.NewOptions(), ExtraOptions: ExtraOptions{ @@ -155,6 +158,7 @@ func (options *Options) AddFlags(fs *pflag.FlagSet) { options.OIDC.AddFlags(fs) options.Cookie.AddFlags(fs) options.Serve.AddFlags(fs) + options.Session.AddFlags(fs) options.ProviderKcp.AddFlags(fs) fs.StringVar(&options.KubeConfig, "kubeconfig", options.KubeConfig, "path to a kubeconfig. Only required if out-of-cluster") @@ -207,6 +211,9 @@ func (options *Options) Complete() (*CompletedOptions, error) { if err := options.Cookie.Complete(); err != nil { return nil, err } + if err := options.Session.Complete(); err != nil { + return nil, err + } } // normalize the scope and the isolation @@ -243,6 +250,7 @@ func (options *Options) Complete() (*CompletedOptions, error) { OIDC: options.OIDC, Cookie: options.Cookie, Serve: options.Serve, + Session: options.Session, ExtraOptions: options.ExtraOptions, }, } @@ -276,6 +284,9 @@ func (options *CompletedOptions) Validate() error { if err := options.Cookie.Validate(); err != nil { return err } + if err := options.Session.Validate(); err != nil { + return err + } } if options.ConsumerScope != string(kubebindv1alpha2.NamespacedScope) && options.ConsumerScope != string(kubebindv1alpha2.ClusterScope) { diff --git a/backend/options/session.go b/backend/options/session.go new file mode 100644 index 000000000..de8603707 --- /dev/null +++ b/backend/options/session.go @@ -0,0 +1,45 @@ +/* +Copyright 2026 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "github.com/spf13/pflag" + + redisoptions "github.com/kube-bind/kube-bind/backend/options/session/redis" +) + +type Session struct { + Redis *redisoptions.Options +} + +func NewSession() *Session { + return &Session{ + Redis: redisoptions.NewOptions(), + } +} + +func (options *Session) AddFlags(fs *pflag.FlagSet) { + options.Redis.AddFlags(fs) +} + +func (options *Session) Complete() error { + return options.Redis.Complete() +} + +func (options *Session) Validate() error { + return options.Redis.Validate() +} diff --git a/backend/options/session/redis/options.go b/backend/options/session/redis/options.go new file mode 100644 index 000000000..1edc53b93 --- /dev/null +++ b/backend/options/session/redis/options.go @@ -0,0 +1,51 @@ +/* +Copyright 2026 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package redis + +import ( + "fmt" + "os" + + "github.com/spf13/pflag" +) + +type Options struct { + Address string + Password string +} + +func NewOptions() *Options { + return &Options{} +} + +func (options *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&options.Address, "redis-addr", options.Address, "The redis address (e.g. localhost:6379) to connect to if storing sessions in Redis. If empty, the in-memory provider is used.") +} + +func (options *Options) Complete() error { + if pwd := os.Getenv("REDIS_PASSWORD"); pwd != "" { + options.Password = pwd + } + return nil +} + +func (options *Options) Validate() error { + if options.Password != "" && options.Address == "" { + return fmt.Errorf("redis-addr must be specified when using redis-password") + } + return nil +} diff --git a/backend/server.go b/backend/server.go index 85e9a7693..b58f0083e 100644 --- a/backend/server.go +++ b/backend/server.go @@ -41,6 +41,8 @@ import ( kube "github.com/kube-bind/kube-bind/backend/kubernetes" "github.com/kube-bind/kube-bind/backend/provider/kcp/controllers/apibindingtemplate" "github.com/kube-bind/kube-bind/backend/provider/kcp/controllers/apiresourceschema" + "github.com/kube-bind/kube-bind/backend/session/memory" + "github.com/kube-bind/kube-bind/backend/session/redis" kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" ) @@ -139,6 +141,16 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) { } } + sessionStore := memory.New() + + if addr := c.Options.Session.Redis.Address; addr != "" { + redisStore, err := redis.New(addr, c.Options.Session.Redis.Password) + if err != nil { + return nil, fmt.Errorf("error setting up Redis session store: %w", err) + } + sessionStore = redisStore + } + handler, err := http.NewHandler( s, s.Config.Options.OIDC.OIDCServer, @@ -153,6 +165,7 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) { s.Kubernetes, c.Options.Frontend, c.Options.TokenExpiry, + sessionStore, ) if err != nil { return nil, fmt.Errorf("error setting up HTTP Handler: %w", err) diff --git a/backend/session/memory/memory.go b/backend/session/memory/memory.go new file mode 100644 index 000000000..c19c80bf5 --- /dev/null +++ b/backend/session/memory/memory.go @@ -0,0 +1,101 @@ +/* +Copyright 2026 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package memory + +import ( + "context" + "errors" + "sync" + "time" + + "github.com/kube-bind/kube-bind/backend/session" +) + +type pkceEntry struct { + verifier string + expiresAt time.Time +} + +type Store struct { + lock sync.RWMutex + sessions map[string]*session.State + pkceVerifiers map[string]pkceEntry +} + +func New() session.Store { + return &Store{ + sessions: make(map[string]*session.State), + pkceVerifiers: make(map[string]pkceEntry), + } +} + +func (s *Store) Save(ctx context.Context, state *session.State) error { + s.lock.Lock() + defer s.lock.Unlock() + s.sessions[state.SessionID] = state + return nil +} + +func (s *Store) Load(ctx context.Context, sessionID string) (*session.State, error) { + s.lock.RLock() + defer s.lock.RUnlock() + state, exists := s.sessions[sessionID] + if !exists { + return nil, session.ErrSessionNotFound + } + return state, nil +} + +func (s *Store) Delete(ctx context.Context, sessionID string) error { + s.lock.Lock() + defer s.lock.Unlock() + delete(s.sessions, sessionID) + return nil +} + +func (s *Store) SavePKCEVerifier(ctx context.Context, sessionID, verifier string) error { + if sessionID == "" || verifier == "" { + return errors.New("sessionID and verifier cannot be empty") + } + s.lock.Lock() + defer s.lock.Unlock() + s.pkceVerifiers[sessionID] = pkceEntry{ + verifier: verifier, + expiresAt: time.Now().Add(session.PKCEVerifierTTL), + } + return nil +} + +func (s *Store) LoadAndDeletePKCEVerifier(ctx context.Context, sessionID string) (string, error) { + if sessionID == "" { + return "", session.ErrPKCEVerifierNotFound + } + s.lock.Lock() + defer s.lock.Unlock() + entry, ok := s.pkceVerifiers[sessionID] + if !ok { + return "", session.ErrPKCEVerifierNotFound + } + + delete(s.pkceVerifiers, sessionID) + + if time.Now().After(entry.expiresAt) { + return "", session.ErrPKCEVerifierNotFound + } + + return entry.verifier, nil +} diff --git a/backend/session/provider.go b/backend/session/provider.go new file mode 100644 index 000000000..0fc693430 --- /dev/null +++ b/backend/session/provider.go @@ -0,0 +1,36 @@ +/* +Copyright 2026 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package session + +import ( + "context" + "fmt" + "time" +) + +const PKCEVerifierTTL = 10 * time.Minute + +var ErrSessionNotFound = fmt.Errorf("session not found") +var ErrPKCEVerifierNotFound = fmt.Errorf("pkce verifier not found") + +type Store interface { + Save(ctx context.Context, state *State) error + Load(ctx context.Context, sessionID string) (*State, error) + Delete(ctx context.Context, sessionID string) error + SavePKCEVerifier(ctx context.Context, sessionID, verifier string) error + LoadAndDeletePKCEVerifier(ctx context.Context, sessionID string) (string, error) +} diff --git a/backend/session/redis/redis.go b/backend/session/redis/redis.go new file mode 100644 index 000000000..a3a14913f --- /dev/null +++ b/backend/session/redis/redis.go @@ -0,0 +1,145 @@ +/* +Copyright 2026 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package redis + +import ( + "context" + "errors" + "fmt" + "time" + + goredis "github.com/redis/go-redis/v9" + "github.com/vmihailenco/msgpack/v4" + "k8s.io/klog/v2" + + "github.com/kube-bind/kube-bind/backend/session" +) + +func getSessionKey(sessionID string) string { + return fmt.Sprintf("session:%s", sessionID) +} + +func getPKCEKey(sessionID string) string { + return fmt.Sprintf("pkce:%s", sessionID) +} + +type store struct { + client *goredis.Client +} + +func New(redisAddr string, redisPassword string) (session.Store, error) { + client := goredis.NewClient(&goredis.Options{ + Addr: redisAddr, + Password: redisPassword, + DB: 0, + }) + + if err := client.Ping(context.Background()).Err(); err != nil { + return nil, fmt.Errorf("failed to connect to redis: %w", err) + } + + return &store{ + client: client, + }, nil +} + +func (s *store) Save(ctx context.Context, state *session.State) error { + encoded, err := state.Encode() + if err != nil { + return fmt.Errorf("failed to encode state: %w", err) + } + + key := getSessionKey(state.SessionID) + + var ttl time.Duration + if !state.ExpiresAt.IsZero() { + ttl = time.Until(state.ExpiresAt) + if ttl <= 0 { + klog.FromContext(context.Background()).V(4).Info("Session already expired, skipping saving to redis", "sessionID", state.SessionID) + return nil + } + } + + err = s.client.Set(ctx, key, encoded, ttl).Err() + if err != nil { + return fmt.Errorf("failed to save session to redis: %w", err) + } + return nil +} + +func (s *store) Load(ctx context.Context, sessionID string) (*session.State, error) { + key := getSessionKey(sessionID) + + val, err := s.client.Get(ctx, key).Bytes() + if err != nil { + if errors.Is(err, goredis.Nil) { + return nil, session.ErrSessionNotFound + } + return nil, fmt.Errorf("failed to load session from redis: %w", err) + } + + var state session.State + err = msgpack.Unmarshal(val, &state) + if err != nil { + return nil, fmt.Errorf("failed to decode state from redis: %w", err) + } + + return &state, nil +} + +func (s *store) Delete(ctx context.Context, sessionID string) error { + key := getSessionKey(sessionID) + err := s.client.Del(ctx, key).Err() + if err != nil { + return fmt.Errorf("failed to delete session from redis: %w", err) + } + return nil +} + +func (s *store) SavePKCEVerifier(ctx context.Context, sessionID, verifier string) error { + if sessionID == "" || verifier == "" { + return errors.New("sessionID and verifier cannot be empty") + } + + key := getPKCEKey(sessionID) + + err := s.client.Set(ctx, key, verifier, session.PKCEVerifierTTL).Err() + if err != nil { + return fmt.Errorf("failed to save pkce to redis: %w", err) + } + return nil +} + +func (s *store) LoadAndDeletePKCEVerifier(ctx context.Context, sessionID string) (string, error) { + if sessionID == "" { + return "", session.ErrPKCEVerifierNotFound + } + + key := getPKCEKey(sessionID) + + val, err := s.client.Get(ctx, key).Result() + if err != nil { + if errors.Is(err, goredis.Nil) { + return "", session.ErrPKCEVerifierNotFound + } + return "", fmt.Errorf("failed to load pkce from redis: %w", err) + } + + _ = s.client.Del(ctx, key).Err() + + return val, nil +} diff --git a/backend/session/redis/redis_test.go b/backend/session/redis/redis_test.go new file mode 100644 index 000000000..5d26c27f6 --- /dev/null +++ b/backend/session/redis/redis_test.go @@ -0,0 +1,98 @@ +/* +Copyright 2026 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package redis + +import ( + "context" + "testing" + "time" + + "github.com/alicebob/miniredis/v2" + "github.com/stretchr/testify/require" + + "github.com/kube-bind/kube-bind/backend/session" +) + +func TestRedisStore(t *testing.T) { + s := miniredis.RunT(t) + defer s.Close() + + store, err := New(s.Addr(), "") + require.NoError(t, err) + + ctx := context.Background() + + t.Run("Save and Load Session", func(t *testing.T) { + state := &session.State{ + SessionID: "sess-123", + ClusterID: "clus-456", + } + state.SetExpiration(1 * time.Hour) + + err := store.Save(ctx, state) + require.NoError(t, err) + + loaded, err := store.Load(ctx, "sess-123") + require.NoError(t, err) + require.Equal(t, "clus-456", loaded.ClusterID) + }) + + t.Run("Load nonexistent Session", func(t *testing.T) { + _, err := store.Load(ctx, "sess-nonexistent") + require.ErrorIs(t, err, session.ErrSessionNotFound) + }) + + t.Run("Delete Session", func(t *testing.T) { + state := &session.State{ + SessionID: "sess-delete-123", + } + err := store.Save(ctx, state) + require.NoError(t, err) + + err = store.Delete(ctx, "sess-delete-123") + require.NoError(t, err) + + _, err = store.Load(ctx, "sess-delete-123") + require.ErrorIs(t, err, session.ErrSessionNotFound) + }) + + t.Run("Save and Load PKCE", func(t *testing.T) { + err := store.SavePKCEVerifier(ctx, "sess-pkce", "my-verifier") + require.NoError(t, err) + + val, err := store.LoadAndDeletePKCEVerifier(ctx, "sess-pkce") + require.NoError(t, err) + require.Equal(t, "my-verifier", val) + + _, err = store.LoadAndDeletePKCEVerifier(ctx, "sess-pkce") + require.ErrorIs(t, err, session.ErrPKCEVerifierNotFound) + }) + + t.Run("Save Expired Session should not store", func(t *testing.T) { + state := &session.State{ + SessionID: "sess-expired", + } + + state.SetExpiration(-1 * time.Hour) + + err := store.Save(ctx, state) + require.NoError(t, err) + + _, err = store.Load(ctx, "sess-expired") + require.ErrorIs(t, err, session.ErrSessionNotFound) + }) +} diff --git a/backend/session/store.go b/backend/session/store.go deleted file mode 100644 index c70a8710f..000000000 --- a/backend/session/store.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package session - -import ( - "errors" - "fmt" - "sync" -) - -var ErrSessionNotFound = fmt.Errorf("session not found") -var ErrPKCEVerifierNotFound = fmt.Errorf("pkce verifier not found") - -type Store interface { - Save(state *State) error - Load(sessionID string) (*State, error) - Delete(sessionID string) error - SavePKCEVerifier(sessionID, verifier string) error - LoadAndDeletePKCEVerifier(sessionID string) (string, error) -} - -type InMemoryStore struct { - lock sync.RWMutex - sessions map[string]*State - pkceVerifiers map[string]string -} - -func NewInMemoryStore() *InMemoryStore { - return &InMemoryStore{ - sessions: make(map[string]*State), - pkceVerifiers: make(map[string]string), - } -} - -func (s *InMemoryStore) Save(state *State) error { - s.lock.Lock() - defer s.lock.Unlock() - s.sessions[state.SessionID] = state - return nil -} - -func (s *InMemoryStore) Load(sessionID string) (*State, error) { - s.lock.RLock() - defer s.lock.RUnlock() - state, exists := s.sessions[sessionID] - if !exists { - return nil, ErrSessionNotFound - } - return state, nil -} - -func (s *InMemoryStore) Delete(sessionID string) error { - s.lock.Lock() - defer s.lock.Unlock() - delete(s.sessions, sessionID) - return nil -} - -func (s *InMemoryStore) SavePKCEVerifier(sessionID, verifier string) error { - if sessionID == "" || verifier == "" { - return errors.New("sessionID and verifier cannot be empty") - } - s.lock.Lock() - defer s.lock.Unlock() - s.pkceVerifiers[sessionID] = verifier - return nil -} - -func (s *InMemoryStore) LoadAndDeletePKCEVerifier(sessionID string) (string, error) { - if sessionID == "" { - return "", ErrPKCEVerifierNotFound - } - s.lock.Lock() - defer s.lock.Unlock() - verifier, ok := s.pkceVerifiers[sessionID] - if !ok { - return "", ErrPKCEVerifierNotFound - } - delete(s.pkceVerifiers, sessionID) - return verifier, nil -} diff --git a/cli/go.sum b/cli/go.sum index f979c1e2a..c88406c2c 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -270,8 +270,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fO github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= -github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -358,6 +358,8 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= diff --git a/contrib/kcp/go.mod b/contrib/kcp/go.mod index f7cdf257b..d7ec5ba60 100644 --- a/contrib/kcp/go.mod +++ b/contrib/kcp/go.mod @@ -52,6 +52,7 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dexidp/dex/api/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -102,6 +103,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/redis/go-redis/v9 v9.18.0 // indirect github.com/spf13/cobra v1.10.1 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect @@ -125,6 +127,7 @@ require ( go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/contrib/kcp/go.sum b/contrib/kcp/go.sum index ab050d6d5..e50bdbc63 100644 --- a/contrib/kcp/go.sum +++ b/contrib/kcp/go.sum @@ -6,6 +6,8 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= +github.com/alicebob/miniredis/v2 v2.37.0 h1:RheObYW32G1aiJIj81XVt78ZHJpHonHLHW7OLIshq68= +github.com/alicebob/miniredis/v2 v2.37.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= @@ -14,6 +16,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -33,6 +39,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dexidp/dex/api/v2 v2.3.0 h1:gv79YqTBTGU6z/QE3RNFzlS6KrPRUudVUN8o858gpCc= github.com/dexidp/dex/api/v2 v2.3.0/go.mod h1:y9As69T4WZOERCS/CfB9D8Dbb12tafU9ywv2ZZLf4EI= +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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= @@ -154,6 +162,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -200,6 +210,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -250,6 +262,10 @@ github.com/xrstf/mockoidc v0.0.0-20251217111820-5b7a850f338a/go.mod h1:1S3+yz/sa github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= @@ -286,6 +302,8 @@ go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZY go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.1-0.20251210191316-2b7fd8a0d244 h1:OdZ8e4E9yDUGiis9x2ta/Ec5yhMAKT6ZivRvakyxC7E= go.uber.org/goleak v1.3.1-0.20251210191316-2b7fd8a0d244/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= diff --git a/contrib/kcp/test/e2e/kcp_test.go b/contrib/kcp/test/e2e/kcp_test.go index 5f249e095..686c50e68 100644 --- a/contrib/kcp/test/e2e/kcp_test.go +++ b/contrib/kcp/test/e2e/kcp_test.go @@ -59,9 +59,6 @@ func testKcpIntegration(t *testing.T, name string, scope kubebindv1alpha2.Inform t.Helper() t.Logf("Testing kcp integration with informer scope %s, tempdir: %s", scope, t.TempDir()) - // dex - framework.StartDex(t) - // kcp bootstrap bootstrapKCP(t, framework.ClientConfig(t)) diff --git a/deploy/charts/backend/README.md b/deploy/charts/backend/README.md index d192c1bfb..5618bde73 100644 --- a/deploy/charts/backend/README.md +++ b/deploy/charts/backend/README.md @@ -34,7 +34,7 @@ See [values.yaml](values.yaml) for the full list of configurable parameters. | backend.externalServerName | string | `""` | External server name for TLS SNI | | backend.extraArgs | list | `[]` | Extra command-line arguments to pass to the backend | | backend.frontendDisabled | bool | `false` | Disable the frontend UI | -| backend.kubeconfig | string | `""` | Path to a kubeconfig file. Only required if out-of-cluster | +| backend.kubeconfig | string | `""` | Path to a kubeconfig file. Only required if out-of-cluster. | | backend.listenAddress | string | `"0.0.0.0:8080"` | Address the backend listens on | | backend.loggingLevel | int | `2` | Logging verbosity level | | backend.multiclusterRuntimeProvider | string | `""` | Multicluster runtime provider (e.g., "kcp") | @@ -43,13 +43,15 @@ See [values.yaml](values.yaml) for the full list of configurable parameters. | backend.oidc.allowedUsers | list | `[]` | List of users allowed to access bindings | | backend.oidc.callbackUrl | string | `""` | OIDC callback URL | | backend.oidc.clientId | string | `""` | OIDC client ID | -| backend.oidc.clientSecret | string | `""` | OIDC client secret (plaintext, prefer clientSecretName for production) | +| backend.oidc.clientSecret | string | `""` | Not required for providers using PKCE or public clients | | backend.oidc.clientSecretKey | string | `""` | Key within the secret (e.g., "client-secret") | -| backend.oidc.clientSecretName | string | `""` | Name of the Kubernetes secret containing the OIDC client secret | +| backend.oidc.clientSecretName | string | `""` | If set, the secret will be mounted as OIDC_CLIENT_SECRET env var | | backend.oidc.issuerUrl | string | `""` | OIDC issuer URL (leave empty for embedded OIDC server) | | backend.oidc.type | string | `"embedded"` | OIDC provider type. Options: "embedded" or "external" | | backend.prettyName | string | `""` | Human-readable name for this backend instance | | backend.schemaSource | string | `""` | Schema source (e.g., "apiresourceschemas") | +| backend.sessionStorage.redisAddress | string | `""` | | +| backend.sessionStorage.redisPassword | string | `""` | | | backend.tls.certSecretName | string | `""` | Name of the Kubernetes secret containing TLS certificate | | backend.tls.enabled | bool | `false` | Enable TLS for the backend | | backend.tls.tlsCertFile | string | `"/etc/kube-bind/tls/tls.crt"` | Path to TLS certificate file inside the container | diff --git a/deploy/charts/backend/templates/deployment.yaml b/deploy/charts/backend/templates/deployment.yaml index ad73ee6ec..4d66af1fd 100644 --- a/deploy/charts/backend/templates/deployment.yaml +++ b/deploy/charts/backend/templates/deployment.yaml @@ -48,14 +48,18 @@ spec: {{- end }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- if .Values.backend.oidc.clientSecretName }} env: + {{- if .Values.backend.oidc.clientSecretName }} - name: OIDC_CLIENT_SECRET valueFrom: secretKeyRef: name: {{ .Values.backend.oidc.clientSecretName }} key: {{ .Values.backend.oidc.clientSecretKey }} {{- end }} + {{- if .Values.backend.sessionStorage.redisPassword }} + - name: REDIS_PASSWORD + value: {{ .Values.backend.sessionStorage.redisPassword }} + {{- end }} args: - --listen-address={{ .Values.backend.listenAddress }} {{- if .Values.backend.kubeconfig }} @@ -103,6 +107,9 @@ spec: {{- if .Values.backend.cookieEncryptionKey }} - --cookie-encryption-key={{ .Values.backend.cookieEncryptionKey }} {{- end }} + {{- if .Values.backend.sessionStorage.redisAddress }} + - --redis-addr={{ .Values.backend.sessionStorage.redisAddress }} + {{- end }} {{- if .Values.backend.tls.enabled }} - --tls-cert-file={{ .Values.backend.tls.tlsCertFile }} - --tls-key-file={{ .Values.backend.tls.tlsKeyFile }} diff --git a/deploy/charts/backend/values.yaml b/deploy/charts/backend/values.yaml index 40546ecd4..dc62ac297 100644 --- a/deploy/charts/backend/values.yaml +++ b/deploy/charts/backend/values.yaml @@ -9,7 +9,7 @@ replicaCount: 1 backend: # -- Address the backend listens on listenAddress: "0.0.0.0:8080" - # -- Path to a kubeconfig file. Only required if out-of-cluster + # -- Path to a kubeconfig file. Only required if out-of-cluster. kubeconfig: "" # -- The name of the kubeconfig context to use context: "" @@ -31,20 +31,22 @@ backend: tlsKeyFile: "/etc/kube-bind/tls/tls.key" # OIDC configuration. Empty values will run the embedded OIDC server. oidc: + # -- OIDC provider type. Options: "embedded" or "external" + type: "embedded" # -- OIDC issuer URL (leave empty for embedded OIDC server) issuerUrl: "" # -- OIDC client ID clientId: "" # -- OIDC client secret (plaintext, prefer clientSecretName for production) + # -- Not required for providers using PKCE or public clients clientSecret: "" # -- Name of the Kubernetes secret containing the OIDC client secret + # -- If set, the secret will be mounted as OIDC_CLIENT_SECRET env var clientSecretName: "" # -- Key within the secret (e.g., "client-secret") clientSecretKey: "" # -- OIDC callback URL callbackUrl: "" - # -- OIDC provider type. Options: "embedded" or "external" - type: "embedded" # -- List of groups allowed to access bindings. With embedded OIDC, system:authenticated is added automatically allowedGroups: [] # -- List of users allowed to access bindings @@ -72,6 +74,12 @@ backend: # -- Extra command-line arguments to pass to the backend extraArgs: [] + # Session storage configuration + sessionStorage: + redisAddress: "" + redisPassword: "" + + # Cookie configuration - these should be base64 encoded keys # -- Cookie signing key (base64 encoded). Empty generates random key on each start (not for production!) cookieSigningKey: "" # -- Cookie encryption key (base64 encoded). Empty generates random key on each start (not for production!) diff --git a/docs/content/setup/helm.md b/docs/content/setup/helm.md index b91d36649..df85fa702 100644 --- a/docs/content/setup/helm.md +++ b/docs/content/setup/helm.md @@ -25,6 +25,7 @@ The following prerequisites are required. Click the links below for detailed set 1. **Get the latest chart version:** Visit the [releases page](https://github.com/kube-bind/kube-bind/releases) or check available versions: + ```bash # For latest tag version (recommended for production): VERSION=$(curl -s https://api.github.com/repos/kube-bind/kube-bind/releases/latest | grep '"tag_name"' | cut -d'"' -f4 | sed 's/v//') @@ -41,9 +42,9 @@ The following prerequisites are required. Click the links below for detailed set 3. **Install the backend using OCI chart:** - Note !!! - To install production configuration, you will need to have OIDC provider. - For more information, just check out the [quickstart guide].(./quickstart.md) +!!! note + To install production configuration, you will need to have an OIDC provider. + For more information, just check out the [quickstart guide](./quickstart.md). ```bash # Using latest release version @@ -68,8 +69,8 @@ helm upgrade \ kube-bind oci://ghcr.io/kube-bind/charts/backend --version ${VERSION} ``` - 4. **Seed with example resources (optional):** + ```bash kubectl apply -f deploy/examples/crd-foo.yaml kubectl apply -f deploy/examples/crd-mangodb.yaml @@ -83,9 +84,11 @@ That's it! Your kube-bind backend is now ready to use. --- ### Helm + Install Helm 3.x from [https://helm.sh/docs/intro/install/](https://helm.sh/docs/intro/install/) **Note**: Helm 3.8+ is required for OCI chart support. Enable experimental OCI support if needed: + ```bash export HELM_EXPERIMENTAL_OCI=1 ``` @@ -95,7 +98,7 @@ export HELM_EXPERIMENTAL_OCI=1 Install gateway API CRDs and controller for advanced ingress management. Kube-bind supports Gateway API for routing traffic to the backend service. Follow the official Gateway API installation instructions: -https://gateway-api.sigs.k8s.io/guides/ +https://gateway-api.sigs.k8s.io/guides/ ```bash kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml @@ -104,7 +107,7 @@ kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/re We used NGINX Gateway controller for testing. Install it as follows: ```bash -helm upgrade --install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric --create-namespace -n nginx-gateway +helm upgrade --install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric --create-namespace -n nginx-gateway ``` ### cert-manager Setup @@ -279,6 +282,7 @@ The example values file at `deploy/charts/backend/examples/values-local-developm - **OIDC credentials**: Get these from your OIDC provider (Dex, GitHub, etc.) - **Cookie keys**: Generate with `openssl rand -base64 32` - **Hostnames**: Update to match your actual domains +- **Redis Shared Session Storage**: If running multiple replicas of `backend`, provide connection details for `backend.sessionStorage.redisAddress` and `backend.sessionStorage.redisPassword`. The password will be securely injected as the `REDIS_PASSWORD` environment variable. For production deployments, create your own values file based on the example. @@ -289,6 +293,7 @@ For production deployments, create your own values file based on the example. kube-bind Helm charts are published as OCI images to GitHub Container Registry: ### Backend Chart + - **Registry**: `oci://ghcr.io/kube-bind/charts/backend` - **Latest Release**: Use the latest tag version (e.g., `1.0.0`) - **Development Builds**: Available as `0.0.0-` format for each commit to main @@ -296,6 +301,7 @@ kube-bind Helm charts are published as OCI images to GitHub Container Registry: ### Finding Available Versions **Release versions:** + ```bash # List all releases curl -s https://api.github.com/repos/kube-bind/kube-bind/releases | grep '"tag_name"' | head -5 @@ -303,4 +309,4 @@ curl -s https://api.github.com/repos/kube-bind/kube-bind/releases | grep '"tag_n # Get latest release version VERSION=$(curl -s https://api.github.com/repos/kube-bind/kube-bind/releases/latest | grep '"tag_name"' | cut -d'"' -f4 | sed 's/v//') echo "Latest version: ${VERSION}" -``` \ No newline at end of file +``` diff --git a/docs/generators/cli-doc/go.sum b/docs/generators/cli-doc/go.sum index c7fd055c1..4325da9ac 100644 --- a/docs/generators/cli-doc/go.sum +++ b/docs/generators/cli-doc/go.sum @@ -270,8 +270,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fO github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= -github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -357,6 +357,8 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= diff --git a/go.mod b/go.mod index cc9e3d875..60138d53a 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ replace ( replace github.com/kcp-dev/sdk => github.com/kcp-dev/sdk v0.0.0-20251210172228-11364df3071c require ( + github.com/alicebob/miniredis/v2 v2.37.0 github.com/coreos/go-oidc/v3 v3.16.0 github.com/dexidp/dex/api/v2 v2.3.0 github.com/evanphx/json-patch/v5 v5.9.11 @@ -35,6 +36,7 @@ require ( github.com/kube-bind/kube-bind/sdk v0.4.1 github.com/kube-bind/kube-bind/web v0.0.0-00010101000000-000000000000 github.com/martinlindhe/base36 v1.1.1 + github.com/redis/go-redis/v9 v9.18.0 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 @@ -73,6 +75,7 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -122,6 +125,7 @@ require ( github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect go.etcd.io/etcd/api/v3 v3.6.4 // indirect go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect go.etcd.io/etcd/client/v3 v3.6.4 // indirect @@ -135,6 +139,7 @@ require ( go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/go.sum b/go.sum index 215021f10..271a12a17 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= +github.com/alicebob/miniredis/v2 v2.37.0 h1:RheObYW32G1aiJIj81XVt78ZHJpHonHLHW7OLIshq68= +github.com/alicebob/miniredis/v2 v2.37.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= @@ -14,6 +16,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -33,6 +39,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dexidp/dex/api/v2 v2.3.0 h1:gv79YqTBTGU6z/QE3RNFzlS6KrPRUudVUN8o858gpCc= github.com/dexidp/dex/api/v2 v2.3.0/go.mod h1:y9As69T4WZOERCS/CfB9D8Dbb12tafU9ywv2ZZLf4EI= +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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= @@ -134,6 +142,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -180,6 +190,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -230,6 +242,10 @@ github.com/xrstf/mockoidc v0.0.0-20251217111820-5b7a850f338a/go.mod h1:1S3+yz/sa github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= @@ -266,6 +282,8 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.1-0.20241121203838-4ff5fa6529ee h1:uOMbcH1Dmxv45VkkpZQYoerZFeDncWpjbN7ATiQOO7c= go.uber.org/goleak v1.3.1-0.20241121203838-4ff5fa6529ee/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=