sr: Avoid recursion when loading site replicator credentials (#20262)

If the site replication is enabled and the code tries to extract jwt
claims while the site replication service account credentials are still
not loaded yet, the code will enter an infinite loop, causing in a
high CPU usage.

Another possibility of the infinite loop is having some service accounts
created by an old deployment version where the service account JWT was
signed by the root credentials, but not anymore.

This commit will remove the possibility of the infinite loop in the code
and add root credential fallback to extract claims from old service
accounts.
This commit is contained in:
Anis Eleuch 2024-08-15 02:29:20 +01:00 committed by GitHub
parent db78431b1d
commit b508264ac4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 66 additions and 30 deletions

View File

@ -249,6 +249,18 @@ func (ies *IAMEtcdStore) addUser(ctx context.Context, user string, userType IAMU
return nil return nil
} }
func (ies *IAMEtcdStore) loadSecretKey(ctx context.Context, user string, userType IAMUserType) (string, error) {
var u UserIdentity
err := ies.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
if err != nil {
if errors.Is(err, errConfigNotFound) {
return "", errNoSuchUser
}
return "", err
}
return u.Credentials.SecretKey, nil
}
func (ies *IAMEtcdStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error { func (ies *IAMEtcdStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error {
var u UserIdentity var u UserIdentity
err := ies.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType)) err := ies.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))

View File

@ -225,6 +225,18 @@ func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]Po
return nil return nil
} }
func (iamOS *IAMObjectStore) loadSecretKey(ctx context.Context, user string, userType IAMUserType) (string, error) {
var u UserIdentity
err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))
if err != nil {
if errors.Is(err, errConfigNotFound) {
return "", errNoSuchUser
}
return "", err
}
return u.Credentials.SecretKey, nil
}
func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error { func (iamOS *IAMObjectStore) loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error {
var u UserIdentity var u UserIdentity
err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType)) err := iamOS.loadIAMConfig(ctx, &u, getUserIdentityPath(user, userType))

View File

