-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
494 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
package api | ||
|
||
import ( | ||
"errors" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// RenewerInput is used as input to the renew function. | ||
type RenewerInput struct { | ||
// Secret is the secret to renew | ||
Secret *Secret | ||
|
||
// Grace is a minimum renewal (in seconds) before returring so the upstream | ||
// client can do a re-read. This can be used to prevent clients from waiting | ||
// too long to read a new credential and incur downtime. | ||
Grace int | ||
} | ||
|
||
// Renewer is a process for renewing a secret. | ||
// | ||
// renewer, err := client.NewRenewer(&RenewerInput{ | ||
// Secret: mySecret, | ||
// }) | ||
// go renewer.Renew() | ||
// defer renewer.Stop() | ||
// | ||
// for { | ||
// select { | ||
// case err := <-DoneCh(): | ||
// if err != nil { | ||
// log.Fatal(err) | ||
// } | ||
// | ||
// // Renewal is now over | ||
// case <-TickCh(): | ||
// log.Println("Successfully renewed") | ||
// default: | ||
// } | ||
// } | ||
// | ||
// | ||
// The `DoneCh` will return if renewal fails or if the remaining lease duration | ||
// after a renewal is less than or equal to the grace (in number of seconds). In | ||
// both cases, the caller should attempt a re-read of the secret. Clients should | ||
// check the return value of the channel to see if renewal was successful. | ||
type Renewer struct { | ||
sync.Mutex | ||
|
||
client *Client | ||
secret *Secret | ||
grace int | ||
doneCh chan error | ||
tickCh chan struct{} | ||
|
||
stopped bool | ||
stopCh chan struct{} | ||
} | ||
|
||
var ( | ||
ErrRenewerMissingInput = errors.New("missing input to renewer") | ||
ErrRenewerMissingSecret = errors.New("missing secret to renew") | ||
ErrRenewerNotRenewable = errors.New("secret is not renewable") | ||
ErrRenewerNoSecretData = errors.New("returned empty secret data") | ||
|
||
// DefaultRenewerGrace is the default grace period | ||
DefaultRenewerGrace = 15 | ||
) | ||
|
||
// NewRenewer creates a new renewer from the given input. | ||
func (c *Client) NewRenewer(i *RenewerInput) (*Renewer, error) { | ||
if i == nil { | ||
return nil, ErrRenewerMissingInput | ||
} | ||
|
||
secret := i.Secret | ||
if secret == nil { | ||
return nil, ErrRenewerMissingSecret | ||
} | ||
|
||
grace := i.Grace | ||
if grace == 0 { | ||
grace = DefaultRenewerGrace | ||
} | ||
|
||
return &Renewer{ | ||
client: c, | ||
secret: secret, | ||
grace: grace, | ||
doneCh: make(chan error, 1), | ||
tickCh: make(chan struct{}, 5), | ||
|
||
stopped: false, | ||
stopCh: make(chan struct{}, 1), | ||
}, nil | ||
} | ||
|
||
// DoneCh returns the channel where the renewer will publish when renewal stops. | ||
// If there is an error, this will be an error. | ||
func (r *Renewer) DoneCh() <-chan error { | ||
return r.doneCh | ||
} | ||
|
||
// TickCh is a channel that receives a message when a successful renewal takes | ||
// place. | ||
func (r *Renewer) TickCh() <-chan struct{} { | ||
return r.tickCh | ||
} | ||
|
||
// Stop stops the renewer. | ||
func (r *Renewer) Stop() { | ||
r.Lock() | ||
if !r.stopped { | ||
close(r.stopCh) | ||
r.stopped = true | ||
} | ||
r.Unlock() | ||
} | ||
|
||
// Renew starts a background process for renewing this secret. When the secret | ||
// is has auth data, this attempts to renew the auth (token). When the secret | ||
// has a lease, this attempts to renew the lease. | ||
// | ||
// This function will not return if nothing is reading from doneCh (it blocks) | ||
// on a write to the channel. | ||
func (r *Renewer) Renew() { | ||
if r.secret.Auth != nil { | ||
r.doneCh <- r.renewAuth() | ||
} else { | ||
r.doneCh <- r.renewLease() | ||
} | ||
} | ||
|
||
// renewAuth is a helper for renewing authentication. | ||
func (r *Renewer) renewAuth() error { | ||
if !r.secret.Auth.Renewable || r.secret.Auth.ClientToken == "" { | ||
return ErrRenewerNotRenewable | ||
} | ||
|
||
client, token := r.client, r.secret.Auth.ClientToken | ||
|
||
for { | ||
// Check if we are stopped. | ||
select { | ||
case <-r.stopCh: | ||
return nil | ||
default: | ||
} | ||
|
||
// Renew the auth. | ||
renewal, err := client.Auth().Token().RenewSelfAsToken(token, 0) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Push a message that a renewal took place. | ||
select { | ||
case r.tickCh <- struct{}{}: | ||
default: | ||
} | ||
|
||
// Somehow, sometimes, this happens. | ||
if renewal == nil || renewal.Auth == nil { | ||
return ErrRenewerNoSecretData | ||
} | ||
|
||
// Do nothing if we are not renewable | ||
if !renewal.Auth.Renewable { | ||
return ErrRenewerNotRenewable | ||
} | ||
|
||
// Grab the lease duration - note that we grab the auth lease duration, not | ||
// the secret lease duration. | ||
leaseDuration := renewal.Auth.LeaseDuration | ||
|
||
// If we are within grace, return now. | ||
if leaseDuration <= r.grace { | ||
return nil | ||
} | ||
|
||
select { | ||
case <-r.stopCh: | ||
return nil | ||
case <-time.After(time.Duration(leaseDuration/2.0) * time.Second): | ||
continue | ||
} | ||
} | ||
} | ||
|
||
// renewLease is a helper for renewing a lease. | ||
func (r *Renewer) renewLease() error { | ||
if !r.secret.Renewable || r.secret.LeaseID == "" { | ||
return ErrRenewerNotRenewable | ||
} | ||
|
||
client, leaseID := r.client, r.secret.LeaseID | ||
|
||
for { | ||
// Check if we are stopped. | ||
select { | ||
case <-r.stopCh: | ||
return nil | ||
default: | ||
} | ||
|
||
// Renew the lease. | ||
renewal, err := client.Sys().Renew(leaseID, 0) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Push a message that a renewal took place. | ||
select { | ||
case r.tickCh <- struct{}{}: | ||
default: | ||
} | ||
|
||
// Somehow, sometimes, this happens. | ||
if renewal == nil { | ||
return ErrRenewerNoSecretData | ||
} | ||
|
||
// Do nothing if we are not renewable | ||
if !renewal.Renewable { | ||
return ErrRenewerNotRenewable | ||
} | ||
|
||
// Grab the lease duration | ||
leaseDuration := renewal.LeaseDuration | ||
|
||
// If we are within grace, return now. | ||
if leaseDuration <= r.grace { | ||
return nil | ||
} | ||
|
||
select { | ||
case <-r.stopCh: | ||
return nil | ||
case <-time.After(time.Duration(leaseDuration/2.0) * time.Second): | ||
continue | ||
} | ||
} | ||
} |
Oops, something went wrong.