Skip to content

TokenCache can return authenticated=true after JWT expiry due to sliding TTL (does not check exp) #375

@HarshitPal25

Description

@HarshitPal25

Summary

TokenCache.Get in pkg/workloadmanager/client_cache.go only checks how long ago the cache entry was last accessed (time.Since(entry.lastAccess) > c.ttl). Every time a cached token is used, lastAccess is reset to time.Now(), continuously pushing the expiry window forward. The token's actual JWT exp claim is never checked on cache hits. By contrast, ClientCache.Get (which caches K8s user clients) correctly parses the JWT expiry via parseJWTExpiry and evicts entries once the token has expired. TokenCache has parseJWTExpiry available but does not use it.

Affected code

func (c *TokenCache) Get(token string) (found bool, authenticated bool, username string) {
	c.mu.Lock()
	defer c.mu.Unlock()
 
	entry, exists := c.cache[token]
	if !exists {
		return false, false, ""
	}
 
	// Only checks sliding lastAccess — token's real JWT exp is neververified
	if time.Since(entry.lastAccess) > c.ttl {
		c.lruList.Remove(entry.element)
		delete(c.cache, token)
		return false, false, ""
	}
 
	// Resets the window — a busy token never expires from cache
	entry.lastAccess = time.Now()
	c.lruList.MoveToFront(entry.element)
	return true, entry.authenticated, entry.username
}

Reproduction scenario

  1. Use a K8s service-account token with a 1-minute lifetime.
  2. Validate it once so it gets cached in TokenCache as authenticated=true.
  3. Send requests every 4 minutes.
    • Each hit updates lastAccess, keeping the cache entry alive.
  4. After the token expires , TokenCache continues to return the cached authenticated=true result until there is a full 5-minute gap with no requests.

Expected behavior

TokenCache.Get should not return a cached authentication result once the JWT has expired (based on exp), even if requests keep arriving within the TTL window.

Actual behavior

TokenCache.Get can continue returning authenticated=true after the token has expired because only sliding lastAccess TTL is enforced.

Suggested fix

Store and enforce JWT expiry in tokenCacheEntry similar to clientCacheEntry:

Metadata

Metadata

Assignees

Labels

kind/bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions