From e9125c42ebdba51293314cd584363931d9eca85d Mon Sep 17 00:00:00 2001 From: Sonia Appasamy Date: Thu, 14 Dec 2023 14:34:15 -0500 Subject: [PATCH 1/3] client/web: use Tailscale IP known by peer node Throughout the web UI, we present the tailscale addresses for the self node. In the case of the node being shared out with a user from another tailnet, the peer viewer may actually know the node by a different IP than the node knows itself as (Tailscale IPs can be configured as desired on a tailnet level). This change includes two fixes: 1. Present the self node's addresses in the frontend as the addresses the viewing node knows it as (i.e. the addresses the viewing node uses to access the web client). 2. We currently redirect the viewer to the Tailscale IPv4 address if viewing it by MagicDNS name, or any other name that maps to the Tailscale node. When doing this redirect, which is primarily added for DNS rebinding protection, we now check the address the peer knows this node as, and redirect to specifically that IP. Fixes tailscale/corp#16402 Signed-off-by: Sonia Appasamy (cherry picked from commit c6a274611e01768f2d77201436f11a45dbc0c75f) --- client/web/web.go | 76 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/client/web/web.go b/client/web/web.go index 1fee3c62dbde3..43bfd9d410c44 100644 --- a/client/web/web.go +++ b/client/web/web.go @@ -306,22 +306,61 @@ func (s *Server) requireTailscaleIP(w http.ResponseWriter, r *http.Request) (han return true } - var ipv4 string // store the first IPv4 address we see for redirect later + ipv4, ipv6 := s.selfNodeAddresses(r, st) + if r.Host == fmt.Sprintf("%s:%d", ipv4.String(), ListenPort) { + return false // already accessing over Tailscale IP + } + if r.Host == fmt.Sprintf("[%s]:%d", ipv6.String(), ListenPort) { + return false // already accessing over Tailscale IP + } + + // Not currently accessing via Tailscale IP, + // redirect them. + + var preferV6 bool + if ap, err := netip.ParseAddrPort(r.Host); err == nil { + // If Host was already ipv6, keep them on same protocol. + preferV6 = ap.Addr().Is6() + } + + newURL := *r.URL + if (preferV6 && ipv6.IsValid()) || !ipv4.IsValid() { + newURL.Host = fmt.Sprintf("[%s]:%d", ipv6.String(), ListenPort) + } else { + newURL.Host = fmt.Sprintf("%s:%d", ipv4.String(), ListenPort) + } + http.Redirect(w, r, newURL.String(), http.StatusMovedPermanently) + return true +} + +// selfNodeAddresses return the Tailscale IPv4 and IPv6 addresses for the self node. +// st is expected to be a status with peers included. +func (s *Server) selfNodeAddresses(r *http.Request, st *ipnstate.Status) (ipv4, ipv6 netip.Addr) { for _, ip := range st.Self.TailscaleIPs { if ip.Is4() { - if r.Host == fmt.Sprintf("%s:%d", ip, ListenPort) { - return false - } - ipv4 = ip.String() + ipv4 = ip + } else if ip.Is6() { + ipv6 = ip } - if ip.Is6() && r.Host == fmt.Sprintf("[%s]:%d", ip, ListenPort) { - return false + if ipv4.IsValid() && ipv6.IsValid() { + break // found both IPs } } - newURL := *r.URL - newURL.Host = fmt.Sprintf("%s:%d", ipv4, ListenPort) - http.Redirect(w, r, newURL.String(), http.StatusMovedPermanently) - return true + if whois, err := s.lc.WhoIs(r.Context(), r.RemoteAddr); err == nil { + // The source peer connecting to this node may know it by a different + // IP than the node knows itself as. Specifically, this may be the case + // if the peer is coming from a different tailnet (sharee node), as IPs + // are specific to each tailnet. + // Here, we check if the source peer knows the node by a different IP, + // and return the peer's version if so. + if knownIPv4 := whois.Node.SelfNodeV4MasqAddrForThisPeer; knownIPv4 != nil { + ipv4 = *knownIPv4 + } + if knownIPv6 := whois.Node.SelfNodeV6MasqAddrForThisPeer; knownIPv6 != nil { + ipv6 = *knownIPv6 + } + } + return ipv4, ipv6 } // authorizeRequest reports whether the request from the web client @@ -666,6 +705,10 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) { ACLAllowsAnyIncomingTraffic: s.aclsAllowAccess(filterRules), } + ipv4, ipv6 := s.selfNodeAddresses(r, st) + data.IPv4 = ipv4.String() + data.IPv6 = ipv6.String() + if hostinfo.GetEnvType() == hostinfo.HomeAssistantAddOn && data.URLPrefix == "" { // X-Ingress-Path is the path prefix in use for Home Assistant // https://developers.home-assistant.io/docs/add-ons/presentation#ingress @@ -678,16 +721,7 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) { } else { data.ClientVersion = cv } - for _, ip := range st.TailscaleIPs { - if ip.Is4() { - data.IPv4 = ip.String() - } else if ip.Is6() { - data.IPv6 = ip.String() - } - if data.IPv4 != "" && data.IPv6 != "" { - break - } - } + if st.CurrentTailnet != nil { data.TailnetName = st.CurrentTailnet.MagicDNSSuffix data.DomainName = st.CurrentTailnet.Name From 067b14566c3e6d123108785ef056dd40ce51ca5d Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Thu, 14 Dec 2023 11:55:34 -0500 Subject: [PATCH 2/3] ipn/ipnlocal: fix usage of slices.Compact Fixes #10595 Signed-off-by: Andrew Dunham Change-Id: I4e96e9c43b8dedb5f88b03368c01b0e46723e15b (cherry picked from commit 3ae562366b23fa59d2331a7455f1ceca279ab3a1) --- ipn/ipnlocal/local.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 5cbedad3486e1..2dceede5a5d0f 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -3422,7 +3422,7 @@ func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs i } } slices.Sort(domains) - slices.Compact(domains) + domains = slices.Compact(domains) b.appConnector.UpdateDomains(domains) } From f1ea3161a2a06ad6474a9ea919e91e9bd6062f84 Mon Sep 17 00:00:00 2001 From: Sonia Appasamy Date: Fri, 15 Dec 2023 14:21:33 -0500 Subject: [PATCH 3/3] VERSION.txt: this is v1.56.1 Signed-off-by: Sonia Appasamy --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 3ebf789f5a8df..43c989b55315f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.56.0 +1.56.1