github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/creds/vault/reauther.go (about)

     1  package vault
     2  
     3  import (
     4  	"code.cloudfoundry.org/lager"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/cenkalti/backoff"
     9  )
    10  
    11  // An Auther is anything which needs to be logged in and then have
    12  // that login renewed on a regulary basis.
    13  type Auther interface {
    14  	Login() (time.Duration, error)
    15  	Renew() (time.Duration, error)
    16  }
    17  
    18  // The ReAuther runs the authorization loop (login, renew) and retries
    19  // using a bounded exponential backoff strategy. If maxTTL is set, a
    20  // new login will be done _regardless_ of the available leaseDuration.
    21  type ReAuther struct {
    22  	auther Auther
    23  	base   time.Duration
    24  	max    time.Duration
    25  	maxTTL time.Duration
    26  
    27  	loggedIn     chan struct{}
    28  	loggedInOnce *sync.Once
    29  
    30  	closedCh chan struct{}
    31  
    32  	logger lager.Logger
    33  }
    34  
    35  // NewReAuther with a retry time and a max retry time.
    36  func NewReAuther(logger lager.Logger, auther Auther, maxTTL, retry, max time.Duration) *ReAuther {
    37  	ra := &ReAuther{
    38  		auther: auther,
    39  		base:   retry,
    40  		max:    max,
    41  		maxTTL: maxTTL,
    42  
    43  		loggedIn:     make(chan struct{}, 1),
    44  		loggedInOnce: &sync.Once{},
    45  
    46  		closedCh: make(chan struct{}, 1),
    47  
    48  		logger: logger,
    49  	}
    50  
    51  	go ra.authLoop()
    52  
    53  	return ra
    54  }
    55  
    56  // LoggedIn will receive a signal after every login. Multiple logins
    57  // may result in a single signal as this channel is not blocked.
    58  func (ra *ReAuther) LoggedIn() <-chan struct{} {
    59  	return ra.loggedIn
    60  }
    61  
    62  func (ra *ReAuther) Close() {
    63  	ra.logger.Debug("vault-reauther-close")
    64  	close(ra.closedCh)
    65  }
    66  
    67  // we can't renew a secret that has exceeded it's maxTTL or it's lease
    68  func (ra *ReAuther) renewable(leaseEnd, tokenEOL time.Time) bool {
    69  	now := time.Now()
    70  
    71  	if ra.maxTTL != 0 && now.After(tokenEOL) {
    72  		// token has exceeded the configured max TTL
    73  		return false
    74  	}
    75  
    76  	if now.After(leaseEnd) {
    77  		// token has exceeded its lease
    78  		return false
    79  	}
    80  
    81  	return true
    82  }
    83  
    84  // sleep until the tokenEOl or half the lease duration
    85  func (ra *ReAuther) sleep(leaseEnd, tokenEOL time.Time) {
    86  	if ra.maxTTL != 0 && leaseEnd.After(tokenEOL) {
    87  		time.Sleep(time.Until(tokenEOL))
    88  	} else {
    89  		time.Sleep(time.Until(leaseEnd) / 2)
    90  	}
    91  }
    92  
    93  func (ra *ReAuther) closed() bool {
    94  	select {
    95  	case <-ra.closedCh:
    96  		ra.logger.Debug("vault-reauther-closed")
    97  		return true
    98  	default: // default clause makes above channel non-blocking.
    99  	}
   100  	return false
   101  }
   102  
   103  func (ra *ReAuther) authLoop() {
   104  	var tokenEOL, leaseEnd time.Time
   105  
   106  	ra.logger.Debug("vault-reauther-started")
   107  	defer ra.logger.Debug("vault-reauther-terminated")
   108  
   109  	for {
   110  		exp := backoff.NewExponentialBackOff()
   111  		exp.InitialInterval = ra.base
   112  		exp.MaxInterval = ra.max
   113  		exp.MaxElapsedTime = 0
   114  		exp.Reset()
   115  
   116  		for {
   117  			if ra.closed() {
   118  				return
   119  			}
   120  
   121  			lease, err := ra.auther.Login()
   122  			if err != nil {
   123  				time.Sleep(exp.NextBackOff())
   124  				continue
   125  			}
   126  
   127  			exp.Reset()
   128  
   129  			ra.loggedInOnce.Do(func() {
   130  				close(ra.loggedIn)
   131  			})
   132  
   133  			now := time.Now()
   134  			tokenEOL = now.Add(ra.maxTTL)
   135  			leaseEnd = now.Add(lease)
   136  			ra.sleep(leaseEnd, tokenEOL)
   137  
   138  			break
   139  		}
   140  
   141  		for {
   142  			if ra.closed() {
   143  				return
   144  			}
   145  
   146  			if !ra.renewable(leaseEnd, tokenEOL) {
   147  				break
   148  			}
   149  
   150  			lease, err := ra.auther.Renew()
   151  			if err != nil {
   152  				time.Sleep(exp.NextBackOff())
   153  				continue
   154  			}
   155  
   156  			exp.Reset()
   157  
   158  			leaseEnd = time.Now().Add(lease)
   159  			ra.sleep(leaseEnd, tokenEOL)
   160  		}
   161  	}
   162  }