Skip to content

Commit

Permalink
cache: Add caching mechanism for bearer tokens
Browse files Browse the repository at this point in the history
Here we introduce a caching mechanism for bearer tokens. AuthService
will use this cache to avoid authenticating freshly authenticated Bearer
Tokens over and over again.

We also add the CACHE_ENABLED envvar.

This envvar offers the following utility:
* false: disable the caching mechanism (default)
* true: enable the caching mechanism

To set the parameters of the cache we introduce:
* CACHE_EXPIRATION_MINUTES: the amount of time (minutes) after which
  each cached Bearer Token is considered expired.

GitHub-PR: arrikto#87

Signed-off-by: Athanasios Markou <[email protected]>
Reviewed-by: Ioannis Bouloumpasis <[email protected]>
  • Loading branch information
Athanasios Markou authored and johnbuluba committed Apr 11, 2022
1 parent 5f8bb0d commit c46caed
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 0 deletions.
7 changes: 7 additions & 0 deletions authenticator_kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,10 @@ func (k8sauth *kubernetesAuthenticator) AuthenticateRequest(r *http.Request) (*a

return resp, found, err
}

// The Kubernetes Authenticator implements the Cacheable
// interface with the getCacheKey().
func (k8sauth *kubernetesAuthenticator) getCacheKey(r *http.Request) (string) {
return getBearerToken(r.Header.Get("Authorization"))

}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/gorilla/mux v1.7.3
github.com/gorilla/sessions v1.2.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
Expand Down
12 changes: 12 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
oidc "github.com/coreos/go-oidc"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
"github.com/tevino/abool"
"github.com/yosssi/boltstore/shared"
Expand All @@ -23,6 +24,7 @@ import (

// Issue: https://github.com/gorilla/sessions/issues/200
const secureCookieKeyPair = "notNeededBecauseCookieValueIsRandom"
const CacheCleanupInterval = 10

func main() {

Expand Down Expand Up @@ -161,6 +163,10 @@ func main() {
groupsClaim: c.GroupsClaim,
}

// Set the bearerUserInfoCache cache to store
// the (Bearer Token, UserInfo) pairs.
bearerUserInfoCache := cache.New(time.Duration(c.CacheExpirationMinutes)*time.Minute, time.Duration(CacheCleanupInterval)*time.Minute)

// Set the server values.
// The isReady atomic variable should protect it from concurrency issues.

Expand All @@ -170,6 +176,7 @@ func main() {
// TODO: Add support for Redis
store: store,
oidcStateStore: oidcStateStore,
bearerUserInfoCache: bearerUserInfoCache,
afterLoginRedirectURL: c.AfterLoginURL.String(),
homepageURL: c.HomepageURL.String(),
afterLogoutRedirectURL: c.AfterLogoutURL.String(),
Expand All @@ -186,6 +193,8 @@ func main() {
userIdTransformer: c.UserIDTransformer,
sessionMaxAgeSeconds: c.SessionMaxAge,
strictSessionValidation: c.StrictSessionValidation,
cacheEnabled: c.CacheEnabled,
cacheExpirationMinutes: c.CacheExpirationMinutes,
authHeader: c.AuthHeader,
caBundle: caBundle,
authenticators: []authenticator.Request{
Expand All @@ -205,6 +214,9 @@ func main() {
s.sessionSameSite = http.SameSiteLaxMode
}

// Print server configuration info
log.Infof("Cache enabled: %t", s.cacheEnabled)

// Setup complete, mark server ready
isReady.Set()

Expand Down
57 changes: 57 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import (
"encoding/gob"
"fmt"
"net/http"
"reflect"
"strings"
"time"

oidc "github.com/coreos/go-oidc"
"github.com/gorilla/sessions"
cache "github.com/patrickmn/go-cache"
"github.com/pkg/errors"
"github.com/tevino/abool"
"golang.org/x/oauth2"
Expand Down Expand Up @@ -43,13 +46,18 @@ type server struct {
oauth2Config *oauth2.Config
store sessions.Store
oidcStateStore sessions.Store
bearerUserInfoCache *cache.Cache
authenticators []authenticator.Request
authorizers []Authorizer
afterLoginRedirectURL string
homepageURL string
afterLogoutRedirectURL string
sessionMaxAgeSeconds int
strictSessionValidation bool

cacheEnabled bool
cacheExpirationMinutes int

authHeader string
idTokenOpts jwtClaimOpts
upstreamHTTPHeaderOpts httpHeaderOpts
Expand Down Expand Up @@ -80,7 +88,35 @@ func (s *server) authenticate(w http.ResponseWriter, r *http.Request) {
logger.Info("Authenticating request...")

var userInfo user.Info

for i, auth := range s.authenticators {
var cacheKey string

if s.cacheEnabled {
// If the cache is enabled, check if the current authenticator implements the Cacheable interface.
cacheable := reflect.TypeOf((*Cacheable)(nil)).Elem()
isCacheable := reflect.TypeOf(auth).Implements(cacheable)

if isCacheable {
// Store the key that we are going to use for caching UserDetails.
// We store it before the authentication, because the authenticators may mutate the request object.
logger.Debugf("Retrieving the cache key...")
cacheableAuthenticator := reflect.ValueOf(auth).Interface().(Cacheable)
cacheKey = cacheableAuthenticator.getCacheKey(r)
}
}

if cacheKey != "" {
// If caching is enabled, try to retrieve the UserInfo from cache.
userInfo = s.getCachedUserInfo(cacheKey, r)

if userInfo != nil {
logger.Infof("Successfully authenticated request using the cache.")
logger.Infof("UserInfo: %+v", userInfo)
break
}
}

logger.Infof("%s starting...", strings.Title(authenticatorsMapping[i]))
resp, found, err := auth.AuthenticateRequest(r)
if err != nil {
Expand All @@ -97,6 +133,12 @@ func (s *server) authenticate(w http.ResponseWriter, r *http.Request) {
logger.Infof("Successfully authenticated request using %s", authenticatorsMapping[i])
userInfo = resp.User
logger.Infof("UserInfo: %+v", userInfo)

if cacheKey != "" {
// If cache is enabled and the current authenticator is Cacheable, store the UserInfo to cache.
logger.Infof("Caching authenticated UserInfo...")
s.bearerUserInfoCache.Set(cacheKey, userInfo, time.Duration(s.cacheExpirationMinutes)*time.Minute)
}
break
}
}
Expand Down Expand Up @@ -149,6 +191,21 @@ func (s *server) authenticate(w http.ResponseWriter, r *http.Request) {
return
}

// getCachedUserInfo returns the UserInfo if it's in the cache
// using the key: 'cacheKey' or it returns nil.
func (s *server) getCachedUserInfo(cacheKey string, r *http.Request) user.Info {
logger := loggerForRequest(r, logModuleInfo)

cachedUserInfo, found := s.bearerUserInfoCache.Get(cacheKey)
if found {
userInfo := cachedUserInfo.(user.Info)
logger.Infof("Found Cached UserInfo: %+v", userInfo)
return userInfo
}
logger.Info("The UserInfo is not cached.")
return nil
}

// authCodeFlowAuthenticationRequest initiates an OIDC Authorization Code flow
func (s *server) authCodeFlowAuthenticationRequest(w http.ResponseWriter, r *http.Request) {
logger := loggerForRequest(r, logModuleInfo)
Expand Down
4 changes: 4 additions & 0 deletions settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ type config struct {
TemplatePath []string `split_words:"true"`
UserTemplateContext map[string]string `ignored:"true"`

// bearerUserInfoCache configuration
CacheEnabled bool `split_words:"true" default:"false" envconfig:"CACHE_ENABLED"`
CacheExpirationMinutes int `split_words:"true" default:"5" envconfig:"CACHE_EXPIRATION_MINUTES"`

// Authorization
GroupsAllowlist []string `split_words:"true" default:"*"`
}
Expand Down
4 changes: 4 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
)

type Cacheable interface {
getCacheKey(r *http.Request) string
}

func realpath(path string) (string, error) {
path, err := filepath.Abs(path)
if err != nil {
Expand Down

0 comments on commit c46caed

Please sign in to comment.