Skip to content

Commit 4ec4c22

Browse files
bradfitznpry
andcommitted
ts_control: support plain HTTP control servers
The control client previously required TLS for all control server connections. This assumptions break when using Go's testcontrol server, which serves plain HTTP. Changes: - ts_control/connect: select HTTP/1.1 over TCP or TLS based on URL scheme, making http:// control URLs work - ts_http_util: change upgrade request from GET to POST to match Go's controlhttpserver which requires POST for /ts2021 Co-Authored-By: Nathan Perry <nathan@tailscale.com>
1 parent 795525e commit 4ec4c22

3 files changed

Lines changed: 24 additions & 3 deletions

File tree

ts_control/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ async_tokio = ["dep:futures-util", "dep:tokio", "dep:tokio-stream"]
5151

5252
# Allow derp connections to be made without verifying TLS certs. Only for use in tests.
5353
insecure-derp = ["ts_transport_derp/insecure-for-tests"]
54+
# Allow control keys to be fetched over plain HTTP1 without TLS. Only for use in tests.
55+
insecure-keyfetch = []
5456

5557
[lints]
5658
workspace = true

ts_control/src/tokio/connect.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pub async fn connect(
7171
control_url: &Url,
7272
machine_keys: &MachineKeyPair,
7373
) -> Result<Http2<BytesBody>, ConnectionError> {
74-
let h1_client = ts_http_util::http1::connect_tls(control_url).await?;
74+
let h1_client = connect_h1(control_url).await?;
7575

7676
let control_public_key = fetch_control_key(control_url).await?;
7777

@@ -94,16 +94,32 @@ pub async fn connect(
9494
Ok(h2_conn)
9595
}
9696

97+
/// Connect an HTTP/1.1 client to the control server, using TLS for https://
98+
/// URLs and plain TCP for http:// URLs.
99+
async fn connect_h1(url: &Url) -> Result<ts_http_util::Http1<EmptyBody>, ConnectionError> {
100+
if url.scheme() == "http" {
101+
Ok(ts_http_util::http1::connect_tcp(url).await?)
102+
} else {
103+
Ok(ts_http_util::http1::connect_tls(url).await?)
104+
}
105+
}
106+
97107
#[tracing::instrument(skip_all, fields(%control_url), ret, err, level = "trace")]
98108
pub async fn fetch_control_key(control_url: &Url) -> Result<MachinePublicKey, ConnectionError> {
99109
let mut key_url = control_url.join("/key")?;
110+
111+
#[cfg(not(feature = "insecure-keyfetch"))]
100112
key_url.set_scheme("https").unwrap();
101113

114+
if key_url.scheme() == "http" {
115+
tracing::warn!("fetching control key over insecure http");
116+
}
117+
102118
key_url
103119
.query_pairs_mut()
104120
.extend_pairs([("v", CapabilityVersion::CURRENT.to_string())]);
105121

106-
let client = ts_http_util::http1::connect_tls::<EmptyBody>(&key_url).await?;
122+
let client = connect_h1(&key_url).await?;
107123
let response = client.get(&key_url, None).await?;
108124
if !response.status().is_success() {
109125
let status = response.status();

ts_http_util/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ pub fn make_upgrade_req(
9797
protocol: &str,
9898
extra_headers: impl IntoIterator<Item = (HeaderName, HeaderValue)>,
9999
) -> Result<Request<EmptyBody>, Error> {
100-
let mut req = Request::get(u.as_str())
100+
// Use POST for the upgrade request. Some server implementations accept both
101+
// GET and POST, but others (e.g. Go's testcontrol) only accept POST. POST
102+
// is what Go's controlhttp client sends, so use it for widest compatibility.
103+
let mut req = Request::post(u.as_str())
101104
.header(HOST, u.host_str().ok_or(Error::InvalidParam)?)
102105
.header(UPGRADE, protocol)
103106
.header(CONNECTION, "Upgrade")

0 commit comments

Comments
 (0)