Skip to content

Commit

Permalink
Merge pull request #877 from fluxcd/token-cache-max-duration
Browse files Browse the repository at this point in the history
Add max duration to token cache
  • Loading branch information
matheuscscp authored Mar 4, 2025
2 parents ff04927 + 1ec89e0 commit 6be31fc
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 27 deletions.
11 changes: 8 additions & 3 deletions auth/github/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ func TestClient_Options(t *testing.T) {
}

func TestClient_GetToken(t *testing.T) {
g := NewWithT(t)

expiresAt := time.Now().UTC().Add(time.Hour)
tests := []struct {
name string
Expand All @@ -180,14 +182,17 @@ func TestClient_GetToken(t *testing.T) {
{
name: "Get cached token",
opts: []OptFunc{func(client *Client) {
c := cache.NewTokenCache(1)
c.GetOrSet(context.Background(), client.buildCacheKey(), func(context.Context) (cache.Token, error) {
c, err := cache.NewTokenCache(1)
g.Expect(err).NotTo(HaveOccurred())
_, ok, err := c.GetOrSet(context.Background(), client.buildCacheKey(), func(context.Context) (cache.Token, error) {
return &AppToken{
Token: "access-token",
ExpiresAt: expiresAt,
}, nil
})
client.cache = c
g.Expect(err).NotTo(HaveOccurred())
g.Expect(ok).To(BeFalse())
WithCache(c, "", "", "")(client)
}},
statusCode: http.StatusInternalServerError, // error status code to make the test fail if the token is not cached
wantAppToken: &AppToken{
Expand Down
2 changes: 1 addition & 1 deletion auth/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
github.com/bradleyfalzon/ghinstallation/v2 v2.14.0
github.com/fluxcd/pkg/cache v0.4.0
github.com/fluxcd/pkg/cache v0.5.0
github.com/fluxcd/pkg/ssh v0.17.0
github.com/onsi/gomega v1.36.2
golang.org/x/net v0.35.0
Expand Down
9 changes: 9 additions & 0 deletions cache/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type storeOptions struct {
interval time.Duration
registerer prometheus.Registerer
metricsPrefix string
maxDuration time.Duration
involvedObject *InvolvedObject
debugKey string
debugValueFunc func(any) any
Expand Down Expand Up @@ -88,6 +89,14 @@ func WithMetricsPrefix(prefix string) Options {
}
}

// WithMaxDuration sets the maximum duration for the cache items.
func WithMaxDuration(duration time.Duration) Options {
return func(o *storeOptions) error {
o.maxDuration = duration
return nil
}
}

// WithInvolvedObject sets the involved object for the cache metrics.
func WithInvolvedObject(kind, name, namespace string) Options {
return func(o *storeOptions) error {
Expand Down
29 changes: 25 additions & 4 deletions cache/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import (
"time"
)

// TokenMaxDuration is the maximum duration that a token can have in the
// TokenCache. This is used to cap the duration of tokens to avoid storing
// tokens that are valid for too long.
const TokenMaxDuration = time.Hour

// Token is an interface that represents an access token that can be used
// to authenticate with a cloud provider. The only common method is to get the
// duration of the token, because different providers may have different ways to
Expand All @@ -45,7 +50,8 @@ type Token interface {
// lifetime, which is the same strategy used by kubelet for rotating
// ServiceAccount tokens.
type TokenCache struct {
cache *LRU[*tokenItem]
cache *LRU[*tokenItem]
maxDuration time.Duration
}

type tokenItem struct {
Expand All @@ -55,9 +61,20 @@ type tokenItem struct {
}

// NewTokenCache returns a new TokenCache with the given capacity.
func NewTokenCache(capacity int, opts ...Options) *TokenCache {
cache, _ := NewLRU[*tokenItem](capacity, opts...)
return &TokenCache{cache: cache}
func NewTokenCache(capacity int, opts ...Options) (*TokenCache, error) {
o := storeOptions{maxDuration: TokenMaxDuration}
o.apply(opts...)

if o.maxDuration > TokenMaxDuration {
o.maxDuration = TokenMaxDuration
}

cache, err := NewLRU[*tokenItem](capacity, opts...)
if err != nil {
return nil, err
}

return &TokenCache{cache, o.maxDuration}, nil
}

// GetOrSet returns the token for the given key if present and not expired, or
Expand Down Expand Up @@ -112,6 +129,10 @@ func (c *TokenCache) newItem(token Token) *tokenItem {
// Ref: https://github.com/kubernetes/kubernetes/blob/4032177faf21ae2f99a2012634167def2376b370/pkg/kubelet/token/token_manager.go#L172-L174
d := (token.GetDuration() * 8) / 10

if m := c.maxDuration; d > m {
d = m
}

mono := time.Now().Add(d)
unix := time.Unix(mono.Unix(), 0)

Expand Down
107 changes: 100 additions & 7 deletions cache/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cache_test

import (
"context"
"fmt"
"testing"
"time"

Expand All @@ -35,11 +36,14 @@ func (t *testToken) GetDuration() time.Duration {
}

func TestTokenCache_Lifecycle(t *testing.T) {
t.Parallel()

g := NewWithT(t)

ctx := context.Background()

tc := cache.NewTokenCache(1)
tc, err := cache.NewTokenCache(1)
g.Expect(err).NotTo(HaveOccurred())

token, retrieved, err := tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) {
return &testToken{duration: 2 * time.Second}, nil
Expand All @@ -48,19 +52,108 @@ func TestTokenCache_Lifecycle(t *testing.T) {
g.Expect(retrieved).To(BeFalse())
g.Expect(err).To(BeNil())

time.Sleep(4 * time.Second)
token, retrieved, err = tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) { return nil, nil })

g.Expect(err).NotTo(HaveOccurred())
g.Expect(token).To(Equal(&testToken{duration: 2 * time.Second}))
g.Expect(retrieved).To(BeTrue())

time.Sleep(2 * time.Second)

token, retrieved, err = tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) {
return &testToken{duration: 100 * time.Second}, nil
return &testToken{duration: time.Hour}, nil
})
g.Expect(token).To(Equal(&testToken{duration: 100 * time.Second}))
g.Expect(token).To(Equal(&testToken{duration: time.Hour}))
g.Expect(retrieved).To(BeFalse())
g.Expect(err).To(BeNil())
}

time.Sleep(2 * time.Second)
func TestTokenCache_80PercentLifetime(t *testing.T) {
t.Parallel()

g := NewWithT(t)

ctx := context.Background()

tc, err := cache.NewTokenCache(1)
g.Expect(err).NotTo(HaveOccurred())

token, retrieved, err := tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) {
return &testToken{duration: 5 * time.Second}, nil
})

g.Expect(err).NotTo(HaveOccurred())
g.Expect(token).To(Equal(&testToken{duration: 5 * time.Second}))
g.Expect(retrieved).To(BeFalse())

token, retrieved, err = tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) { return nil, nil })
g.Expect(token).To(Equal(&testToken{duration: 100 * time.Second}))

g.Expect(err).NotTo(HaveOccurred())
g.Expect(token).To(Equal(&testToken{duration: 5 * time.Second}))
g.Expect(retrieved).To(BeTrue())
g.Expect(err).To(BeNil())

time.Sleep(4 * time.Second)

token, retrieved, err = tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) {
return &testToken{duration: time.Hour}, nil
})

g.Expect(err).NotTo(HaveOccurred())
g.Expect(token).To(Equal(&testToken{duration: time.Hour}))
g.Expect(retrieved).To(BeFalse())
}

func TestTokenCache_MaxDuration(t *testing.T) {
t.Parallel()

g := NewWithT(t)

ctx := context.Background()

tc, err := cache.NewTokenCache(1, cache.WithMaxDuration(time.Second))
g.Expect(err).NotTo(HaveOccurred())

token, retrieved, err := tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) {
return &testToken{duration: time.Hour}, nil
})

g.Expect(err).NotTo(HaveOccurred())
g.Expect(token).To(Equal(&testToken{duration: time.Hour}))
g.Expect(retrieved).To(BeFalse())

token, retrieved, err = tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) { return nil, nil })

g.Expect(err).NotTo(HaveOccurred())
g.Expect(token).To(Equal(&testToken{duration: time.Hour}))
g.Expect(retrieved).To(BeTrue())

time.Sleep(2 * time.Second)

token, retrieved, err = tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) {
return &testToken{duration: 10 * time.Millisecond}, nil
})

