mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-07 08:07:42 +00:00
control/control{client,http}: don't noise dial localhost:443 in http-only tests
1eaad7d3deb regressed some tests in another repo that were starting up a control server on `http://127.0.0.1:nnn`. Because there was no https running, and because of a bug in 1eaad7d3deb (which ended up checking the recently-dialed-control check twice in a single dial call), we ended up forcing only the use of TLS dials in a test that only had plaintext HTTP running. Instead, plumb down support for explicitly disabling TLS fallbacks and use it only when running in a test and using `http` scheme control plane URLs to 127.0.0.1 or localhost. This fixes the tests elsewhere. Updates #13597 Change-Id: I97212ded21daf0bd510891a278078daec3eebaa6 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
6b03e18975
commit
a01b545441
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -16,6 +17,7 @@
|
|||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"tailscale.com/control/controlhttp"
|
"tailscale.com/control/controlhttp"
|
||||||
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/health"
|
"tailscale.com/health"
|
||||||
"tailscale.com/internal/noiseconn"
|
"tailscale.com/internal/noiseconn"
|
||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
@ -28,6 +30,7 @@
|
|||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
"tailscale.com/util/multierr"
|
"tailscale.com/util/multierr"
|
||||||
"tailscale.com/util/singleflight"
|
"tailscale.com/util/singleflight"
|
||||||
|
"tailscale.com/util/testenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NoiseClient provides a http.Client to connect to tailcontrol over
|
// NoiseClient provides a http.Client to connect to tailcontrol over
|
||||||
@ -56,8 +59,8 @@ type NoiseClient struct {
|
|||||||
privKey key.MachinePrivate
|
privKey key.MachinePrivate
|
||||||
serverPubKey key.MachinePublic
|
serverPubKey key.MachinePublic
|
||||||
host string // the host part of serverURL
|
host string // the host part of serverURL
|
||||||
httpPort string // the default port to call
|
httpPort string // the default port to dial
|
||||||
httpsPort string // the fallback Noise-over-https port
|
httpsPort string // the fallback Noise-over-https port or empty if none
|
||||||
|
|
||||||
// dialPlan optionally returns a ControlDialPlan previously received
|
// dialPlan optionally returns a ControlDialPlan previously received
|
||||||
// from the control server; either the function or the return value can
|
// from the control server; either the function or the return value can
|
||||||
@ -104,6 +107,11 @@ type NoiseOpts struct {
|
|||||||
DialPlan func() *tailcfg.ControlDialPlan
|
DialPlan func() *tailcfg.ControlDialPlan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// controlIsPlaintext is whether we should assume that the controlplane is only accessible
|
||||||
|
// over plaintext HTTP (as the first hop, before the ts2021 encryption begins).
|
||||||
|
// This is used by some tests which don't have a real TLS certificate.
|
||||||
|
var controlIsPlaintext = envknob.RegisterBool("TS_CONTROL_IS_PLAINTEXT_HTTP")
|
||||||
|
|
||||||
// NewNoiseClient returns a new noiseClient for the provided server and machine key.
|
// NewNoiseClient returns a new noiseClient for the provided server and machine key.
|
||||||
// serverURL is of the form https://<host>:<port> (no trailing slash).
|
// serverURL is of the form https://<host>:<port> (no trailing slash).
|
||||||
//
|
//
|
||||||
@ -116,14 +124,17 @@ func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
|
|||||||
}
|
}
|
||||||
var httpPort string
|
var httpPort string
|
||||||
var httpsPort string
|
var httpsPort string
|
||||||
if u.Port() != "" {
|
if port := u.Port(); port != "" {
|
||||||
// If there is an explicit port specified, trust the scheme and hope for the best
|
// If there is an explicit port specified, trust the scheme and hope for the best
|
||||||
if u.Scheme == "http" {
|
if u.Scheme == "http" {
|
||||||
httpPort = u.Port()
|
httpPort = port
|
||||||
httpsPort = "443"
|
httpsPort = "443"
|
||||||
|
if (testenv.InTest() || controlIsPlaintext()) && (u.Hostname() == "127.0.0.1" || u.Hostname() == "localhost") {
|
||||||
|
httpsPort = ""
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
httpPort = "80"
|
httpPort = "80"
|
||||||
httpsPort = u.Port()
|
httpsPort = port
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, use the standard ports
|
// Otherwise, use the standard ports
|
||||||
@ -340,7 +351,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseconn.Conn, error) {
|
|||||||
clientConn, err := (&controlhttp.Dialer{
|
clientConn, err := (&controlhttp.Dialer{
|
||||||
Hostname: nc.host,
|
Hostname: nc.host,
|
||||||
HTTPPort: nc.httpPort,
|
HTTPPort: nc.httpPort,
|
||||||
HTTPSPort: nc.httpsPort,
|
HTTPSPort: cmp.Or(nc.httpsPort, controlhttp.NoPort),
|
||||||
MachineKey: nc.privKey,
|
MachineKey: nc.privKey,
|
||||||
ControlKey: nc.serverPubKey,
|
ControlKey: nc.serverPubKey,
|
||||||
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
|
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
|
||||||
|
@ -86,9 +86,6 @@ func (a *Dialer) getProxyFunc() func(*http.Request) (*url.URL, error) {
|
|||||||
// httpsFallbackDelay is how long we'll wait for a.HTTPPort to work before
|
// httpsFallbackDelay is how long we'll wait for a.HTTPPort to work before
|
||||||
// starting to try a.HTTPSPort.
|
// starting to try a.HTTPSPort.
|
||||||
func (a *Dialer) httpsFallbackDelay() time.Duration {
|
func (a *Dialer) httpsFallbackDelay() time.Duration {
|
||||||
if a.forceNoise443() {
|
|
||||||
return time.Nanosecond
|
|
||||||
}
|
|
||||||
if v := a.testFallbackDelay; v != 0 {
|
if v := a.testFallbackDelay; v != 0 {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@ -323,6 +320,9 @@ func (a *Dialer) dialHost(ctx context.Context, optAddr netip.Addr) (*ClientConn,
|
|||||||
Host: net.JoinHostPort(a.Hostname, strDef(a.HTTPSPort, "443")),
|
Host: net.JoinHostPort(a.Hostname, strDef(a.HTTPSPort, "443")),
|
||||||
Path: serverUpgradePath,
|
Path: serverUpgradePath,
|
||||||
}
|
}
|
||||||
|
if a.HTTPSPort == NoPort {
|
||||||
|
u443 = nil
|
||||||
|
}
|
||||||
|
|
||||||
type tryURLRes struct {
|
type tryURLRes struct {
|
||||||
u *url.URL // input (the URL conn+err are for/from)
|
u *url.URL // input (the URL conn+err are for/from)
|
||||||
@ -347,15 +347,24 @@ type tryURLRes struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forceTLS := a.forceNoise443()
|
||||||
|
|
||||||
// Start the plaintext HTTP attempt first, unless disabled by the envknob.
|
// Start the plaintext HTTP attempt first, unless disabled by the envknob.
|
||||||
if !a.forceNoise443() {
|
if !forceTLS || u443 == nil {
|
||||||
go try(u80)
|
go try(u80)
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case outbound port 80 blocked or MITM'ed poorly, start a backup timer
|
// In case outbound port 80 blocked or MITM'ed poorly, start a backup timer
|
||||||
// to dial port 443 if port 80 doesn't either succeed or fail quickly.
|
// to dial port 443 if port 80 doesn't either succeed or fail quickly.
|
||||||
try443Timer := a.clock().AfterFunc(a.httpsFallbackDelay(), func() { try(u443) })
|
var try443Timer tstime.TimerController
|
||||||
defer try443Timer.Stop()
|
if u443 != nil {
|
||||||
|
delay := a.httpsFallbackDelay()
|
||||||
|
if forceTLS {
|
||||||
|
delay = 0
|
||||||
|
}
|
||||||
|
try443Timer = a.clock().AfterFunc(delay, func() { try(u443) })
|
||||||
|
defer try443Timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
var err80, err443 error
|
var err80, err443 error
|
||||||
for {
|
for {
|
||||||
@ -374,7 +383,7 @@ type tryURLRes struct {
|
|||||||
// Stop the fallback timer and run it immediately. We don't use
|
// Stop the fallback timer and run it immediately. We don't use
|
||||||
// Timer.Reset(0) here because on AfterFuncs, that can run it
|
// Timer.Reset(0) here because on AfterFuncs, that can run it
|
||||||
// again.
|
// again.
|
||||||
if try443Timer.Stop() {
|
if try443Timer != nil && try443Timer.Stop() {
|
||||||
go try(u443)
|
go try(u443)
|
||||||
} // else we lost the race and it started already which is what we want
|
} // else we lost the race and it started already which is what we want
|
||||||
case u443:
|
case u443:
|
||||||
|
@ -32,6 +32,11 @@
|
|||||||
serverUpgradePath = "/ts2021"
|
serverUpgradePath = "/ts2021"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NoPort is a sentinel value for Dialer.HTTPSPort to indicate that HTTPS
|
||||||
|
// should not be tried on any port. It exists primarily for some localhost
|
||||||
|
// tests where the control plane only runs on HTTP.
|
||||||
|
const NoPort = "none"
|
||||||
|
|
||||||
// Dialer contains configuration on how to dial the Tailscale control server.
|
// Dialer contains configuration on how to dial the Tailscale control server.
|
||||||
type Dialer struct {
|
type Dialer struct {
|
||||||
// Hostname is the hostname to connect to, with no port number.
|
// Hostname is the hostname to connect to, with no port number.
|
||||||
@ -62,6 +67,8 @@ type Dialer struct {
|
|||||||
// HTTPSPort is the port number to use when making a HTTPS connection.
|
// HTTPSPort is the port number to use when making a HTTPS connection.
|
||||||
//
|
//
|
||||||
// If not specified, this defaults to port 443.
|
// If not specified, this defaults to port 443.
|
||||||
|
//
|
||||||
|
// If "none" (NoPort), HTTPS is disabled.
|
||||||
HTTPSPort string
|
HTTPSPort string
|
||||||
|
|
||||||
// Dialer is the dialer used to make outbound connections.
|
// Dialer is the dialer used to make outbound connections.
|
||||||
|
@ -1791,6 +1791,7 @@ func (n *testNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon {
|
|||||||
cmd.Args = append(cmd.Args, "--config="+n.configFile)
|
cmd.Args = append(cmd.Args, "--config="+n.configFile)
|
||||||
}
|
}
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
|
"TS_CONTROL_IS_PLAINTEXT_HTTP=1",
|
||||||
"TS_DEBUG_PERMIT_HTTP_C2N=1",
|
"TS_DEBUG_PERMIT_HTTP_C2N=1",
|
||||||
"TS_LOG_TARGET="+n.env.LogCatcherServer.URL,
|
"TS_LOG_TARGET="+n.env.LogCatcherServer.URL,
|
||||||
"HTTP_PROXY="+n.env.TrafficTrapServer.URL,
|
"HTTP_PROXY="+n.env.TrafficTrapServer.URL,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user