ipn: add support for HTTP Redirects (#17594)

Adds a new Redirect field to HTTPHandler for serving HTTP redirects
from the Tailscale serve config. The redirect URL supports template
variables ${HOST} and ${REQUEST_URI} that are resolved per request.

By default, it redirects using HTTP Status 302 (Found). For another
redirect status, like 301 - Moved Permanently, pass the HTTP status
code followed by ':' on Redirect, like: "301:https://tailscale.com"

Updates #11252
Updates #11330

Signed-off-by: Fernando Serboncini <fserb@tailscale.com>
This commit is contained in:
Fernando Serboncini
2025-10-29 21:27:59 -04:00
committed by GitHub
parent 05d2dcaf49
commit d68513b0db
5 changed files with 168 additions and 1 deletions
+20
View File
@@ -966,6 +966,19 @@ func (b *LocalBackend) addAppCapabilitiesHeader(r *httputil.ProxyRequest) error
return nil
}
// parseRedirectWithCode parses a redirect string that may optionally start with
// a HTTP redirect status code ("3xx:").
// Returns the status code and the final redirect URL.
// If no code prefix is found, returns http.StatusFound (302).
func parseRedirectWithCode(redirect string) (code int, url string) {
if len(redirect) >= 4 && redirect[3] == ':' {
if statusCode, err := strconv.Atoi(redirect[:3]); err == nil && statusCode >= 300 && statusCode <= 399 {
return statusCode, redirect[4:]
}
}
return http.StatusFound, redirect
}
// serveWebHandler is an http.HandlerFunc that maps incoming requests to the
// correct *http.
func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
@@ -979,6 +992,13 @@ func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, s)
return
}
if v := h.Redirect(); v != "" {
code, v := parseRedirectWithCode(v)
v = strings.ReplaceAll(v, "${HOST}", r.Host)
v = strings.ReplaceAll(v, "${REQUEST_URI}", r.RequestURI)
http.Redirect(w, r, v, code)
return
}
if v := h.Path(); v != "" {
b.serveFileOrDirectory(w, r, v, mountPoint)
return