Skip to content

Commit fa5360f

Browse files
committed
Merge branch 'rustls' into next
* rustls: Load root certificates for rustls Add backward compatiblity for tls feature (alias for native-tls now) Rename tls feature into _tls ci: increase timeout for native-tls one more time (attempt to fix test_concurrent_queries) ci: enable full stacktraces on CI (to sched some light on timeout problems) Fix test_many_connection for TLS ci: increase timeout for native-tls (sometimes 500ms is not enough) ci: run all tests over secure connection for TLS builds ci: add rustls build ci: start docker manually without services ci: add configuration with native-tls ci: use image from clickhouse/clickhouse-server (over yandex) Add ability to use rustls over native-tls
2 parents 34311ac + 464b886 commit fa5360f

File tree

12 files changed

+327
-63
lines changed

12 files changed

+327
-63
lines changed

.github/workflows/test.yml

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,74 @@ on:
1010

1111
env:
1212
CARGO_TERM_COLOR: always
13+
RUST_BACKTRACE: 1
1314

1415
jobs:
1516
build:
16-
1717
runs-on: ubuntu-latest
18-
1918
services:
2019
clickhouse:
21-
image: yandex/clickhouse-server
20+
image: clickhouse/clickhouse-server
2221
ports:
2322
- 9000:9000
24-
2523
steps:
2624
- uses: actions/checkout@v3
2725
- name: Build
2826
run: cargo build --verbose
2927
- name: Run tests
3028
run: cargo test --verbose
29+
30+
build-native-tls:
31+
runs-on: ubuntu-latest
32+
env:
33+
# NOTE: not all tests "secure" aware, so let's define DATABASE_URL explicitly
34+
# NOTE: sometimes for native-tls default connection_timeout (500ms) is not enough, interestingly that for rustls it is OK.
35+
DATABASE_URL: "tcp://localhost:9440?compression=lz4&ping_timeout=2s&retry_timeout=3s&secure=true&skip_verify=true&connection_timeout=5s"
36+
steps:
37+
- uses: actions/checkout@v3
38+
# NOTE:
39+
# - we cannot use "services" because they are executed before the steps, i.e. repository checkout.
40+
# - "job.container.network" is empty, hence "host"
41+
# - github actions does not support YAML anchors (sigh)
42+
- name: Run clickhouse-server
43+
run: docker run
44+
-v ./extras/ci/generate_certs.sh:/docker-entrypoint-initdb.d/generate_certs.sh
45+
-v ./extras/ci/overrides.xml:/etc/clickhouse-server/config.d/overrides.xml
46+
-e CH_SSL_CERTIFICATE=/etc/clickhouse-server/config.d/server.crt
47+
-e CH_SSL_PRIVATE_KEY=/etc/clickhouse-server/config.d/server.key
48+
--network host
49+
--rm
50+
--detach
51+
--publish 9440:9440
52+
clickhouse/clickhouse-server
53+
- name: Build
54+
run: cargo build --features tls-native-tls --verbose
55+
- name: Run tests
56+
run: cargo test --features tls-native-tls --verbose
57+
58+
build-rustls:
59+
runs-on: ubuntu-latest
60+
env:
61+
# NOTE: not all tests "secure" aware, so let's define DATABASE_URL explicitly
62+
DATABASE_URL: "tcp://localhost:9440?compression=lz4&ping_timeout=2s&retry_timeout=3s&secure=true&skip_verify=true"
63+
steps:
64+
- uses: actions/checkout@v3
65+
# NOTE:
66+
# - we cannot use "services" because they are executed before the steps, i.e. repository checkout.
67+
# - "job.container.network" is empty, hence "host"
68+
# - github actions does not support YAML anchors (sigh)
69+
- name: Run clickhouse-server
70+
run: docker run
71+
-v ./extras/ci/generate_certs.sh:/docker-entrypoint-initdb.d/generate_certs.sh
72+
-v ./extras/ci/overrides.xml:/etc/clickhouse-server/config.d/overrides.xml
73+
-e CH_SSL_CERTIFICATE=/etc/clickhouse-server/config.d/server.crt
74+
-e CH_SSL_PRIVATE_KEY=/etc/clickhouse-server/config.d/server.key
75+
--network host
76+
--rm
77+
--detach
78+
--publish 9440:9440
79+
clickhouse/clickhouse-server
80+
- name: Build
81+
run: cargo build --features tls-rustls --verbose
82+
- name: Run tests
83+
run: cargo test --features tls-rustls --verbose

