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 <sonia@tailscale.com>
This commit is contained in:
parent
685b853763
commit
c6a274611e
|
@ -314,24 +314,63 @@ func (s *Server) requireTailscaleIP(w http.ResponseWriter, r *http.Request) (han
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipv4 string // store the first IPv4 address we see for redirect later
|
ipv4, ipv6 := s.selfNodeAddresses(r, st)
|
||||||
for _, ip := range st.Self.TailscaleIPs {
|
if r.Host == fmt.Sprintf("%s:%d", ipv4.String(), ListenPort) {
|
||||||
if ip.Is4() {
|
return false // already accessing over Tailscale IP
|
||||||
if r.Host == fmt.Sprintf("%s:%d", ip, ListenPort) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ipv4 = ip.String()
|
|
||||||
}
|
|
||||||
if ip.Is6() && r.Host == fmt.Sprintf("[%s]:%d", ip, ListenPort) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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
|
newURL := *r.URL
|
||||||
newURL.Host = fmt.Sprintf("%s:%d", ipv4, ListenPort)
|
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)
|
http.Redirect(w, r, newURL.String(), http.StatusMovedPermanently)
|
||||||
return true
|
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() {
|
||||||
|
ipv4 = ip
|
||||||
|
} else if ip.Is6() {
|
||||||
|
ipv6 = ip
|
||||||
|
}
|
||||||
|
if ipv4.IsValid() && ipv6.IsValid() {
|
||||||
|
break // found both IPs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
// authorizeRequest reports whether the request from the web client
|
||||||
// is authorized to be completed.
|
// is authorized to be completed.
|
||||||
// It reports true if the request is authorized, and false otherwise.
|
// It reports true if the request is authorized, and false otherwise.
|
||||||
|
@ -674,6 +713,10 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) {
|
||||||
ACLAllowsAnyIncomingTraffic: s.aclsAllowAccess(filterRules),
|
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 == "" {
|
if hostinfo.GetEnvType() == hostinfo.HomeAssistantAddOn && data.URLPrefix == "" {
|
||||||
// X-Ingress-Path is the path prefix in use for Home Assistant
|
// X-Ingress-Path is the path prefix in use for Home Assistant
|
||||||
// https://developers.home-assistant.io/docs/add-ons/presentation#ingress
|
// https://developers.home-assistant.io/docs/add-ons/presentation#ingress
|
||||||
|
@ -686,16 +729,7 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) {
|
||||||
} else {
|
} else {
|
||||||
data.ClientVersion = cv
|
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 {
|
if st.CurrentTailnet != nil {
|
||||||
data.TailnetName = st.CurrentTailnet.MagicDNSSuffix
|
data.TailnetName = st.CurrentTailnet.MagicDNSSuffix
|
||||||
data.DomainName = st.CurrentTailnet.Name
|
data.DomainName = st.CurrentTailnet.Name
|
||||||
|
|
Loading…
Reference in New Issue