g.Expect(err).NotTo(HaveOccurred())
g.Expect(token).To(Equal(&testToken{duration: 10 * time.Millisecond}))
g.Expect(retrieved).To(BeFalse())
}

func TestTokenCache_GetOrSet_Error(t *testing.T) {
t.Parallel()

g := NewWithT(t)

ctx := context.Background()

tc, err := cache.NewTokenCache(1)
g.Expect(err).NotTo(HaveOccurred())

token, retrieved, err := tc.GetOrSet(ctx, "test", func(context.Context) (cache.Token, error) {
return nil, fmt.Errorf("failed")
})

g.Expect(err).To(HaveOccurred())
g.Expect(err).To(MatchError("failed"))
g.Expect(token).To(BeNil())
g.Expect(retrieved).To(BeFalse())
}
3 changes: 2 additions & 1 deletion git/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.23.0

replace (
github.com/fluxcd/pkg/auth => ../auth
github.com/fluxcd/pkg/cache => ../cache
github.com/fluxcd/pkg/ssh => ../ssh
)

Expand All @@ -24,7 +25,7 @@ require (
github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/fluxcd/pkg/cache v0.4.0 // indirect
github.com/fluxcd/pkg/cache v0.5.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
Expand Down
2 changes: 0 additions & 2 deletions git/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ 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/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/fluxcd/pkg/cache v0.4.0 h1:Jzy1dT6WNPE+srNM9aKQ4MQ5HKfYhHqaXlT99AvNL0M=
github.com/fluxcd/pkg/cache v0.4.0/go.mod h1:VNMLzJa62iDHfojoywykJ2pdUlgSjVzTLrOgkNlPxEo=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
Expand Down
3 changes: 2 additions & 1 deletion git/gogit/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.23.0

replace (
github.com/fluxcd/pkg/auth => ../../auth
github.com/fluxcd/pkg/cache => ../../cache
github.com/fluxcd/pkg/git => ../../git
github.com/fluxcd/pkg/gittestserver => ../../gittestserver
github.com/fluxcd/pkg/ssh => ../../ssh
Expand Down Expand Up @@ -40,7 +41,7 @@ require (
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fluxcd/pkg/cache v0.4.0 // indirect
github.com/fluxcd/pkg/cache v0.5.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
Expand Down
2 changes: 0 additions & 2 deletions git/gogit/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=
github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo=
github.com/fluxcd/pkg/cache v0.4.0 h1:Jzy1dT6WNPE+srNM9aKQ4MQ5HKfYhHqaXlT99AvNL0M=
github.com/fluxcd/pkg/cache v0.4.0/go.mod h1:VNMLzJa62iDHfojoywykJ2pdUlgSjVzTLrOgkNlPxEo=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
Expand Down
3 changes: 2 additions & 1 deletion git/internal/e2e/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.23.0

replace (
github.com/fluxcd/pkg/auth => ../../../auth
github.com/fluxcd/pkg/cache => ../../../cache
github.com/fluxcd/pkg/git => ../../../git
github.com/fluxcd/pkg/git/gogit => ../../gogit
github.com/fluxcd/pkg/gittestserver => ../../../gittestserver
Expand Down Expand Up @@ -41,7 +42,7 @@ require (
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fluxcd/gitkit v0.6.0 // indirect
github.com/fluxcd/pkg/cache v0.4.0 // indirect
github.com/fluxcd/pkg/cache v0.5.0 // indirect
github.com/fluxcd/pkg/version v0.6.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
Expand Down
2 changes: 0 additions & 2 deletions git/internal/e2e/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=
github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo=
github.com/fluxcd/go-git-providers v0.22.0 h1:THHUcCZkFQBWE5FlipRJMpUwA9v/Vj+tf965guSRKnU=
github.com/fluxcd/go-git-providers v0.22.0/go.mod h1:qkdWXdcA/9ILOkesIXB1rDtcvi3h1FgnmbCn84yYpTk=
github.com/fluxcd/pkg/cache v0.4.0 h1:Jzy1dT6WNPE+srNM9aKQ4MQ5HKfYhHqaXlT99AvNL0M=
github.com/fluxcd/pkg/cache v0.4.0/go.mod h1:VNMLzJa62iDHfojoywykJ2pdUlgSjVzTLrOgkNlPxEo=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
Expand Down
3 changes: 2 additions & 1 deletion oci/tests/integration/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.23.0

replace (
github.com/fluxcd/pkg/auth => ../../../auth
github.com/fluxcd/pkg/cache => ../../../cache
github.com/fluxcd/pkg/git => ../../../git
github.com/fluxcd/pkg/git/gogit => ../../../git/gogit
github.com/fluxcd/pkg/oci => ../../
Expand Down Expand Up @@ -65,7 +66,7 @@ require (
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fluxcd/pkg/cache v0.4.0 // indirect
github.com/fluxcd/pkg/cache v0.5.0 // indirect
github.com/fluxcd/pkg/ssh v0.17.0 // indirect
github.com/fluxcd/pkg/version v0.6.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
Expand Down
2 changes: 0 additions & 2 deletions oci/tests/integration/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg=
github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo=
github.com/fluxcd/pkg/cache v0.4.0 h1:Jzy1dT6WNPE+srNM9aKQ4MQ5HKfYhHqaXlT99AvNL0M=
github.com/fluxcd/pkg/cache v0.4.0/go.mod h1:VNMLzJa62iDHfojoywykJ2pdUlgSjVzTLrOgkNlPxEo=
github.com/fluxcd/pkg/gittestserver v0.16.0 h1:HXbxW6F24B3qgnkNm/UKz7Wpt1kKtmOsE2bVQUPWOhk=
github.com/fluxcd/pkg/gittestserver v0.16.0/go.mod h1:sGjpkv/X1NkJs43PSjlUxKTCit84Y1YyYn4U5ywBbFo=
github.com/fluxcd/pkg/ssh v0.17.0 h1:o+MgdM/OB8R/+KEc3W3ml/inEKZqCwT8V71dkbTAbm4=
Expand Down

0 comments on commit 6be31fc

Please sign in to comment.