net/tlsdial: fix TLS cert validation of HTTPS proxies

If you had HTTPS_PROXY=https://some-valid-cert.example.com running a
CONNECT proxy, we should've been able to do a TLS CONNECT request to
e.g. controlplane.tailscale.com:443 through that, and I'm pretty sure
it used to work, but refactorings and lack of integration tests made
it regress.

It probably regressed when we added the baked-in LetsEncrypt root cert
validation fallback code, which was testing against the wrong hostname
(the ultimate one, not the one which we were being asked to validate)

Fixes #16222

Change-Id: If014e395f830e2f87f056f588edacad5c15e91bc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2025-06-08 18:51:41 -07:00
committed by Brad Fitzpatrick
parent 4979ce7a94
commit e92eb6b17b
17 changed files with 672 additions and 49 deletions

View File

@@ -647,12 +647,13 @@ func (c *Client) dialRegion(ctx context.Context, reg *tailcfg.DERPRegion) (net.C
}
func (c *Client) tlsClient(nc net.Conn, node *tailcfg.DERPNode) *tls.Conn {
tlsConf := tlsdial.Config(c.tlsServerName(node), c.HealthTracker, c.TLSConfig)
tlsConf := tlsdial.Config(c.HealthTracker, c.TLSConfig)
if node != nil {
if node.InsecureForTests {
tlsConf.InsecureSkipVerify = true
tlsConf.VerifyConnection = nil
}
tlsConf.ServerName = c.tlsServerName(node)
if node.CertName != "" {
if suf, ok := strings.CutPrefix(node.CertName, "sha256-raw:"); ok {
tlsdial.SetConfigExpectedCertHash(tlsConf, suf)

View File

@@ -7,10 +7,14 @@ import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"maps"
"net"
"net/http"
"net/http/httptest"
"slices"
"strings"
"sync"
"testing"
@@ -19,6 +23,7 @@ import (
"tailscale.com/derp"
"tailscale.com/net/netmon"
"tailscale.com/net/netx"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
@@ -556,3 +561,32 @@ func TestNotifyError(t *testing.T) {
t.Fatalf("context done before receiving error: %v", ctx.Err())
}
}
var liveNetworkTest = flag.Bool("live-net-tests", false, "run live network tests")
func TestManualDial(t *testing.T) {
if !*liveNetworkTest {
t.Skip("skipping live network test without --live-net-tests")
}
dm := &tailcfg.DERPMap{}
res, err := http.Get("https://controlplane.tailscale.com/derpmap/default")
if err != nil {
t.Fatalf("fetching DERPMap: %v", err)
}
defer res.Body.Close()
if err := json.NewDecoder(res.Body).Decode(dm); err != nil {
t.Fatalf("decoding DERPMap: %v", err)
}
region := slices.Sorted(maps.Keys(dm.Regions))[0]
netMon := netmon.NewStatic()
rc := NewRegionClient(key.NewNode(), t.Logf, netMon, func() *tailcfg.DERPRegion {
return dm.Regions[region]
})
defer rc.Close()
if err := rc.Connect(context.Background()); err != nil {
t.Fatalf("rc.Connect: %v", err)
}
}