open-consul/agent/consul/acl_token_exp.go
Dan Upton 6bfdb48560
acl: gRPC login and logout endpoints (#12935)
Introduces two new public gRPC endpoints (`Login` and `Logout`) and
includes refactoring of the equivalent net/rpc endpoints to enable the
majority of logic to be reused (i.e. by extracting the `Binder` and
`TokenWriter` types).

This contains the OSS portions of the following enterprise commits:

- 75fcdbfcfa6af21d7128cb2544829ead0b1df603
- bce14b714151af74a7f0110843d640204082630a
- cc508b70fbf58eda144d9af3d71bd0f483985893
2022-05-04 17:38:45 +01:00

123 lines
3 KiB
Go

package consul
import (
"context"
"fmt"
"time"
"golang.org/x/time/rate"
"github.com/hashicorp/consul/agent/structs"
)
func (s *Server) reapExpiredTokens(ctx context.Context) error {
limiter := rate.NewLimiter(aclTokenReapingRateLimit, aclTokenReapingBurst)
for {
if err := limiter.Wait(ctx); err != nil {
return err
}
if s.LocalTokensEnabled() {
if _, err := s.reapExpiredLocalACLTokens(); err != nil {
s.logger.Error("error reaping expired local ACL tokens", "error", err)
}
}
if s.InPrimaryDatacenter() {
if _, err := s.reapExpiredGlobalACLTokens(); err != nil {
s.logger.Error("error reaping expired global ACL tokens", "error", err)
}
}
}
}
func (s *Server) startACLTokenReaping(ctx context.Context) {
// Do a quick check for config settings that would imply the goroutine
// below will just spin forever.
//
// We can only check the config settings here that cannot change without a
// restart, so we omit the check for a non-empty replication token as that
// can be changed at runtime.
if !s.InPrimaryDatacenter() && !s.config.ACLTokenReplication {
return
}
s.leaderRoutineManager.Start(ctx, aclTokenReapingRoutineName, s.reapExpiredTokens)
}
func (s *Server) stopACLTokenReaping() {
s.leaderRoutineManager.Stop(aclTokenReapingRoutineName)
}
func (s *Server) reapExpiredGlobalACLTokens() (int, error) {
return s.reapExpiredACLTokens(false, true)
}
func (s *Server) reapExpiredLocalACLTokens() (int, error) {
return s.reapExpiredACLTokens(true, false)
}
func (s *Server) reapExpiredACLTokens(local, global bool) (int, error) {
if !s.config.ACLsEnabled {
return 0, nil
}
if local == global {
return 0, fmt.Errorf("cannot reap both local and global tokens in the same request")
}
locality := localityName(local)
minExpiredTime, err := s.fsm.State().ACLTokenMinExpirationTime(local)
if err != nil {
return 0, err
}
now := time.Now()
if minExpiredTime.After(now) {
return 0, nil // nothing to do
}
tokens, _, err := s.fsm.State().ACLTokenListExpired(local, now, aclBatchDeleteSize)
if err != nil {
return 0, err
}
if len(tokens) == 0 {
return 0, nil
}
var (
secretIDs []string
req structs.ACLTokenBatchDeleteRequest
)
for _, token := range tokens {
if token.Local != local {
return 0, fmt.Errorf("expired index for local=%v returned a mismatched token with local=%v: %s", local, token.Local, token.AccessorID)
}
req.TokenIDs = append(req.TokenIDs, token.AccessorID)
secretIDs = append(secretIDs, token.SecretID)
}
s.logger.Info("deleting expired ACL tokens",
"amount", len(req.TokenIDs),
"locality", locality,
)
_, err = s.leaderRaftApply("ACL.TokenDelete", structs.ACLTokenDeleteRequestType, &req)
if err != nil {
return 0, fmt.Errorf("Failed to apply token expiration deletions: %v", err)
}
// Purge the identities from the cache
for _, secretID := range secretIDs {
s.ACLResolver.cache.RemoveIdentityWithSecretToken(secretID)
}
return len(req.TokenIDs), nil
}
func localityName(local bool) string {
if local {
return "local"
}
return "global"
}