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:
+12
-5
@@ -6,12 +6,15 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/tsweb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -39,11 +42,15 @@ func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
pair, err := h.b.GetCertPEMWithValidity(r.Context(), domain, minValidity)
|
||||
if err != nil {
|
||||
// TODO(bradfitz): 500 is a little lazy here. The errors returned from
|
||||
// GetCertPEM (and everywhere) should carry info to get whether
|
||||
// they're 400 vs 403 vs 500 at minimum. And then we should have helpers
|
||||
// (in tsweb probably) to return an error that looks at the error value
|
||||
// to determine the HTTP status code.
|
||||
if hs, ok := errors.AsType[tsweb.HTTPStatuser](err); ok {
|
||||
resp := hs.HTTPStatus()
|
||||
maps.Copy(w.Header(), resp.Header)
|
||||
http.Error(w, resp.Msg, resp.Code)
|
||||
return
|
||||
}
|
||||
// TODO(bradfitz): 500 is a little lazy. Other errors from GetCertPEM
|
||||
// should also implement tsweb.HTTPStatuser (400 vs 403 vs 500 vs …)
|
||||
// rather than falling through here.
|
||||
http.Error(w, fmt.Sprint(err), 500)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user