Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/lifetime_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ func (r *LifetimeWatcher) doRenewWithOptions(tokenMode bool, nonRenewable bool,
InitialInterval: initialRetryInterval,
MaxInterval: 5 * time.Minute,
Multiplier: 2,
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}
errorBackoff.Reset()
Expand Down
47 changes: 47 additions & 0 deletions api/renewer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,50 @@ func TestCalcSleepPeriod(t *testing.T) {
t.Error(err)
}
}

// TestLifetimeWatcherErrorBackoffStops verifies that after the maximum elapsed
// time is exceeded during persistent renewal failures, the error backoff in the
// LifetimeWatcher correctly returns backoff.Stop so that the watcher terminates.
// This is a regression test for https://github.com/hashicorp/vault/issues/28611.
func TestLifetimeWatcherErrorBackoffStops(t *testing.T) {
t.Parallel()

client, err := NewClient(DefaultConfig())
if err != nil {
t.Fatal(err)
}

renewErr := fmt.Errorf("renew failure")

v, err := client.NewLifetimeWatcher(&LifetimeWatcherInput{
Secret: &Secret{
LeaseDuration: 3,
},
Increment: 3,
})
if err != nil {
t.Fatal(err)
}

doneCh := make(chan error, 1)
go func() {
// Use a very short initial retry interval so the backoff exhausts quickly.
doneCh <- v.doRenewWithOptions(false, false, 3, "myleaseID",
func(_ string, _ int) (*Secret, error) {
return nil, renewErr
}, 100*time.Millisecond)
}()
defer v.Stop()

select {
case <-time.After(30 * time.Second):
t.Fatal("timed out waiting for LifetimeWatcher to stop; " +
"error backoff may not be returning backoff.Stop correctly")
case gotErr := <-doneCh:
// The watcher should terminate. It may return either the renewal error
// (via the backoff.Stop path) or nil (via the grace period path).
// Prior to the fix, it would loop indefinitely with 0-duration sleeps
// and eventually return nil. The important thing is it terminates promptly.
_ = gotErr
}
}