@ -535,6 +535,7 @@ type IAMStorageAPI interface {
loadPolicyDocWithRetry(ctx context.Context, policy string, m map[string]PolicyDoc, retries int) error loadPolicyDocWithRetry(ctx context.Context, policy string, m map[string]PolicyDoc, retries int) error
loadPolicyDocs(ctx context.Context, m map[string]PolicyDoc) error loadPolicyDocs(ctx context.Context, m map[string]PolicyDoc) error
loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error
loadSecretKey(ctx context.Context, user string, userType IAMUserType) (string, error)
loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error
loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error
loadGroups(ctx context.Context, m map[string]GroupInfo) error loadGroups(ctx context.Context, m map[string]GroupInfo) error
@ -2820,20 +2821,29 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) error
return err return err
} }
func extractJWTClaims(u UserIdentity) (*jwt.MapClaims, error) { func extractJWTClaims(u UserIdentity) (jwtClaims *jwt.MapClaims, err error) {
jwtClaims, err := auth.ExtractClaims(u.Credentials.SessionToken, u.Credentials.SecretKey) keys := make([]string, 0, 3)
if err != nil { // Append credentials secret key itself
secretKey, err := getTokenSigningKey() keys = append(keys, u.Credentials.SecretKey)
if err != nil { // Use site-replication credentials if found
return nil, err if globalSiteReplicationSys.isEnabled() {
siteReplSecretKey, e := globalSiteReplicatorCred.Get(GlobalContext)
if e != nil {
return nil, e
} }
// Session tokens for STS creds will be generated with root secret or site-replicator-0 secret keys = append(keys, siteReplSecretKey)
jwtClaims, err = auth.ExtractClaims(u.Credentials.SessionToken, secretKey) }
if err != nil { // Use root credentials for credentials created with older deployments
return nil, err keys = append(keys, globalActiveCred.SecretKey)
// Iterate over all keys and return with the first successful claim extraction
for _, key := range keys {
jwtClaims, err = auth.ExtractClaims(u.Credentials.SessionToken, key)
if err == nil {
break
} }
} }
return jwtClaims, nil return
} }
func validateSvcExpirationInUTC(expirationInUTC time.Time) error { func validateSvcExpirationInUTC(expirationInUTC time.Time) error {

View File

@ -211,7 +211,7 @@ func (sys *IAMSys) Load(ctx context.Context, firstTime bool) error {
if !globalSiteReplicatorCred.IsValid() { if !globalSiteReplicatorCred.IsValid() {
sa, _, err := sys.getServiceAccount(ctx, siteReplicatorSvcAcc) sa, _, err := sys.getServiceAccount(ctx, siteReplicatorSvcAcc)
if err == nil { if err == nil {
globalSiteReplicatorCred.Set(sa.Credentials) globalSiteReplicatorCred.Set(sa.Credentials.SecretKey)
} }
} }

View File

@ -597,7 +597,7 @@ func (c *SiteReplicationSys) AddPeerClusters(ctx context.Context, psites []madmi
} }
if !globalSiteReplicatorCred.IsValid() { if !globalSiteReplicatorCred.IsValid() {
globalSiteReplicatorCred.Set(svcCred) globalSiteReplicatorCred.Set(svcCred.SecretKey)
} }
result := madmin.ReplicateAddStatus{ result := madmin.ReplicateAddStatus{
Success: true, Success: true,
@ -659,7 +659,7 @@ func (c *SiteReplicationSys) PeerJoinReq(ctx context.Context, arg madmin.SRPeerJ
return errSRBackendIssue(fmt.Errorf("unable to save cluster-replication state to drive on %s: %v", ourName, err)) return errSRBackendIssue(fmt.Errorf("unable to save cluster-replication state to drive on %s: %v", ourName, err))
} }
if !globalSiteReplicatorCred.IsValid() { if !globalSiteReplicatorCred.IsValid() {
globalSiteReplicatorCred.Set(sa) globalSiteReplicatorCred.Set(sa.SecretKey)
} }
return nil return nil
@ -6269,34 +6269,36 @@ func ilmExpiryReplicationEnabled(sites map[string]madmin.PeerInfo) bool {
} }
type siteReplicatorCred struct { type siteReplicatorCred struct {
Creds auth.Credentials secretKey string
sync.RWMutex sync.RWMutex
} }
// Get or attempt to load site replicator credentials from disk. // Get or attempt to load site replicator credentials from disk.
func (s *siteReplicatorCred) Get(ctx context.Context) (auth.Credentials, error) { func (s *siteReplicatorCred) Get(ctx context.Context) (string, error) {
s.RLock() s.RLock()
if s.Creds.IsValid() { secretKey := s.secretKey
s.RUnlock() s.RUnlock()
return s.Creds, nil
if secretKey != "" {
return secretKey, nil
} }
s.RUnlock()
m := make(map[string]UserIdentity) secretKey, err := globalIAMSys.store.loadSecretKey(ctx, siteReplicatorSvcAcc, svcUser)
if err := globalIAMSys.store.loadUser(ctx, siteReplicatorSvcAcc, svcUser, m); err != nil { if err != nil {
return auth.Credentials{}, err return "", err
} }
s.Set(m[siteReplicatorSvcAcc].Credentials) s.Set(secretKey)
return m[siteReplicatorSvcAcc].Credentials, nil return secretKey, nil
} }
func (s *siteReplicatorCred) Set(c auth.Credentials) { func (s *siteReplicatorCred) Set(secretKey string) {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
s.Creds = c s.secretKey = secretKey
} }
func (s *siteReplicatorCred) IsValid() bool { func (s *siteReplicatorCred) IsValid() bool {
s.RLock() s.RLock()
defer s.RUnlock() defer s.RUnlock()
return s.Creds.IsValid() return s.secretKey != ""
} }

View File

@ -241,11 +241,11 @@ func parseForm(r *http.Request) error {
func getTokenSigningKey() (string, error) { func getTokenSigningKey() (string, error) {
secret := globalActiveCred.SecretKey secret := globalActiveCred.SecretKey
if globalSiteReplicationSys.isEnabled() { if globalSiteReplicationSys.isEnabled() {
c, err := globalSiteReplicatorCred.Get(GlobalContext) secretKey, err := globalSiteReplicatorCred.Get(GlobalContext)
if err != nil { if err != nil {
return "", err return "", err
} }
return c.SecretKey, nil return secretKey, nil
} }
return secret, nil return secret, nil
} }