Skip to content

Commit 771dc90

Browse files
committed
add optional redis for backend storage
1 parent 93994bc commit 771dc90

20 files changed

Lines changed: 486 additions & 62 deletions

File tree

backend/auth/handler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func (ah *AuthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request) {
118118
ah.respondWithError(w, authReq.ClientType, "failed to generate PKCE", http.StatusInternalServerError)
119119
return
120120
}
121-
if err := ah.sessionStore.SavePKCEVerifier(authReq.SessionID, verifier); err != nil {
121+
if err := ah.sessionStore.SavePKCEVerifier(r.Context(), authReq.SessionID, verifier); err != nil {
122122
logger.Error(err, "failed to store PKCE verifier")
123123
ah.respondWithError(w, authReq.ClientType, "failed to store PKCE verifier", http.StatusInternalServerError)
124124
return
@@ -194,7 +194,7 @@ func (ah *AuthHandler) HandleCallback(w http.ResponseWriter, r *http.Request) {
194194
ctx = context.WithValue(ctx, oauth2.HTTPClient, client)
195195
}
196196

197-
verifier, err := ah.sessionStore.LoadAndDeletePKCEVerifier(authCode.SessionID)
197+
verifier, err := ah.sessionStore.LoadAndDeletePKCEVerifier(r.Context(), authCode.SessionID)
198198
if err != nil || verifier == "" {
199199
logger.Error(err, "PKCE verifier not found for session; cannot exchange code", "sessionID", authCode.SessionID)
200200
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."
@@ -217,7 +217,7 @@ func (ah *AuthHandler) HandleCallback(w http.ResponseWriter, r *http.Request) {
217217
return
218218
}
219219
// Set session expiration and store in middleware
220-
err = ah.sessionStore.Save(sessionState)
220+
err = ah.sessionStore.Save(r.Context(), sessionState)
221221
if err != nil {
222222
logger.Error(err, "failed to save session state")
223223
http.Error(w, "internal error", http.StatusInternalServerError)

backend/auth/middleware.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func (am *AuthMiddleware) verifyState(next http.Handler) http.Handler {
182182
return
183183
}
184184

185-
if !am.isValidSession(state.SessionID) {
185+
if !am.isValidSession(r.Context(), state.SessionID) {
186186
logger.V(2).Info("Session expired or invalid", "sessionID", state.SessionID)
187187
writeErrorResponse(w, http.StatusUnauthorized, kubebindv1alpha2.ErrorCodeAuthenticationFailed, "Authentication required", "Session has expired or is invalid")
188188
return
@@ -197,8 +197,8 @@ func (am *AuthMiddleware) verifyState(next http.Handler) http.Handler {
197197
}
198198

199199
// isValidSession checks if a session ID exists and hasn't expired
200-
func (am *AuthMiddleware) isValidSession(sessionID string) bool {
201-
sessionInfo, err := am.sessionStore.Load(sessionID)
200+
func (am *AuthMiddleware) isValidSession(ctx context.Context, sessionID string) bool {
201+
sessionInfo, err := am.sessionStore.Load(ctx, sessionID)
202202
if err != nil {
203203
return false
204204
}

backend/http/handler.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,14 @@ func NewHandler(
112112
mgr *kubernetes.Manager,
113113
frontend string,
114114
tokenExpiry time.Duration,
115+
sessionStore session.Store,
115116
) (*handler, error) {
116117
// Create JWT service for CLI authentication
117118
jwtService, err := auth.NewJWTService("kube-bind-backend")
118119
if err != nil {
119120
return nil, fmt.Errorf("failed to create JWT service: %w", err)
120121
}
121122

122-
sessionStore := session.NewInMemoryStore()
123-
124123
// Create auth middleware for request authentication
125124
authMiddleware := auth.NewAuthMiddleware(jwtService, cookieSigningKey, cookieEncryptionKey, mgr, sessionStore)
126125

backend/options/options.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ import (
3333
)
3434

3535
type Options struct {
36-
Logs *logs.Options
37-
OIDC *OIDC
38-
Cookie *Cookie
39-
Serve *Serve
36+
Logs *logs.Options
37+
OIDC *OIDC
38+
Cookie *Cookie
39+
Serve *Serve
40+
Session *Session
4041

4142
ProviderKcp *providerkcp.Options
4243

@@ -81,10 +82,11 @@ type ExtraOptions struct {
8182
}
8283

8384
type completedOptions struct {
84-
Logs *logs.Options
85-
OIDC *OIDC
86-
Cookie *Cookie
87-
Serve *Serve
85+
Logs *logs.Options
86+
OIDC *OIDC
87+
Cookie *Cookie
88+
Serve *Serve
89+
Session *Session
8890

8991
// Provider specific options
9092
ProviderKcp *providerkcp.CompletedOptions
@@ -106,6 +108,7 @@ func NewOptions() *Options {
106108
OIDC: NewOIDC(),
107109
Cookie: NewCookie(),
108110
Serve: NewServe(),
111+
Session: NewSession(),
109112
ProviderKcp: providerkcp.NewOptions(),
110113

111114
ExtraOptions: ExtraOptions{
@@ -155,6 +158,7 @@ func (options *Options) AddFlags(fs *pflag.FlagSet) {
155158
options.OIDC.AddFlags(fs)
156159
options.Cookie.AddFlags(fs)
157160
options.Serve.AddFlags(fs)
161+
options.Session.AddFlags(fs)
158162
options.ProviderKcp.AddFlags(fs)
159163

160164
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) {
207211
if err := options.Cookie.Complete(); err != nil {
208212
return nil, err
209213
}
214+
if err := options.Session.Complete(); err != nil {
215+
return nil, err
216+
}
210217
}
211218

212219
// normalize the scope and the isolation
@@ -243,6 +250,7 @@ func (options *Options) Complete() (*CompletedOptions, error) {
243250
OIDC: options.OIDC,
244251
Cookie: options.Cookie,
245252
Serve: options.Serve,
253+
Session: options.Session,
246254
ExtraOptions: options.ExtraOptions,
247255
},
248256
}
@@ -276,6 +284,9 @@ func (options *CompletedOptions) Validate() error {
276284
if err := options.Cookie.Validate(); err != nil {
277285
return err
278286
}
287+
if err := options.Session.Validate(); err != nil {
288+
return err
289+
}
279290
}
280291

281292
if options.ConsumerScope != string(kubebindv1alpha2.NamespacedScope) && options.ConsumerScope != string(kubebindv1alpha2.ClusterScope) {

backend/options/session.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Copyright 2026 The Kube Bind Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package options
18+
19+
import (
20+
"github.com/spf13/pflag"
21+
22+
redisoptions "github.com/kube-bind/kube-bind/backend/options/session/redis"
23+
)
24+
25+
type Session struct {
26+
Redis *redisoptions.Options
27+
}
28+
29+
func NewSession() *Session {
30+
return &Session{
31+
Redis: redisoptions.NewOptions(),
32+
}
33+
}
34+
35+
func (options *Session) AddFlags(fs *pflag.FlagSet) {
36+
options.Redis.AddFlags(fs)
37+
}
38+
39+
func (options *Session) Complete() error {
40+
return nil
41+
}
42+
43+
func (options *Session) Validate() error {
44+
return options.Redis.Validate()
45+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2026 The Kube Bind Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package redis
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/spf13/pflag"
23+
)
24+
25+
type Options struct {
26+
Address string
27+
Password string
28+
}
29+
30+
func NewOptions() *Options {
31+
return &Options{}
32+
}
33+
34+
func (options *Options) AddFlags(fs *pflag.FlagSet) {
35+
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.")
36+
fs.StringVar(&options.Password, "redis-password", options.Password, "The connection password to use if storing sessions in Redis.")
37+
}
38+
func (options *Options) Validate() error {
39+
if options.Password != "" && options.Address == "" {
40+
return fmt.Errorf("redis-addr must be specified when using redis-password")
41+
}
42+
return nil
43+
}

backend/server.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import (
3838
"github.com/kube-bind/kube-bind/backend/controllers/servicenamespace"
3939
http "github.com/kube-bind/kube-bind/backend/http"
4040
kube "github.com/kube-bind/kube-bind/backend/kubernetes"
41+
"github.com/kube-bind/kube-bind/backend/session/memory"
42+
"github.com/kube-bind/kube-bind/backend/session/redis"
4143
kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
4244
)
4345

@@ -126,6 +128,11 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) {
126128
}
127129
}
128130

131+
sessionStore := memory.NewInMemoryStore()
132+
if c.Options.Session.Redis.Address != "" || c.Options.Session.Redis.Password != "" {
133+
sessionStore = redis.New(c.Options.Session.Redis.Address, c.Options.Session.Redis.Password)
134+
}
135+
129136
handler, err := http.NewHandler(
130137
s,
131138
s.Config.Options.OIDC.OIDCServer,
@@ -140,6 +147,7 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) {
140147
s.Kubernetes,
141148
c.Options.Frontend,
142149
c.Options.TokenExpiry,
150+
sessionStore,
143151
)
144152
if err != nil {
145153
return nil, fmt.Errorf("error setting up HTTP Handler: %w", err)
Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2025 The Kube Bind Authors.
2+
Copyright 2026 The Kube Bind Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -14,63 +14,54 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package session
17+
package memory
1818

1919
import (
20+
"context"
2021
"errors"
21-
"fmt"
2222
"sync"
23-
)
24-
25-
var ErrSessionNotFound = fmt.Errorf("session not found")
26-
var ErrPKCEVerifierNotFound = fmt.Errorf("pkce verifier not found")
2723

28-
type Store interface {
29-
Save(state *State) error
30-
Load(sessionID string) (*State, error)
31-
Delete(sessionID string) error
32-
SavePKCEVerifier(sessionID, verifier string) error
33-
LoadAndDeletePKCEVerifier(sessionID string) (string, error)
34-
}
24+
"github.com/kube-bind/kube-bind/backend/session"
25+
)
3526

3627
type InMemoryStore struct {
3728
lock sync.RWMutex
38-
sessions map[string]*State
29+
sessions map[string]*session.State
3930
pkceVerifiers map[string]string
4031
}
4132

42-
func NewInMemoryStore() *InMemoryStore {
33+
func NewInMemoryStore() session.Store {
4334
return &InMemoryStore{
44-
sessions: make(map[string]*State),
35+
sessions: make(map[string]*session.State),
4536
pkceVerifiers: make(map[string]string),
4637
}
4738
}
4839

49-
func (s *InMemoryStore) Save(state *State) error {
40+
func (s *InMemoryStore) Save(ctx context.Context, state *session.State) error {
5041
s.lock.Lock()
5142
defer s.lock.Unlock()
5243
s.sessions[state.SessionID] = state
5344
return nil
5445
}
5546

56-
func (s *InMemoryStore) Load(sessionID string) (*State, error) {
47+
func (s *InMemoryStore) Load(ctx context.Context, sessionID string) (*session.State, error) {
5748
s.lock.RLock()
5849
defer s.lock.RUnlock()
5950
state, exists := s.sessions[sessionID]
6051
if !exists {
61-
return nil, ErrSessionNotFound
52+
return nil, session.ErrSessionNotFound
6253
}
6354
return state, nil
6455
}
6556

66-
func (s *InMemoryStore) Delete(sessionID string) error {
57+
func (s *InMemoryStore) Delete(ctx context.Context, sessionID string) error {
6758
s.lock.Lock()
6859
defer s.lock.Unlock()
6960
delete(s.sessions, sessionID)
7061
return nil
7162
}
7263

73-
func (s *InMemoryStore) SavePKCEVerifier(sessionID, verifier string) error {
64+
func (s *InMemoryStore) SavePKCEVerifier(ctx context.Context, sessionID, verifier string) error {
7465
if sessionID == "" || verifier == "" {
7566
return errors.New("sessionID and verifier cannot be empty")
7667
}
@@ -80,15 +71,15 @@ func (s *InMemoryStore) SavePKCEVerifier(sessionID, verifier string) error {
8071
return nil
8172
}
8273

83-
func (s *InMemoryStore) LoadAndDeletePKCEVerifier(sessionID string) (string, error) {
74+
func (s *InMemoryStore) LoadAndDeletePKCEVerifier(ctx context.Context, sessionID string) (string, error) {
8475
if sessionID == "" {
85-
return "", ErrPKCEVerifierNotFound
76+
return "", session.ErrPKCEVerifierNotFound
8677
}
8778
s.lock.Lock()
8879
defer s.lock.Unlock()
8980
verifier, ok := s.pkceVerifiers[sessionID]
9081
if !ok {
91-
return "", ErrPKCEVerifierNotFound
82+
return "", session.ErrPKCEVerifierNotFound
9283
}
9384
delete(s.pkceVerifiers, sessionID)
9485
return verifier, nil

backend/session/provider.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
Copyright 2026 The Kube Bind Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package session
18+
19+
import (
20+
"context"
21+
"fmt"
22+
)
23+
24+
var ErrSessionNotFound = fmt.Errorf("session not found")
25+
var ErrPKCEVerifierNotFound = fmt.Errorf("pkce verifier not found")
26+
27+
type Store interface {
28+
Save(ctx context.Context, state *State) error
29+
Load(ctx context.Context, sessionID string) (*State, error)
30+
Delete(ctx context.Context, sessionID string) error
31+
SavePKCEVerifier(ctx context.Context, sessionID, verifier string) error
32+
LoadAndDeletePKCEVerifier(ctx context.Context, sessionID string) (string, error)
33+
}

0 commit comments

Comments
 (0)