|
|
@@ -5,6 +5,7 @@ package controlclient
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
+ "cmp"
|
|
|
"context"
|
|
|
"encoding/json"
|
|
|
"errors"
|
|
|
@@ -16,6 +17,7 @@ import (
|
|
|
|
|
|
"golang.org/x/net/http2"
|
|
|
"tailscale.com/control/controlhttp"
|
|
|
+ "tailscale.com/envknob"
|
|
|
"tailscale.com/health"
|
|
|
"tailscale.com/internal/noiseconn"
|
|
|
"tailscale.com/net/dnscache"
|
|
|
@@ -28,6 +30,7 @@ import (
|
|
|
"tailscale.com/util/mak"
|
|
|
"tailscale.com/util/multierr"
|
|
|
"tailscale.com/util/singleflight"
|
|
|
+ "tailscale.com/util/testenv"
|
|
|
)
|
|
|
|
|
|
// NoiseClient provides a http.Client to connect to tailcontrol over
|
|
|
@@ -56,8 +59,8 @@ type NoiseClient struct {
|
|
|
privKey key.MachinePrivate
|
|
|
serverPubKey key.MachinePublic
|
|
|
host string // the host part of serverURL
|
|
|
- httpPort string // the default port to call
|
|
|
- httpsPort string // the fallback Noise-over-https port
|
|
|
+ httpPort string // the default port to dial
|
|
|
+ httpsPort string // the fallback Noise-over-https port or empty if none
|
|
|
|
|
|
// dialPlan optionally returns a ControlDialPlan previously received
|
|
|
// from the control server; either the function or the return value can
|
|
|
@@ -104,6 +107,11 @@ type NoiseOpts struct {
|
|
|
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.
|
|
|
// 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 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 u.Scheme == "http" {
|
|
|
- httpPort = u.Port()
|
|
|
+ httpPort = port
|
|
|
httpsPort = "443"
|
|
|
+ if (testenv.InTest() || controlIsPlaintext()) && (u.Hostname() == "127.0.0.1" || u.Hostname() == "localhost") {
|
|
|
+ httpsPort = ""
|
|
|
+ }
|
|
|
} else {
|
|
|
httpPort = "80"
|
|
|
- httpsPort = u.Port()
|
|
|
+ httpsPort = port
|
|
|
}
|
|
|
} else {
|
|
|
// Otherwise, use the standard ports
|
|
|
@@ -340,7 +351,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseconn.Conn, error) {
|
|
|
clientConn, err := (&controlhttp.Dialer{
|
|
|
Hostname: nc.host,
|
|
|
HTTPPort: nc.httpPort,
|
|
|
- HTTPSPort: nc.httpsPort,
|
|
|
+ HTTPSPort: cmp.Or(nc.httpsPort, controlhttp.NoPort),
|
|
|
MachineKey: nc.privKey,
|
|
|
ControlKey: nc.serverPubKey,
|
|
|
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
|