controlclient: do not set HTTPS port for any private coordination server IP (#14564)

Fixes tailscale/tailscale#14563

When creating a NoiseClient, ensure that if any private IP address is provided, with both an `http` scheme and an explicit port number, we do not ever attempt to use HTTPS. We were only handling the case of `127.0.0.1` and `localhost`, but `192.168.x.y` is a private IP as well. This uses the `netip` package to check and adds some logging in case we ever need to troubleshoot this.

Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
This commit is contained in:
Andrea Gottardo
2025-01-07 10:24:32 -08:00
committed by GitHub
parent f4f57b815b
commit 6db220b478
2 changed files with 140 additions and 6 deletions

View File

@ -11,6 +11,7 @@ import (
"errors"
"math"
"net/http"
"net/netip"
"net/url"
"sync"
"time"
@ -111,24 +112,39 @@ type NoiseOpts struct {
// netMon may be nil, if non-nil it's used to do faster interface lookups.
// dialPlan may be nil
func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
logf := opts.Logf
u, err := url.Parse(opts.ServerURL)
if err != nil {
return nil, err
}
if u.Scheme != "http" && u.Scheme != "https" {
return nil, errors.New("invalid ServerURL scheme, must be http or https")
}
var httpPort string
var httpsPort string
addr, _ := netip.ParseAddr(u.Hostname())
isPrivateHost := addr.IsPrivate() || addr.IsLoopback() || u.Hostname() == "localhost"
if port := u.Port(); port != "" {
// If there is an explicit port specified, trust the scheme and hope for the best
if u.Scheme == "http" {
// If there is an explicit port specified, entirely rely on the scheme,
// unless it's http with a private host in which case we never try using HTTPS.
if u.Scheme == "https" {
httpPort = ""
httpsPort = port
} else if u.Scheme == "http" {
httpPort = port
httpsPort = "443"
if u.Hostname() == "127.0.0.1" || u.Hostname() == "localhost" {
if isPrivateHost {
logf("setting empty HTTPS port with http scheme and private host %s", u.Hostname())
httpsPort = ""
}
} else {
httpPort = "80"
httpsPort = port
}
} else if u.Scheme == "http" && isPrivateHost {
// Whenever the scheme is http and the hostname is an IP address, do not set the HTTPS port,
// as there cannot be a TLS certificate issued for an IP, unless it's a public IP.
httpPort = "80"
httpsPort = ""
} else {
// Otherwise, use the standard ports
httpPort = "80"