Cargo.toml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ exclude = ["tests/*", "examples/*"]
1515

1616
[features]
1717
default = ["tokio_io"]
18-
tls = ["tokio-native-tls", "native-tls"]
18+
_tls = [] # meta feature for the clickhouse-rs generic TLS code
19+
tls = ["tls-native-tls"] # backward compatibility
20+
tls-native-tls = ["tokio-native-tls", "native-tls", "_tls"]
21+
tls-rustls = ["tokio-rustls", "rustls", "rustls-pemfile", "webpki-roots", "_tls"]
1922
async_std = ["async-std"]
2023
tokio_io = ["tokio"]
2124

@@ -67,6 +70,22 @@ optional = true
6770
version = "^0.3"
6871
optional = true
6972

73+
[dependencies.rustls]
74+
version = "0.22.1"
75+
optional = true
76+
77+
[dependencies.rustls-pemfile]
78+
version = "2.0"
79+
optional = true
80+
81+
[dependencies.tokio-rustls]
82+
version = "0.25.0"
83+
optional = true
84+
85+
[dependencies.webpki-roots]
86+
version = "*"
87+
optional = true
88+
7089
[dependencies.chrono]
7190
version = "^0.4"
7291
default-features = false

examples/simple.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ async fn execute(database_url: String) -> Result<(), Box<dyn Error>> {
3838
Ok(())
3939
}
4040

41-
#[cfg(all(feature = "tokio_io", not(feature = "tls")))]
41+
#[cfg(all(feature = "tokio_io", not(feature = "_tls")))]
4242
#[tokio::main]
4343
async fn main() -> Result<(), Box<dyn Error>> {
4444
let database_url =
4545
env::var("DATABASE_URL").unwrap_or_else(|_| "tcp://localhost:9000?compression=lz4".into());
4646
execute(database_url).await
4747
}
4848

