mirror of
https://github.com/versity/versitygw.git
synced 2026-09-23 00:14:15 +00:00
auth: add GetUserAccountFresh to bypass the IAM cache
The cached GetUserAccount keeps serving a stale entry for the cache TTL after the backing IAM service changes, which delays credential revocation by up to the configured expiry. Add GetUserAccountFresh to the IAM cache, which reads directly from the underlying service and refreshes the cached entry with the result. Callers that need revocations to take effect immediately can use it instead of the cached path. Signed-off-by: Jihyeon Gim <potatogim@potatogim.net>
This commit is contained in:
@@ -177,6 +177,21 @@ func (c *IAMCache) ResolveAccounts(accessKeyIDs []string) ([]string, error) {
|
||||
return resolveAccountsByLookup(accessKeyIDs, c.GetUserAccount)
|
||||
}
|
||||
|
||||
// GetUserAccountFresh bypasses the in-memory cache and fetches the
|
||||
// account directly from the underlying IAM service, then refreshes
|
||||
// the cached entry with the result. This gives callers that need
|
||||
// revocation to take effect immediately (rather than after the
|
||||
// cache TTL) a way to observe the backing store state.
|
||||
func (c *IAMCache) GetUserAccountFresh(access string) (Account, error) {
|
||||
a, err := c.service.GetUserAccount(access)
|
||||
if err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
|
||||
c.iamcache.set(access, a)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// DeleteUserAccount deletes account from IAM service and cache
|
||||
func (c *IAMCache) DeleteUserAccount(access string) error {
|
||||
err := c.service.DeleteUserAccount(access)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type freshTestService struct {
|
||||
IAMService
|
||||
accounts map[string]Account
|
||||
fetches int
|
||||
}
|
||||
|
||||
func (s *freshTestService) GetUserAccount(access string) (Account, error) {
|
||||
s.fetches++
|
||||
a, ok := s.accounts[access]
|
||||
if !ok {
|
||||
return Account{}, ErrNoSuchUser
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// GetUserAccountFresh must bypass a cached-but-stale entry and
|
||||
// reflect the backing service state, refreshing the cache.
|
||||
func TestIAMCacheGetUserAccountFresh(t *testing.T) {
|
||||
svc := &freshTestService{accounts: map[string]Account{
|
||||
"acct": {Access: "acct", Secret: "old", Role: RoleUser},
|
||||
}}
|
||||
c := NewCache(svc, time.Minute, time.Hour)
|
||||
defer c.Shutdown()
|
||||
|
||||
// Prime the cache with the old secret.
|
||||
if _, err := c.GetUserAccount("acct"); err != nil {
|
||||
t.Fatalf("prime: %v", err)
|
||||
}
|
||||
|
||||
// Change the backing store behind the cache.
|
||||
svc.accounts["acct"] = Account{Access: "acct", Secret: "new", Role: RoleAdmin}
|
||||
|
||||
// Cached read still serves the stale entry.
|
||||
got, err := c.GetUserAccount("acct")
|
||||
if err != nil {
|
||||
t.Fatalf("cached read: %v", err)
|
||||
}
|
||||
if got.Secret != "old" {
|
||||
t.Fatalf("cached read: expected stale secret, got %q", got.Secret)
|
||||
}
|
||||
|
||||
// Fresh read must observe the update.
|
||||
got, err = c.GetUserAccountFresh("acct")
|
||||
if err != nil {
|
||||
t.Fatalf("fresh read: %v", err)
|
||||
}
|
||||
if got.Secret != "new" || got.Role != RoleAdmin {
|
||||
t.Fatalf("fresh read: got %+v", got)
|
||||
}
|
||||
|
||||
// And the cache is refreshed as a side effect.
|
||||
got, err = c.GetUserAccount("acct")
|
||||
if err != nil {
|
||||
t.Fatalf("post-fresh cached read: %v", err)
|
||||
}
|
||||
if got.Secret != "new" {
|
||||
t.Fatalf("post-fresh cached read: cache not refreshed, got %q", got.Secret)
|
||||
}
|
||||
|
||||
if svc.fetches != 2 {
|
||||
t.Fatalf("expected 2 backing fetches (prime + fresh), got %d", svc.fetches)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user