2022-03-20 17:47:52 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"os/user"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
log "github.com/golang/glog"
|
|
|
|
vapi "github.com/hashicorp/vault/api"
|
|
|
|
)
|
|
|
|
|
|
|
|
type TokenSecret = vapi.Secret
|
|
|
|
|
|
|
|
type ttledSecret struct {
|
|
|
|
*TokenSecret
|
|
|
|
|
|
|
|
expiration time.Time
|
|
|
|
|
|
|
|
isRenewable bool
|
|
|
|
renewThreshold time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
func wrapToken(s *TokenSecret) ttledSecret {
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
ttl := s.Auth.LeaseDuration
|
|
|
|
renewable := s.Auth.Renewable
|
|
|
|
|
|
|
|
var ttlBuffer time.Duration
|
|
|
|
if ttl < 300 {
|
|
|
|
// Give ourselves an extra two minute buffer for renewal.
|
|
|
|
ttlBuffer = 2 * time.Minute
|
|
|
|
}
|
|
|
|
|
2023-03-12 15:21:38 +00:00
|
|
|
// DEBUG: debugging some annoying renewal(?) issues with tokend
|
|
|
|
accessor, _ := s.TokenAccessor()
|
|
|
|
ttl, _ := s.TokenTTL()
|
|
|
|
log.Infof("wrapping token accessor %v with token TTL %v", accessor, ttl)
|
|
|
|
|
2022-03-20 17:47:52 +00:00
|
|
|
return ttledSecret{
|
|
|
|
TokenSecret: s,
|
|
|
|
|
|
|
|
expiration: now.Add(time.Duration(ttl) * time.Second),
|
|
|
|
|
|
|
|
isRenewable: renewable,
|
|
|
|
renewThreshold: now.Add(time.Duration(ttl/2) * time.Second).Add(-ttlBuffer),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s ttledSecret) Expired() bool {
|
|
|
|
// We use !After rather than Before so that if it's _exactly_ now then we still return true.
|
|
|
|
return !s.expiration.After(time.Now())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s ttledSecret) ShouldRenew() bool {
|
|
|
|
if !s.isRenewable {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Now().After(s.renewThreshold)
|
|
|
|
}
|
|
|
|
|
|
|
|
type tokenInteractor interface {
|
|
|
|
Revoke(ctx context.Context, tokenSecret *TokenSecret) error
|
|
|
|
Issue(ctx context.Context, username string, isPlainUser bool) (*TokenSecret, error)
|
|
|
|
Renew(ctx context.Context, tokenSecret *TokenSecret) (*TokenSecret, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type tokenUserCache struct {
|
|
|
|
l sync.RWMutex
|
|
|
|
m map[string]ttledSecret
|
|
|
|
i tokenInteractor
|
|
|
|
}
|
|
|
|
|
|
|
|
func newCache(i tokenInteractor) *tokenUserCache {
|
|
|
|
return &tokenUserCache{
|
|
|
|
m: make(map[string]ttledSecret),
|
|
|
|
i: i,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *tokenUserCache) expire() (revoke map[string]*TokenSecret) {
|
|
|
|
c.l.Lock()
|
|
|
|
defer c.l.Unlock()
|
|
|
|
|
|
|
|
var remove []string
|
|
|
|
revoke = make(map[string]*TokenSecret)
|
|
|
|
for username, s := range c.m {
|
|
|
|
// If the token has expired, remove it.
|
|
|
|
if s.Expired() {
|
|
|
|
remove = append(remove, username)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the user no longer exists on the system, revoke it.
|
|
|
|
if _, err := user.Lookup(username); err != nil {
|
|
|
|
log.Infof("token for %v will be revoked because user lookup returned error: %v", username, err)
|
|
|
|
remove = append(remove, username)
|
|
|
|
revoke[username] = s.TokenSecret
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, leave it alone.
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, username := range remove {
|
|
|
|
delete(c.m, username)
|
|
|
|
}
|
|
|
|
|
|
|
|
return revoke
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *tokenUserCache) renew() {
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
toRenew := make(map[string]*TokenSecret)
|
|
|
|
c.l.Lock()
|
|
|
|
for username, s := range c.m {
|
|
|
|
if s.ShouldRenew() {
|
|
|
|
toRenew[username] = s.TokenSecret
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.l.Unlock()
|
|
|
|
|
|
|
|
for username, s := range toRenew {
|
|
|
|
log.Infof("renewing token for %v", username)
|
|
|
|
newS, err := c.i.Renew(ctx, s)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("renewing token for %v: %w (discarding cached token)", username, err)
|
|
|
|
|
|
|
|
// Discard the token in cache.
|
|
|
|
c.l.Lock()
|
|
|
|
delete(c.m, username)
|
|
|
|
c.l.Unlock()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
c.l.Lock()
|
|
|
|
if oldS, ok := c.m[username]; ok && oldS.TokenSecret == s {
|
|
|
|
c.m[username] = wrapToken(newS)
|
|
|
|
} else if ok {
|
|
|
|
log.Warningf("after renewing token for %v discovered that the token in cache had changed in the meantime; dropping refreshed token")
|
|
|
|
}
|
|
|
|
c.l.Unlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *tokenUserCache) tick(ctx context.Context) error {
|
|
|
|
log.Info("token cache is ticking")
|
|
|
|
revoke := c.expire()
|
|
|
|
for username, tokenSecret := range revoke {
|
|
|
|
if err := c.i.Revoke(ctx, tokenSecret); err != nil {
|
|
|
|
log.Errorf("unable to revoke token for %v: %w", username, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.renew()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *tokenUserCache) Get(ctx context.Context, username string, isPlainUser bool) (*TokenSecret, error) {
|
|
|
|
c.l.RLock()
|
|
|
|
token, ok := c.m[username]
|
|
|
|
c.l.RUnlock()
|
|
|
|
|
|
|
|
// If we got a token, but it's expired, delete it and pretend it didn't exist.
|
|
|
|
if ok && token.Expired() {
|
|
|
|
c.l.Lock()
|
|
|
|
delete(c.m, username)
|
|
|
|
c.l.Unlock()
|
|
|
|
|
|
|
|
ok = false
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
// We have a non-expired pre-existing token!
|
|
|
|
return token.TokenSecret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Issue a new token.
|
|
|
|
issuedToken, err := c.i.Issue(ctx, username, isPlainUser)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// OK, now we check if someone has issued a token for us in the
|
|
|
|
// meantime... We could (but don't) do any more complex coordination
|
|
|
|
// than this, so we might actually issue two tokens for the same user
|
|
|
|
// concurrently. Even if we do this though, one of the tokens will
|
|
|
|
// "win" and we'll revoke the other one.
|
|
|
|
c.l.Lock()
|
|
|
|
defer c.l.Unlock()
|
|
|
|
|
|
|
|
if bgToken, ok := c.m[username]; ok && !bgToken.Expired() {
|
|
|
|
// Just use this one, I guess.
|
|
|
|
go c.i.Revoke(context.Background(), issuedToken)
|
|
|
|
return bgToken.TokenSecret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
token = wrapToken(issuedToken)
|
|
|
|
c.m[username] = token
|
|
|
|
|
|
|
|
return token.TokenSecret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *tokenUserCache) Purge(ctx context.Context, username string) {
|
|
|
|
c.l.Lock()
|
|
|
|
token, ok := c.m[username]
|
|
|
|
delete(c.m, username)
|
|
|
|
c.l.Unlock()
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Revoke the token as well, for good measure, in case the parent hasn't already done that.
|
|
|
|
c.i.Revoke(ctx, token.TokenSecret)
|
|
|
|
}
|