49-
#[cfg(all(feature = "tokio_io", feature = "tls"))]
49+
#[cfg(all(feature = "tokio_io", feature = "_tls"))]
5050
#[tokio::main]
5151
async fn main() -> Result<(), Box<dyn Error>> {
5252
let database_url = env::var("DATABASE_URL")

extras/ci/generate_certs.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
3+
crt=$CH_SSL_CERTIFICATE
4+
key=$CH_SSL_PRIVATE_KEY
5+
6+
openssl req -subj "/CN=localhost" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout "$key" -out "$crt"
7+
chown clickhouse:clickhouse "$crt" "$key"

extras/ci/overrides.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<clickhouse>
2+
<openSSL>
3+
<server>
4+
<certificateFile from_env="CH_SSL_CERTIFICATE" replace="1"></certificateFile>
5+
<privateKeyFile from_env="CH_SSL_PRIVATE_KEY" replace="1"></privateKeyFile>
6+
<verificationMode>none</verificationMode>
7+
<loadDefaultCAFile>true</loadDefaultCAFile>
8+
<cacheSessions>true</cacheSessions>
9+
<disableProtocols>sslv2,sslv3</disableProtocols>
10+
<preferServerCiphers>true</preferServerCiphers>
11+
</server>
12+
</openSSL>
13+
<tcp_port_secure>9440</tcp_port_secure>
14+
15+
<logger>
16+
<console>1</console>
17+
</logger>
18+
</clickhouse>

src/connecting_stream.rs

Lines changed: 136 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,35 @@ use std::{
66
};
77

88
use futures_util::future::{select_ok, BoxFuture, SelectOk, TryFutureExt};
9-
#[cfg(feature = "tls")]
9+
#[cfg(feature = "_tls")]
1010
use futures_util::FutureExt;
1111

1212
#[cfg(feature = "async_std")]
1313
use async_std::net::TcpStream;
14-
#[cfg(feature = "tls")]
14+
#[cfg(feature = "tls-native-tls")]
1515
use native_tls::TlsConnector;
1616
#[cfg(feature = "tokio_io")]
1717
use tokio::net::TcpStream;
18+
#[cfg(feature = "tls-rustls")]
19+
use {
20+
rustls::{
21+
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
22+
crypto::{verify_tls12_signature, verify_tls13_signature},
23+
pki_types::{CertificateDer, ServerName, UnixTime},
24+
ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore,
25+
},
26+
std::sync::Arc,
27+
tokio_rustls::TlsConnector,
28+
};
1829

1930
use pin_project::pin_project;
2031
use url::Url;
2132

2233
use crate::{errors::ConnectionError, io::Stream as InnerStream, Options};
23-
#[cfg(feature = "tls")]
34+
#[cfg(feature = "tls-native-tls")]
2435
use tokio_native_tls::TlsStream;
36+
#[cfg(feature = "tls-rustls")]
37+
use tokio_rustls::client::TlsStream;
2538

2639
type Result<T> = std::result::Result<T, ConnectionError>;
2740

@@ -33,7 +46,7 @@ enum TcpState {
3346
Fail(Option<ConnectionError>),
3447
}
3548

36-
#[cfg(feature = "tls")]
49+
#[cfg(feature = "_tls")]
3750
#[pin_project(project = TlsStateProj)]
3851
enum TlsState {
3952
Wait(#[pin] ConnectingFuture<TlsStream<TcpStream>>),
@@ -43,7 +56,7 @@ enum TlsState {
4356
#[pin_project(project = StateProj)]
4457
enum State {
4558
Tcp(#[pin] TcpState),
46-
#[cfg(feature = "tls")]
59+
#[cfg(feature = "_tls")]
4760
Tls(#[pin] TlsState),
4861
}
4962

@@ -60,7 +73,7 @@ impl TcpState {
6073
}
6174
}
6275

63-
#[cfg(feature = "tls")]
76+
#[cfg(feature = "_tls")]
6477
impl TlsState {
6578
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<InnerStream>> {
6679
match self.project() {
@@ -81,7 +94,7 @@ impl State {
8194
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<InnerStream>> {
8295
match self.project() {
8396
StateProj::Tcp(inner) => inner.poll(cx),
84-
#[cfg(feature = "tls")]
97+
#[cfg(feature = "_tls")]
8598
StateProj::Tls(inner) => inner.poll(cx),
8699
}
87100
}
@@ -91,7 +104,7 @@ impl State {
91104
State::Tcp(TcpState::Fail(Some(conn_error)))
92105
}
93106

94-
#[cfg(feature = "tls")]
107+
#[cfg(feature = "_tls")]
95108
fn tls_host_err() -> Self {
96109
State::Tls(TlsState::Fail(Some(ConnectionError::TlsHostNotProvided)))
97110
}
@@ -100,7 +113,7 @@ impl State {
100113
State::Tcp(TcpState::Wait(socket))
101114
}
102115

103-
#[cfg(feature = "tls")]
116+
#[cfg(feature = "_tls")]
104117
fn tls_wait(s: ConnectingFuture<TlsStream<TcpStream>>) -> Self {
105118
State::Tls(TlsState::Wait(s))
106119
}
@@ -112,6 +125,57 @@ pub(crate) struct ConnectingStream {
112125
state: State,
113126
}
114127

128+
#[derive(Debug)]
129+
struct DummyTlsVerifier;
130+
131+
#[cfg(feature = "tls-rustls")]
132+
impl ServerCertVerifier for DummyTlsVerifier {
133+
fn verify_server_cert(
134+
&self,
135+
_end_entity: &CertificateDer<'_>,
136+
_intermediates: &[CertificateDer<'_>],
137+
_server_name: &ServerName<'_>,
138+
_ocsp_response: &[u8],
139+
_now: UnixTime,
140+
) -> std::result::Result<ServerCertVerified, TlsError> {
141+
Ok(ServerCertVerified::assertion())
142+
}
143+
144+
fn verify_tls12_signature(
145+
&self,
146+
message: &[u8],
147+
cert: &CertificateDer<'_>,
148+
dss: &DigitallySignedStruct,
149+
) -> std::result::Result<HandshakeSignatureValid, TlsError> {
150+
verify_tls12_signature(
151+
message,
152+
cert,
153+
dss,
154+
&rustls::crypto::ring::default_provider().signature_verification_algorithms,
155+
)
156+
}
157+
158+
fn verify_tls13_signature(
159+
&self,
160+
message: &[u8],
161+
cert: &CertificateDer<'_>,
162+
dss: &DigitallySignedStruct,
163+
) -> std::result::Result<HandshakeSignatureValid, TlsError> {
164+
verify_tls13_signature(
165+
message,
166+
cert,
167+
dss,
168+
&rustls::crypto::ring::default_provider().signature_verification_algorithms,
169+
)
170+
}
171+
172+
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
173+
rustls::crypto::ring::default_provider()
174+
.signature_verification_algorithms
175+
.supported_schemes()
176+
}
177+
}
178+
115179
impl ConnectingStream {
116180
#[allow(unused_variables)]
117181
pub(crate) fn new(addr: &Url, options: &Options) -> Self {
@@ -137,7 +201,7 @@ impl ConnectingStream {
137201

138202
let socket = select_ok(streams);
139203

140-
#[cfg(feature = "tls")]
204+
#[cfg(feature = "_tls")]
141205
{
142206
if options.secure {
143207
return ConnectingStream::new_tls_connection(addr, socket, options);
@@ -154,7 +218,7 @@ impl ConnectingStream {
154218
}
155219
}
156220

157-
#[cfg(feature = "tls")]
221+
#[cfg(feature = "tls-native-tls")]
158222
fn new_tls_connection(
159223
addr: &Url,
160224
socket: SelectOk<ConnectingFuture<TcpStream>>,
@@ -185,6 +249,67 @@ impl ConnectingStream {
185249
}
186250
}
187251
}
252+
253+
#[cfg(feature = "tls-rustls")]
254+
fn new_tls_connection(
255+
addr: &Url,
256+
socket: SelectOk<ConnectingFuture<TcpStream>>,
257+
options: &Options,
258+
) -> Self {
259+
match addr.host_str().map(|host| host.to_owned()) {
260+
None => Self {
261+
state: State::tls_host_err(),
262+
},
263+
Some(host) => {
264+
let config = if options.skip_verify {
265+
ClientConfig::builder()
266+
.dangerous()
267+
.with_custom_certificate_verifier(Arc::new(DummyTlsVerifier))
268+
.with_no_client_auth()
269+
} else {
270+
let mut cert_store = RootCertStore::empty();
271+
cert_store.extend(
272+
webpki_roots::TLS_SERVER_ROOTS
273+
.iter()
274+
.cloned()
275+
);
276+
if let Some(certificates) = options.certificate.clone() {
277+
for certificate in
278+
Into::<Vec<rustls::pki_types::CertificateDer<'static>>>::into(
279+
certificates,
280+
)
281+
{
282+
match cert_store.add(certificate) {
283+
Ok(_) => {},
284+
Err(err) => {
285+
let err = io::Error::new(
286+
io::ErrorKind::InvalidInput,
287+
format!("Could not load certificate: {}.", err),
288+
);
289+
return Self { state: State::tcp_err(err) };
290+
},
291+
}
292+
}
293+
}
294+
ClientConfig::builder()
295+
.with_root_certificates(cert_store)
296+
.with_no_client_auth()
297+
};
298+
Self {
299+
state: State::tls_wait(Box::pin(async move {
300+
let (s, _) = socket.await?;
301+
let cx = TlsConnector::from(Arc::new(config));
302+
let host = ServerName::try_from(host)
303+
.map_err(|_| ConnectionError::TlsHostNotProvided)?;
304+
Ok(cx
305+
.connect(host, s)
306+
.await
307+
.map_err(|e| ConnectionError::IoError(e))?)
308+
})),
309+
}
310+
}
311+
}
312+
}
188313
}
189314

190315
impl Future for ConnectingStream {

0 commit comments

Comments
 (0)