1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
package federation
import (
"context"
"time"
"github.com/spiffe/go-spiffe/v2/bundle/spiffebundle"
"github.com/spiffe/go-spiffe/v2/spiffeid"
)
// BundleWatcher is used by WatchBundle to provide the caller with bundle updates and
// control the next refresh time.
type BundleWatcher interface {
// NextRefresh is called by WatchBundle to determine when the next refresh
// should take place. A refresh hint is provided, which can be zero, meaning
// the watcher is free to choose its own refresh cadence. If the refresh hint
// is greater than zero, the watcher SHOULD return a next refresh time at or
// below that to ensure the bundle stays up-to-date.
NextRefresh(refreshHint time.Duration) time.Duration
// OnUpdate is called when a bundle has been updated. If a bundle is
// fetched but has not changed from the previously fetched bundle, OnUpdate
// will not be called. This function is called synchronously by WatchBundle
// and therefore should have a short execution time to prevent blocking the
// watch.
OnUpdate(*spiffebundle.Bundle)
// OnError is called if there is an error fetching the bundle from the
// endpoint. This function is called synchronously by WatchBundle
// and therefore should have a short execution time to prevent blocking the
// watch.
OnError(err error)
}
// WatchBundle watches a bundle on a bundle endpoint. It returns when the
// context is canceled, returning ctx.Err().
func WatchBundle(ctx context.Context, trustDomain spiffeid.TrustDomain, url string, watcher BundleWatcher, options ...FetchOption) error {
if watcher == nil {
return federationErr.New("watcher cannot be nil")
}
latestBundle := &spiffebundle.Bundle{}
var timer *time.Timer
for {
bundle, err := FetchBundle(ctx, trustDomain, url, options...)
switch {
// Context was canceled when fetching bundle, so to avoid
// more calls to FetchBundle (because the timer could be expired at
// this point) we return now.
case ctx.Err() == context.Canceled:
return ctx.Err()
case err != nil:
watcher.OnError(err)
case !latestBundle.Equal(bundle):
watcher.OnUpdate(bundle)
latestBundle = bundle
}
var nextRefresh time.Duration
if refreshHint, ok := latestBundle.RefreshHint(); ok {
nextRefresh = watcher.NextRefresh(refreshHint)
} else {
nextRefresh = watcher.NextRefresh(0)
}
if timer == nil {
timer = time.NewTimer(nextRefresh)
defer timer.Stop()
} else {
timer.Reset(nextRefresh)
}
select {
case <-timer.C:
case <-ctx.Done():
return ctx.Err()
}
}
}
|