ipn/localapi,client/local: honour Retry-After on cert rate-limit (#20315)
* ipn/localapi,ipnlocal,feature/acme,client/local: honour Retry-After on cert rate-limit serveCert now responds with 429 + Retry-After when the underlying ACME error is a rate limit, instead of a generic 500. client/local surfaces this as a typed RateLimitedError with the parsed hint so callers can back off intelligently. Updates tailscale/corp#42164 Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk> * tsweb,feature/acme,ipn/localapi,ipnlocal: generalise cert error → HTTP mapping via tsweb.HTTPStatuser Introduces a tsweb.HTTPStatuser interface, any error can implement to describe its intended HTTP response (code, message, headers). Moves CertRateLimitedError from ipnlocal to feature/acme where it's constructed, and it now uses HTTPStatuser to return 429 + Retry-After. serveCert now checks for tsweb.HTTPStatuser rather than the specific error type, so it no longer needs to know about the ACME rate-limit type. Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk> --------- Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"tailscale.com/tsweb"
|
||||
)
|
||||
|
||||
// certRateLimitedError is returned when the upstream ACME CA rate-limited
|
||||
// the issuance. It exists so cert-fetching failures can surface as HTTP
|
||||
// 429 responses via [tsweb.HTTPStatuser].
|
||||
type certRateLimitedError struct {
|
||||
retryAfter time.Duration
|
||||
underlying error
|
||||
}
|
||||
|
||||
func (e certRateLimitedError) Error() string { return e.underlying.Error() }
|
||||
func (e certRateLimitedError) Unwrap() error { return e.underlying }
|
||||
|
||||
// HTTPStatus implements [tsweb.HTTPStatuser].
|
||||
func (e certRateLimitedError) HTTPStatus() tsweb.HTTPError {
|
||||
h := http.Header{}
|
||||
if e.retryAfter > 0 {
|
||||
h.Set("Retry-After", strconv.Itoa(int(e.retryAfter.Seconds())))
|
||||
}
|
||||
return tsweb.HTTPError{
|
||||
Code: http.StatusTooManyRequests,
|
||||
Msg: e.Error(),
|
||||
Header: h,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user