Skip to content

Commit fa1bed1

Browse files
committed
auth: implement session cookie per pod
Make it possible for multiple sessions to exist in parallel across all the instances of the console. This is achieved by naming the session cookies after the pod that create them. That means that if there's a new rollout, a new cookie will be created if browser traffic is redirected to a new console instance. The old cookies should eventually be removed once they reach their MaxAge.
1 parent a20a4d3 commit fa1bed1

2 files changed

Lines changed: 63 additions & 36 deletions

File tree

pkg/auth/sessions/combined_sessions.go

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package sessions
33
import (
44
"fmt"
55
"net/http"
6+
"os"
7+
"strings"
68
"sync"
79

810
gorilla "github.com/gorilla/sessions"
@@ -16,6 +18,16 @@ type CombinedSessionStore struct {
1618
sessionLock sync.Mutex
1719
}
1820

21+
type session struct {
22+
sessionToken *gorilla.Session
23+
refreshToken *gorilla.Session
24+
}
25+
26+
func SessionCookieName() string {
27+
podName, _ := os.LookupEnv("POD_NAME")
28+
return OpenshiftAccessTokenCookieName + "-" + podName
29+
}
30+
1931
func NewSessionStore(authnKey, encryptKey []byte, secureCookies bool, cookiePath string) *CombinedSessionStore {
2032
clientStore := gorilla.NewCookieStore(authnKey, encryptKey)
2133
clientStore.Options.Secure = secureCookies
@@ -39,11 +51,32 @@ func (cs *CombinedSessionStore) AddSession(w http.ResponseWriter, r *http.Reques
3951
return fmt.Errorf("failed to add session to server store: %w", err)
4052
}
4153

42-
clientSession, _ := cs.clientStore.Get(r, OpenshiftAccessTokenCookieName)
43-
clientSession.Values["session-token"] = ls.sessionToken
44-
clientSession.Values["refresh-token"] = ls.refreshToken
54+
clientSession := cs.getCookieSession(r)
55+
clientSession.sessionToken.Values["session-token"] = ls.sessionToken
56+
clientSession.refreshToken.Values["refresh-token"] = ls.refreshToken
4557

46-
return clientSession.Save(r, w)
58+
return clientSession.save(r, w)
59+
}
60+
61+
func (cs *CombinedSessionStore) getCookieSession(r *http.Request) *session {
62+
clientSession, _ := cs.clientStore.Get(r, SessionCookieName())
63+
refreshSession, _ := cs.clientStore.Get(r, openshiftRefreshTokenCookieName)
64+
return &session{
65+
sessionToken: clientSession,
66+
refreshToken: refreshSession,
67+
}
68+
}
69+
70+
func (s *session) save(r *http.Request, w http.ResponseWriter) error {
71+
if err := s.sessionToken.Save(r, w); err != nil {
72+
return fmt.Errorf("failed to save session token cookie: %w", err)
73+
}
74+
75+
if err := s.refreshToken.Save(r, w); err != nil {
76+
return fmt.Errorf("failed to save refresh token cookie: %w", err)
77+
}
78+
79+
return nil
4780
}
4881

4982
// GetSession returns a session identified by the cookie from the current request.
@@ -53,33 +86,23 @@ func (cs *CombinedSessionStore) GetSession(w http.ResponseWriter, r *http.Reques
5386
defer cs.sessionLock.Unlock()
5487

5588
// Get always returns a session, even if empty.
56-
clientSession, _ := cs.clientStore.Get(r, OpenshiftAccessTokenCookieName)
89+
clientSession := cs.getCookieSession(r)
5790

58-
// TODO: what happens if session-token is set by another instance and we get here?
59-
// Will we loop in redirects forever since it's set but we don't know anything about that session
60-
// and so we redirect back to login, which sees session token exists?
6191
var sessionToken, refreshToken string
62-
if sessionTokenIface, ok := clientSession.Values["session-token"]; ok { // FIXME: allow for multiple session tokens, add pruning
92+
if sessionTokenIface, ok := clientSession.sessionToken.Values["session-token"]; ok {
6393
sessionToken = sessionTokenIface.(string)
6494
}
65-
if refreshTokenIface, ok := clientSession.Values["refresh-token"]; ok {
95+
if refreshTokenIface, ok := clientSession.refreshToken.Values["refresh-token"]; ok {
6696
refreshToken = refreshTokenIface.(string)
6797
}
6898

6999
loginState := cs.serverStore.GetSession(sessionToken, refreshToken)
70-
if loginState == nil {
71-
// // the session-token was created by someone else (or it was pruned), we need to get our own server session
72-
// clientSession.Values["session-token"] = "" // FIXME: wrong for non-sticky sessions but should be ok for PoC
73-
// clientSession.Save(r, w)
74-
return nil, nil
75-
}
76-
77100
return loginState, nil
78101
}
79102

80103
func (cs *CombinedSessionStore) GetCookieRefreshToken(r *http.Request) string {
81104
// Get always returns a session, even if empty.
82-
clientSession, _ := cs.clientStore.Get(r, OpenshiftAccessTokenCookieName)
105+
clientSession, _ := cs.clientStore.Get(r, openshiftRefreshTokenCookieName)
83106
if refreshToken, ok := clientSession.Values["refresh-token"].(string); ok {
84107
return refreshToken
85108
}
@@ -88,7 +111,7 @@ func (cs *CombinedSessionStore) GetCookieRefreshToken(r *http.Request) string {
88111

89112
func (cs *CombinedSessionStore) UpdateCookieRefreshToken(w http.ResponseWriter, r *http.Request, refreshToken string) error {
90113
// no need to lock here since there shouldn't be any races around the client session
91-
clientSession, _ := cs.clientStore.Get(r, OpenshiftAccessTokenCookieName)
114+
clientSession, _ := cs.clientStore.Get(r, openshiftRefreshTokenCookieName)
92115
clientSession.Values["refresh-token"] = refreshToken
93116
return clientSession.Save(r, w)
94117
}
@@ -97,18 +120,18 @@ func (cs *CombinedSessionStore) UpdateTokens(w http.ResponseWriter, r *http.Requ
97120
cs.sessionLock.Lock()
98121
defer cs.sessionLock.Unlock()
99122

100-
clientSession, _ := cs.clientStore.Get(r, OpenshiftAccessTokenCookieName)
123+
clientSession := cs.getCookieSession(r)
101124
var oldRefreshToken string
102-
if oldToken, ok := clientSession.Values["refresh-token"]; ok {
125+
if oldToken, ok := clientSession.refreshToken.Values["refresh-token"]; ok {
103126
oldRefreshToken = oldToken.(string)
104127
}
105128

106129
refreshToken := tokenResponse.RefreshToken
107-
clientSession.Values["refresh-token"] = refreshToken
130+
clientSession.refreshToken.Values["refresh-token"] = refreshToken
108131

109132
var loginState *LoginState
110-
sessionToken, ok := clientSession.Values["session-token"]
111-
if ok { // TODO: since we only have a single session token in the cookie, we need to check that the session was actually created by us or not
133+
sessionToken, ok := clientSession.sessionToken.Values["session-token"]
134+
if ok {
112135
loginState = cs.serverStore.GetSession(sessionToken.(string), "")
113136
}
114137
if loginState == nil {
@@ -122,7 +145,7 @@ func (cs *CombinedSessionStore) UpdateTokens(w http.ResponseWriter, r *http.Requ
122145
return nil, fmt.Errorf("failed to add session to server store: %w", err)
123146
}
124147
cs.serverStore.byRefreshToken[oldRefreshToken] = loginState
125-
clientSession.Values["session-token"] = loginState.sessionToken
148+
clientSession.sessionToken.Values["session-token"] = loginState.sessionToken
126149
} else {
127150
if err := loginState.UpdateTokens(tokenVerifier, tokenResponse); err != nil {
128151
return nil, err
@@ -132,21 +155,23 @@ func (cs *CombinedSessionStore) UpdateTokens(w http.ResponseWriter, r *http.Requ
132155

133156
}
134157

135-
return loginState, clientSession.Save(r, w)
136-
}
137-
138-
func (cs *CombinedSessionStore) deleteSession(w http.ResponseWriter, r *http.Request, sessionToken string) error {
139-
clientSession, _ := cs.clientStore.Get(r, OpenshiftAccessTokenCookieName)
140-
delete(clientSession.Values, "session-token")
141-
clientSession.Save(r, w)
142-
return cs.serverStore.DeleteSession(sessionToken)
158+
return loginState, clientSession.save(r, w)
143159
}
144160

145161
func (cs *CombinedSessionStore) DeleteSession(w http.ResponseWriter, r *http.Request, sessionToken string) error {
146162
cs.sessionLock.Lock()
147163
defer cs.sessionLock.Unlock()
148164

149-
return cs.deleteSession(w, r, sessionToken)
165+
for _, cookie := range r.Cookies() {
166+
if strings.HasPrefix(cookie.Name, OpenshiftAccessTokenCookieName) {
167+
cookie.MaxAge = -1
168+
http.SetCookie(w, cookie)
169+
}
170+
}
171+
172+
refreshTokenCookie, _ := cs.clientStore.Get(r, openshiftRefreshTokenCookieName)
173+
refreshTokenCookie.Options.MaxAge = -1
174+
return cs.clientStore.Save(r, w, refreshTokenCookie)
150175
}
151176

152177
// FIXME: do this regulary in a separate goroutine on background

pkg/auth/sessions/server_session.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import (
1010
"k8s.io/klog"
1111
)
1212

13-
const OpenshiftAccessTokenCookieName = "openshift-session-token"
13+
const (
14+
OpenshiftAccessTokenCookieName = "openshift-session-token"
15+
openshiftRefreshTokenCookieName = "openshift-refresh-token"
16+
)
1417

1518
type SessionStore struct {
1619
byToken map[string]*LoginState
@@ -23,7 +26,6 @@ type SessionStore struct {
2326
mux sync.Mutex
2427
}
2528

26-
// TODO: how is this shared between console instances? I doubt it is, we may want to use encrypted cookies instead
2729
func NewServerSessionStore(maxSessions int) *SessionStore {
2830
return &SessionStore{
2931
byToken: make(map[string]*LoginState),

0 commit comments

Comments
 (0)