client/tailscale/apitype: add LocalAPIHost const, use it

Removes duplication.

Updates tailcale/corp#7948

Change-Id: I564c912ecfde31ba2293124bb1316e433c2a10f1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/6347/head
Brad Fitzpatrick 2 years ago committed by Brad Fitzpatrick
parent 97319a1970
commit 976e88d430

@ -7,6 +7,9 @@ package apitype
import "tailscale.com/tailcfg" import "tailscale.com/tailcfg"
// LocalAPIHost is the Host header value used by the LocalAPI.
const LocalAPIHost = "local-tailscaled.sock"
// WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler. // WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler.
type WhoIsResponse struct { type WhoIsResponse struct {
Node *tailcfg.Node Node *tailcfg.Node

@ -200,7 +200,7 @@ func (lc *LocalClient) send(ctx context.Context, method, path string, wantStatus
if jr, ok := body.(jsonReader); ok && jr.err != nil { if jr, ok := body.(jsonReader); ok && jr.err != nil {
return nil, jr.err // fail early if there was a JSON marshaling error return nil, jr.err // fail early if there was a JSON marshaling error
} }
req, err := http.NewRequestWithContext(ctx, method, "http://local-tailscaled.sock"+path, body) req, err := http.NewRequestWithContext(ctx, method, "http://"+apitype.LocalAPIHost+path, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -440,7 +440,7 @@ func (lc *LocalClient) DeleteWaitingFile(ctx context.Context, baseName string) e
} }
func (lc *LocalClient) GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, size int64, err error) { func (lc *LocalClient) GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, size int64, err error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/files/"+url.PathEscape(baseName), nil) req, err := http.NewRequestWithContext(ctx, "GET", "http://"+apitype.LocalAPIHost+"/localapi/v0/files/"+url.PathEscape(baseName), nil)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -473,7 +473,7 @@ func (lc *LocalClient) FileTargets(ctx context.Context) ([]apitype.FileTarget, e
// A size of -1 means unknown. // A size of -1 means unknown.
// The name parameter is the original filename, not escaped. // The name parameter is the original filename, not escaped.
func (lc *LocalClient) PushFile(ctx context.Context, target tailcfg.StableNodeID, size int64, name string, r io.Reader) error { func (lc *LocalClient) PushFile(ctx context.Context, target tailcfg.StableNodeID, size int64, name string, r io.Reader) error {
req, err := http.NewRequestWithContext(ctx, "PUT", "http://local-tailscaled.sock/localapi/v0/file-put/"+string(target)+"/"+url.PathEscape(name), r) req, err := http.NewRequestWithContext(ctx, "PUT", "http://"+apitype.LocalAPIHost+"/localapi/v0/file-put/"+string(target)+"/"+url.PathEscape(name), r)
if err != nil { if err != nil {
return err return err
} }
@ -584,7 +584,7 @@ func (lc *LocalClient) DialTCP(ctx context.Context, host string, port uint16) (n
}, },
} }
ctx = httptrace.WithClientTrace(ctx, &trace) ctx = httptrace.WithClientTrace(ctx, &trace)
req, err := http.NewRequestWithContext(ctx, "POST", "http://local-tailscaled.sock/localapi/v0/dial", nil) req, err := http.NewRequestWithContext(ctx, "POST", "http://"+apitype.LocalAPIHost+"/localapi/v0/dial", nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -34,6 +34,7 @@ import (
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/net/netutil" "tailscale.com/net/netutil"
"tailscale.com/safesocket"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tka" "tailscale.com/tka"
"tailscale.com/types/key" "tailscale.com/types/key"
@ -137,6 +138,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "server has no local backend", http.StatusInternalServerError) http.Error(w, "server has no local backend", http.StatusInternalServerError)
return return
} }
if r.Referer() != "" || r.Header.Get("Origin") != "" || !validHost(r.Host) {
http.Error(w, "invalid localapi request", http.StatusForbidden)
return
}
w.Header().Set("Tailscale-Version", version.Long) w.Header().Set("Tailscale-Version", version.Long)
if h.RequiredPassword != "" { if h.RequiredPassword != "" {
_, pass, ok := r.BasicAuth() _, pass, ok := r.BasicAuth()
@ -156,6 +161,24 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
} }
// validHost reports whether h is a valid Host header value for a LocalAPI request.
func validHost(h string) bool {
// The client code sends a hostname of "local-tailscaled.sock".
switch h {
case "", apitype.LocalAPIHost:
return true
}
// Otherwise, any Host header we see should at most be an ip:port.
ap, err := netip.ParseAddrPort(h)
if err != nil {
return false
}
if runtime.GOOS == "windows" && ap.Port() != safesocket.WindowsLocalPort {
return false
}
return ap.Addr().IsLoopback()
}
// handlerForPath returns the LocalAPI handler for the provided Request.URI.Path. // handlerForPath returns the LocalAPI handler for the provided Request.URI.Path.
// (the path doesn't include any query parameters) // (the path doesn't include any query parameters)
func handlerForPath(urlPath string) (h localAPIHandler, ok bool) { func handlerForPath(urlPath string) (h localAPIHandler, ok bool) {

Loading…
Cancel
Save