Wait on in-flight OIDC discovery instead of returning 503
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

This commit is contained in:
2026-10-14 22:31:34 +11:00
parent 51a77e3446
commit 98ad145b6f
2 changed files with 67 additions and 14 deletions
+31 -14
View File
@@ -22,10 +22,10 @@ type BearerOIDC struct {
timeout time.Duration
backoff time.Duration
mu sync.Mutex
verifier *oidc.IDTokenVerifier
discovering bool
failedAt time.Time
mu sync.Mutex
verifier *oidc.IDTokenVerifier
inflight chan struct{}
failedAt time.Time
}
var errIssuerUnavailable = errors.New("oidc issuer unavailable")
@@ -39,18 +39,34 @@ func NewBearerOIDC(issuer string, audiences []string) *BearerOIDC {
}
// getVerifier runs at most one discovery at a time without holding the lock
// across network I/O; callers arriving mid-discovery or within the backoff fail fast.
func (b *BearerOIDC) getVerifier() (*oidc.IDTokenVerifier, error) {
// across network I/O; concurrent callers wait for it, and only callers within
// the post-failure backoff fail fast.
func (b *BearerOIDC) getVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) {
b.mu.Lock()
if b.verifier != nil || b.discovering || time.Since(b.failedAt) < b.backoff {
v := b.verifier
if v := b.verifier; v != nil {
b.mu.Unlock()
if v == nil {
return nil, errIssuerUnavailable
}
return v, nil
}
b.discovering = true
if done := b.inflight; done != nil {
b.mu.Unlock()
select {
case <-done:
case <-ctx.Done():
return nil, ctx.Err()
}
b.mu.Lock()
defer b.mu.Unlock()
if b.verifier == nil {
return nil, errIssuerUnavailable
}
return b.verifier, nil
}
if time.Since(b.failedAt) < b.backoff {
b.mu.Unlock()
return nil, errIssuerUnavailable
}
done := make(chan struct{})
b.inflight = done
b.mu.Unlock()
// The provider keeps this context and client for JWKS refreshes, so the
@@ -60,7 +76,8 @@ func (b *BearerOIDC) getVerifier() (*oidc.IDTokenVerifier, error) {
b.mu.Lock()
defer b.mu.Unlock()
b.discovering = false
b.inflight = nil
defer close(done)
if err != nil {
b.failedAt = time.Now()
slog.Error("oidc discovery failed", "issuer", b.issuer, "error", err)
@@ -83,7 +100,7 @@ func (b *BearerOIDC) Require(next http.Handler) http.Handler {
unauthorized(w, "missing bearer token")
return
}
v, err := b.getVerifier()
v, err := b.getVerifier(r.Context())
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)