This is an automated email from the ASF dual-hosted git repository. CritasWang pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/iotdb-client-rust.git
commit 460dfd8156781cb6338522cfa30ebc8c172bd4a0 Author: CritasWang <[email protected]> AuthorDate: Mon Jul 13 14:53:08 2026 +0800 TLS hardening: nodejs-style dispatch tests (wire-byte assertion), mutual TLS client identity (cert/key PEM), real end-to-end TLS verified vs IoTDB 2.0.6 (enable_thrift_ssl, TLSv1.3, full cert verification) --- .gitignore | 2 + README.md | 15 ++- README_ZH.md | 15 ++- src/client/session.rs | 89 +++++++++++++- src/client/table_session.rs | 28 ++++- src/connection/mod.rs | 242 ++++++++++++++++++++++++++++++++++++- tests/fixtures/tls/README.md | 57 ++++++++- tests/fixtures/tls/client-cert.pem | 20 +++ tests/fixtures/tls/client-key.pem | 28 +++++ 9 files changed, 487 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 96ef6c0..f362b0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target Cargo.lock +# derived from cert.pem/key.pem for the live TLS test — see tests/fixtures/tls/README.md +tests/fixtures/tls/server-keystore.p12 diff --git a/README.md b/README.md index 823be0b..2b5dfd5 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,20 @@ let config = SessionConfig { // or: TableSession::builder().use_ssl(true).ca_cert_path("ca.pem")... ``` -The server needs Thrift SSL enabled (`enable_thrift_ssl=true` + key store). Pool configs pass both options through their embedded `session` config. +For **mutual TLS** (server has `thrift_ssl_client_auth=true`), add a PEM client certificate and its PKCS#8 key — the analogue of the Node.js `sslOptions.cert`/`sslOptions.key`: + +```rust +let config = SessionConfig { + use_ssl: true, + ca_cert_path: Some("ca.pem".into()), + client_cert_path: Some("client.crt".into()), // must be set together + client_key_path: Some("client.key".into()), // with client_cert_path + ..Default::default() +}; +// or: TableSession::builder().use_ssl(true).client_cert_path("client.crt").client_key_path("client.key")... +``` + +The server needs Thrift SSL enabled (`enable_thrift_ssl=true` + key store; see `tests/fixtures/tls/README.md` for a throwaway docker setup). Pool configs pass all options through their embedded `session` config. ## Thrift codegen diff --git a/README_ZH.md b/README_ZH.md index 6f40256..25e384b 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -200,7 +200,20 @@ let config = SessionConfig { // 或:TableSession::builder().use_ssl(true).ca_cert_path("ca.pem")... ``` -服务端需开启 Thrift SSL(`enable_thrift_ssl=true` + 密钥库)。连接池配置通过其内嵌的 `session` 配置透传这两组选项。 +**双向 TLS**(服务端 `thrift_ssl_client_auth=true`)需额外提供 PEM 客户端证书及其 PKCS#8 私钥 —— 对应 Node.js 的 `sslOptions.cert`/`sslOptions.key`: + +```rust +let config = SessionConfig { + use_ssl: true, + ca_cert_path: Some("ca.pem".into()), + client_cert_path: Some("client.crt".into()), // 必须与 client_key_path + client_key_path: Some("client.key".into()), // 同时设置 + ..Default::default() +}; +// 或:TableSession::builder().use_ssl(true).client_cert_path("client.crt").client_key_path("client.key")... +``` + +服务端需开启 Thrift SSL(`enable_thrift_ssl=true` + 密钥库;一次性 docker 环境见 `tests/fixtures/tls/README.md`)。连接池配置通过其内嵌的 `session` 配置透传全部选项。 ## Thrift 代码生成 diff --git a/src/client/session.rs b/src/client/session.rs index 5f4425c..91480c4 100644 --- a/src/client/session.rs +++ b/src/client/session.rs @@ -95,6 +95,15 @@ pub struct SessionConfig { /// host (e.g. when connecting by IP). #[cfg(feature = "tls")] pub domain_override: Option<String>, + /// PEM client certificate for mutual TLS (server has + /// `thrift_ssl_client_auth=true`); set together with + /// `client_key_path`. Mirrors the Node.js `sslOptions.cert`. + #[cfg(feature = "tls")] + pub client_cert_path: Option<std::path::PathBuf>, + /// PEM PKCS#8 private key for the client certificate; set together + /// with `client_cert_path`. Mirrors the Node.js `sslOptions.key`. + #[cfg(feature = "tls")] + pub client_key_path: Option<std::path::PathBuf>, } impl Default for SessionConfig { @@ -122,6 +131,10 @@ impl Default for SessionConfig { accept_invalid_certs: false, #[cfg(feature = "tls")] domain_override: None, + #[cfg(feature = "tls")] + client_cert_path: None, + #[cfg(feature = "tls")] + client_key_path: None, } } } @@ -152,6 +165,8 @@ impl SessionConfig { ca_cert_path: self.ca_cert_path.clone(), accept_invalid_certs: self.accept_invalid_certs, domain_override: self.domain_override.clone(), + client_cert_path: self.client_cert_path.clone(), + client_key_path: self.client_key_path.clone(), }), } } @@ -1027,6 +1042,8 @@ mod tests { assert!(cfg.ca_cert_path.is_none()); assert!(!cfg.accept_invalid_certs); assert!(cfg.domain_override.is_none()); + assert!(cfg.client_cert_path.is_none()); + assert!(cfg.client_key_path.is_none()); } } @@ -1056,12 +1073,42 @@ mod tests { ca_cert_path: Some("/certs/ca.pem".into()), accept_invalid_certs: true, domain_override: Some("iotdb.internal".into()), + client_cert_path: Some("/certs/client.pem".into()), + client_key_path: Some("/certs/client-key.pem".into()), ..Default::default() }; let tls = cfg.connection_options().tls.expect("tls options"); assert_eq!(tls.ca_cert_path.as_deref(), Some("/certs/ca.pem".as_ref())); assert!(tls.accept_invalid_certs); assert_eq!(tls.domain_override.as_deref(), Some("iotdb.internal")); + assert_eq!( + tls.client_cert_path.as_deref(), + Some("/certs/client.pem".as_ref()) + ); + assert_eq!( + tls.client_key_path.as_deref(), + Some("/certs/client-key.pem".as_ref()) + ); + + // Protocol × TLS are independent axes: every combination maps + // through, ConnectionOptions carries both. + for (compression, ssl) in [(false, false), (false, true), (true, false), (true, true)] { + let cfg = SessionConfig { + enable_rpc_compression: compression, + use_ssl: ssl, + ..Default::default() + }; + let options = cfg.connection_options(); + assert_eq!( + options.protocol, + if compression { + RpcProtocol::Compact + } else { + RpcProtocol::Binary + } + ); + assert_eq!(options.tls.is_some(), ssl); + } } } @@ -1598,11 +1645,49 @@ mod tests { use_ssl: true, ca_cert_path: std::env::var_os("IOTDB_TLS_CA").map(Into::into), accept_invalid_certs: std::env::var_os("IOTDB_TLS_INSECURE").is_some(), + domain_override: std::env::var("IOTDB_TLS_DOMAIN").ok(), + // A client identity is only *verified* by a server with + // thrift_ssl_client_auth=true (not available in 2.0.6, whose + // RPC service hardcodes requireClientAuth(false)); setting these + // against any TLS server still proves the identity loads and + // the handshake tolerates it. + client_cert_path: std::env::var_os("IOTDB_TLS_CLIENT_CERT").map(Into::into), + client_key_path: std::env::var_os("IOTDB_TLS_CLIENT_KEY").map(Into::into), ..Default::default() }); session.open().expect("open TLS session"); - let mut dataset = session.execute_query("SHOW DATABASES").expect("query"); - while dataset.next_row().expect("next_row").is_some() {} + + // Full write+read roundtrip over the TLS transport, not just a + // metadata query. + const DB: &str = "root.rusttest_tls"; + let _ = session.execute_non_query(&format!("DELETE DATABASE {DB}")); + session + .execute_non_query(&format!("CREATE DATABASE {DB}")) + .expect("create database"); + session + .execute_non_query(&format!( + "CREATE TIMESERIES {DB}.d1.s1 WITH DATATYPE=INT32, ENCODING=PLAIN" + )) + .expect("create timeseries"); + session + .execute_non_query(&format!( + "INSERT INTO {DB}.d1(timestamp, s1) VALUES (1, 42)" + )) + .expect("insert"); + let mut rows = Vec::new(); + { + let mut dataset = session + .execute_query(&format!("SELECT s1 FROM {DB}.d1")) + .expect("query"); + while let Some(row) = dataset.next_row().expect("next_row") { + rows.push((row.timestamp.expect("timestamp"), row.values[0].clone())); + } + } + assert_eq!(rows, [(1, crate::data::Value::Int32(42))]); + session + .execute_non_query(&format!("DELETE DATABASE {DB}")) + .expect("cleanup"); + session.close().expect("close session"); } /// DATE wire-format adjudication test (goal V1). Inserts one DATE row via diff --git a/src/client/table_session.rs b/src/client/table_session.rs index a37b02f..b708e26 100644 --- a/src/client/table_session.rs +++ b/src/client/table_session.rs @@ -144,6 +144,22 @@ impl TableSessionBuilder { self } + /// PEM client certificate for mutual TLS; set together with + /// [`client_key_path`](Self::client_key_path) (cargo feature `tls`). + #[cfg(feature = "tls")] + pub fn client_cert_path(mut self, path: impl Into<std::path::PathBuf>) -> Self { + self.config.client_cert_path = Some(path.into()); + self + } + + /// PEM PKCS#8 private key for the client certificate; set together with + /// [`client_cert_path`](Self::client_cert_path) (cargo feature `tls`). + #[cfg(feature = "tls")] + pub fn client_key_path(mut self, path: impl Into<std::path::PathBuf>) -> Self { + self.config.client_key_path = Some(path.into()); + self + } + /// The [`SessionConfig`] this builder resolves to (dialect always /// `"table"`). pub fn config(&self) -> &SessionConfig { @@ -249,12 +265,22 @@ mod tests { .use_ssl(true) .ca_cert_path("/certs/ca.pem") .accept_invalid_certs(true) - .domain_override("iotdb.internal"); + .domain_override("iotdb.internal") + .client_cert_path("/certs/client.pem") + .client_key_path("/certs/client-key.pem"); let cfg = b.config(); assert!(cfg.use_ssl); assert_eq!(cfg.ca_cert_path.as_deref(), Some("/certs/ca.pem".as_ref())); assert!(cfg.accept_invalid_certs); assert_eq!(cfg.domain_override.as_deref(), Some("iotdb.internal")); + assert_eq!( + cfg.client_cert_path.as_deref(), + Some("/certs/client.pem".as_ref()) + ); + assert_eq!( + cfg.client_key_path.as_deref(), + Some("/certs/client-key.pem".as_ref()) + ); } } diff --git a/src/connection/mod.rs b/src/connection/mod.rs index 5f77f6c..69dc3c7 100644 --- a/src/connection/mod.rs +++ b/src/connection/mod.rs @@ -71,6 +71,15 @@ pub struct TlsOptions { /// Hostname used for SNI + certificate validation instead of the /// endpoint host (e.g. when connecting by IP). pub domain_override: Option<String>, + /// PEM client certificate for mutual TLS (server has + /// `thrift_ssl_client_auth=true`). Must be set together with + /// [`client_key_path`](Self::client_key_path); mirrors the Node.js + /// `sslOptions.cert`. + pub client_cert_path: Option<std::path::PathBuf>, + /// PEM PKCS#8 private key for the client certificate. Must be set + /// together with [`client_cert_path`](Self::client_cert_path); mirrors + /// the Node.js `sslOptions.key`. + pub client_key_path: Option<std::path::PathBuf>, } /// How to open a [`Connection`]: timeout, wire protocol, optional TLS. @@ -272,6 +281,30 @@ fn tls_handshake( if tls.accept_invalid_certs { builder.danger_accept_invalid_certs(true); } + match (&tls.client_cert_path, &tls.client_key_path) { + (Some(cert_path), Some(key_path)) => { + let cert = std::fs::read(cert_path).map_err(|e| { + Error::Client(format!( + "cannot read client certificate {}: {e}", + cert_path.display() + )) + })?; + let key = std::fs::read(key_path).map_err(|e| { + Error::Client(format!( + "cannot read client key {}: {e}", + key_path.display() + )) + })?; + let identity = native_tls::Identity::from_pkcs8(&cert, &key).map_err(Error::Tls)?; + builder.identity(identity); + } + (None, None) => {} + _ => { + return Err(Error::Client( + "mutual TLS requires both client_cert_path and client_key_path".into(), + )) + } + } let connector = builder.build().map_err(Error::Tls)?; let domain = tls.domain_override.as_deref().unwrap_or(&endpoint.host); connector.connect(domain, stream).map_err(|e| match e { @@ -396,7 +429,7 @@ mod tests { /// A local listener that accepts and immediately drops connections, so /// `Connection::open` (which issues no RPC) succeeds for any protocol. - fn accept_then_drop_listener() -> Endpoint { + pub(super) fn accept_then_drop_listener() -> Endpoint { let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind"); let port = listener.local_addr().expect("local_addr").port(); std::thread::spawn(move || { @@ -410,6 +443,51 @@ mod tests { Endpoint::new("127.0.0.1", port) } + /// A local listener that accepts one connection, reports the first byte + /// the client puts on the wire, then closes (unblocking the client with + /// an EOF/reset). Lets tests assert *which* stack touched the socket: + /// a TLS ClientHello starts with the handshake record type `0x16`, a + /// plain Thrift framed message with the frame-length MSB `0x00`. + pub(super) fn first_byte_listener() -> (Endpoint, std::sync::mpsc::Receiver<u8>) { + let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind"); + let port = listener.local_addr().expect("local_addr").port(); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + if let Ok((mut stream, _)) = listener.accept() { + let mut byte = [0u8; 1]; + if stream.read_exact(&mut byte).is_ok() { + let _ = tx.send(byte[0]); + } + } + }); + (Endpoint::new("127.0.0.1", port), rx) + } + + /// Dispatch (Node.js `Connection.test.ts` analogue): without TLS the + /// plain TCP stack talks to the socket — the first wire byte of an RPC + /// is the framed-transport length MSB (`0x00`), not a TLS ClientHello + /// record type (`0x16`). + #[test] + fn plain_dispatch_writes_thrift_frame_not_tls() { + use crate::protocol::client::TIClientRPCServiceSyncClient; + + let (endpoint, first_byte) = first_byte_listener(); + #[allow(clippy::needless_update)] + let options = ConnectionOptions { + connect_timeout: Duration::from_millis(500), + protocol: RpcProtocol::Binary, + ..Default::default() + }; + let mut connection = Connection::open(endpoint, &options).expect("plain open"); + // The RPC itself fails once the listener closes after one byte — + // only the bytes it managed to put on the wire matter here. + let _ = connection.client_mut().request_statement_id(1); + let byte = first_byte + .recv_timeout(Duration::from_secs(5)) + .expect("first wire byte"); + assert_eq!(byte, 0x00, "expected framed length MSB, got 0x{byte:02x}"); + } + /// Both protocol variants construct their transport/protocol stack and /// report the choice back via `Connection::protocol`. (Wire-level /// verification against a live server lives in the session tests.) @@ -482,6 +560,7 @@ mod tls_tests { ca_cert_path: Some(fixture("cert.pem")), accept_invalid_certs: false, domain_override: Some("localhost".into()), + ..Default::default() }), }; let mut connection = Connection::open(endpoint, &options).expect("TLS handshake"); @@ -508,6 +587,7 @@ mod tls_tests { ca_cert_path: None, accept_invalid_certs: false, domain_override: Some("localhost".into()), + ..Default::default() }), }; let err = match Connection::open(endpoint, &options) { @@ -534,6 +614,166 @@ mod tls_tests { assert_eq!(connection.protocol(), RpcProtocol::Compact); } + /// Dispatch (Node.js `Connection.test.ts` analogue), TLS side: with + /// `tls: Some(..)` the first wire byte is the TLS handshake record type + /// `0x16` (ClientHello) — the plain Thrift stack never touches the + /// socket. The listener is not a TLS server, so `open` itself fails. + #[test] + fn tls_dispatch_sends_client_hello() { + let (endpoint, first_byte) = super::tests::first_byte_listener(); + let options = ConnectionOptions { + connect_timeout: Duration::from_millis(500), + protocol: RpcProtocol::Binary, + tls: Some(TlsOptions { + accept_invalid_certs: true, + ..Default::default() + }), + }; + assert!( + Connection::open(endpoint, &options).is_err(), + "plain listener is not a TLS server" + ); + let byte = first_byte + .recv_timeout(Duration::from_secs(5)) + .expect("first wire byte"); + assert_eq!( + byte, 0x16, + "expected TLS handshake record type, got 0x{byte:02x}" + ); + } + + /// Dispatch pair against the *same kind* of plain (non-TLS) endpoint: + /// `tls: None` opens fine (plain TCP), `tls: Some(..)` — even with + /// certificate verification disabled — dies in the handshake with a + /// TLS error. Together with the ClientHello byte check this proves the + /// `tls` option selects the code path, mirroring the Node.js test that + /// asserts the SSL constructor is (not) called. + #[test] + fn tls_option_selects_stack_against_plain_endpoint() { + let endpoint = super::tests::accept_then_drop_listener(); + + let plain = ConnectionOptions { + connect_timeout: Duration::from_millis(500), + protocol: RpcProtocol::Binary, + tls: None, + }; + Connection::open(endpoint.clone(), &plain).expect("plain open against plain listener"); + + let tls = ConnectionOptions { + connect_timeout: Duration::from_millis(500), + protocol: RpcProtocol::Binary, + tls: Some(TlsOptions { + accept_invalid_certs: true, + ..Default::default() + }), + }; + let err = match Connection::open(endpoint, &tls) { + Ok(_) => panic!("TLS handshake against a plain endpoint must fail"), + Err(e) => e, + }; + assert!(matches!(err, Error::Tls(_)), "got {err:?}"); + } + + /// Mutual TLS: a PEM client certificate + PKCS#8 key load into an + /// identity and the handshake completes with the identity configured. + /// `native-tls`'s `TlsAcceptor` has no API to *request* a client + /// certificate, so the loopback server cannot verify it — acceptor-side + /// client-auth is exercised only by the live `IOTDB_TLS_URL` test + /// against a real server. + #[test] + fn tls_client_identity_handshake_succeeds() { + let endpoint = tls_acceptor_once(); + let options = ConnectionOptions { + connect_timeout: Duration::from_millis(500), + protocol: RpcProtocol::Binary, + tls: Some(TlsOptions { + ca_cert_path: Some(fixture("cert.pem")), + domain_override: Some("localhost".into()), + client_cert_path: Some(fixture("client-cert.pem")), + client_key_path: Some(fixture("client-key.pem")), + ..Default::default() + }), + }; + let connection = Connection::open(endpoint, &options).expect("TLS handshake with identity"); + assert_eq!(connection.protocol(), RpcProtocol::Binary); + } + + /// Setting only one of the client cert/key pair is a config error + /// caught before any I/O. + #[test] + fn tls_client_identity_requires_both_paths() { + let endpoint = tls_acceptor_once(); + for (cert, key) in [ + (Some(fixture("client-cert.pem")), None), + (None, Some(fixture("client-key.pem"))), + ] { + let options = ConnectionOptions { + connect_timeout: Duration::from_millis(500), + protocol: RpcProtocol::Binary, + tls: Some(TlsOptions { + accept_invalid_certs: true, + client_cert_path: cert.clone(), + client_key_path: key.clone(), + ..Default::default() + }), + }; + let err = match Connection::open(endpoint.clone(), &options) { + Ok(_) => panic!("half a client identity must fail"), + Err(e) => e, + }; + assert!( + matches!(&err, Error::Client(m) if m.contains("mutual TLS")), + "cert={cert:?} key={key:?}: got {err:?}" + ); + } + } + + /// A missing client key file is a clear client error before any connect. + #[test] + fn tls_missing_client_key_file_is_client_error() { + let endpoint = tls_acceptor_once(); + let options = ConnectionOptions { + connect_timeout: Duration::from_millis(500), + protocol: RpcProtocol::Binary, + tls: Some(TlsOptions { + accept_invalid_certs: true, + client_cert_path: Some(fixture("client-cert.pem")), + client_key_path: Some(fixture("does-not-exist-key.pem")), + ..Default::default() + }), + }; + let err = match Connection::open(endpoint, &options) { + Ok(_) => panic!("missing client key must fail"), + Err(e) => e, + }; + assert!( + matches!(&err, Error::Client(m) if m.contains("cannot read client key")), + "got {err:?}" + ); + } + + /// A file that is not a PKCS#8 key (here: the certificate itself) fails + /// identity construction with a TLS error, not a panic. + #[test] + fn tls_corrupt_client_key_is_tls_error() { + let endpoint = tls_acceptor_once(); + let options = ConnectionOptions { + connect_timeout: Duration::from_millis(500), + protocol: RpcProtocol::Binary, + tls: Some(TlsOptions { + accept_invalid_certs: true, + client_cert_path: Some(fixture("client-cert.pem")), + client_key_path: Some(fixture("client-cert.pem")), // not a key + ..Default::default() + }), + }; + let err = match Connection::open(endpoint, &options) { + Ok(_) => panic!("a certificate is not a private key"), + Err(e) => e, + }; + assert!(matches!(err, Error::Tls(_)), "got {err:?}"); + } + /// A missing CA file is a clear client error before any connect. #[test] fn tls_missing_ca_file_is_client_error() { diff --git a/tests/fixtures/tls/README.md b/tests/fixtures/tls/README.md index 4ff8a2a..7b5a9e5 100644 --- a/tests/fixtures/tls/README.md +++ b/tests/fixtures/tls/README.md @@ -19,9 +19,14 @@ under the License. # TLS test fixtures -Self-signed certificate + PKCS#8 key used only by the loopback TLS tests -(`connection::tls_tests`, cargo feature `tls`). Not a secret — the key -never protects anything. +Self-signed certificates + PKCS#8 keys used only by the loopback TLS tests +(`connection::tls_tests`, cargo feature `tls`) and the opt-in live TLS test. +Not secrets — the keys never protect anything. + +| File | Role | +|---|---| +| `cert.pem` + `key.pem` | server identity (loopback `TlsAcceptor`, live-server keystore) | +| `client-cert.pem` + `client-key.pem` | client identity for the mutual-TLS tests | macOS SecureTransport imposes extra requirements even on explicitly trusted roots: validity ≤ 825 days (error −67901) and an @@ -37,3 +42,49 @@ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \ -addext "extendedKeyUsage=serverAuth" \ -addext "keyUsage=digitalSignature,keyEncipherment" ``` + +The client certificate (expires **2028-10-10** as well) is standalone +self-signed — `cert.pem` cannot act as its issuer because its `keyUsage` +lacks `keyCertSign`; nothing in the tests validates the client chain +anyway (`native-tls`'s `TlsAcceptor` cannot request client certs): + +```sh +cd tests/fixtures/tls +openssl req -x509 -newkey rsa:2048 -keyout client-key.pem -out client-cert.pem \ + -days 820 -nodes -subj "/CN=iotdb-client-rust-test-client" \ + -addext "extendedKeyUsage=clientAuth" \ + -addext "keyUsage=digitalSignature,keyEncipherment" +``` + +## Live TLS server keystore + +The end-to-end test (`live_tls_roundtrip`, gated by `IOTDB_TLS_URL`) needs a +TLS-enabled IoTDB. Build a PKCS#12 keystore from the server fixture pair +(regenerate whenever `cert.pem` is regenerated; not checked in): + +```sh +cd tests/fixtures/tls +openssl pkcs12 -export -in cert.pem -inkey key.pem \ + -out server-keystore.p12 -name iotdb -passout pass:iotdbtls +``` + +Then run a throwaway TLS-enabled server and point the test at it +(the docker env-var mechanism appends any lowercase var to +`iotdb-system.properties`; IoTDB ≥ 2.x reads `enable_thrift_ssl`, +`key_store_path`, `key_store_pwd` — client RPC port only, internal +cluster ports stay plain): + +```sh +docker run -d --name iotdb-rust-tls-test -p 6668:6667 \ + -e enable_thrift_ssl=true \ + -e key_store_path=/tls/server-keystore.p12 \ + -e key_store_pwd=iotdbtls \ + -v "$PWD/server-keystore.p12":/tls/server-keystore.p12:ro \ + apache/iotdb:2.0.6-standalone + +IOTDB_TLS_URL=127.0.0.1:6668 \ +IOTDB_TLS_CA=tests/fixtures/tls/cert.pem \ +cargo test --features tls live_tls_roundtrip + +docker rm -f iotdb-rust-tls-test +``` diff --git a/tests/fixtures/tls/client-cert.pem b/tests/fixtures/tls/client-cert.pem new file mode 100644 index 0000000..7fd9ba9 --- /dev/null +++ b/tests/fixtures/tls/client-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIUPEd19SXvrayngu1MttuvSr5zI9owDQYJKoZIhvcNAQEL +BQAwKDEmMCQGA1UEAwwdaW90ZGItY2xpZW50LXJ1c3QtdGVzdC1jbGllbnQwHhcN +MjYwNzEzMDY0MDUxWhcNMjgxMDEwMDY0MDUxWjAoMSYwJAYDVQQDDB1pb3RkYi1j +bGllbnQtcnVzdC10ZXN0LWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAN9vhv0UheTWaJh44F0/0Hn0gvek+YQQkhP5AU7m5XRR10ahG9m/3XLv +5AVxvowaVYtmlTBigyLOZH8AX7UrUg4RMMI/lahUmthauXFklfW8cnCj+1BEsYiD +rXH6vmFV8gpmuNgBC8cKfJJKk9/A+DmuMHMBuNYe81SgHQTceidp8c3SRaRx9mQq +Nhtix/a9aerFbxuK3XERl2skzJZRLib0aUKXGLrEGFIrlTea95zZ7Q9R0NbU+6XJ +/KHHAjdWH/jP1vk+a+G1ov/jRsbhuAjIPSyuFTS+nBsHjTvccA4Jb2iALu5ImzUT +GEvB+CBeYe+hzxW4WE7kycf3+C7GhZkCAwEAAaN1MHMwHQYDVR0OBBYEFLjH7RNa +2AHWqxjUag9W4M3maHwFMB8GA1UdIwQYMBaAFLjH7RNa2AHWqxjUag9W4M3maHwF +MA8GA1UdEwEB/wQFMAMBAf8wEwYDVR0lBAwwCgYIKwYBBQUHAwIwCwYDVR0PBAQD +AgWgMA0GCSqGSIb3DQEBCwUAA4IBAQC4sjGOurJLqDYRGneoWfleQdQXI3uelmMJ ++cHMjwU/CoCC1Ri3WYwQR4FTQYSI6ONhneVNTFp4fZSH5qYOzH6U1FudpaarY8kD +bYRi/01MnmJ+rJW7K5ni9EVMN3qH4w3GK+pTOx1SkEUFQYzRHNCtFSWvYATk8eFQ +JdBghxHPe7WQrgOre4Z7WgUUU4IwA9nfp+rLArXaQFfzX0qpm/9LzluHyTS895Ph +E0Jn2hdUzLQGwfW7e5W6WRbtzPAuDoXz87pvYcnX020S+//wYoEE6zRnq/mvSgMC +GGXBAsp4ihTz1zcwYTUlUabTSYY0R8MRP7jSvAtUf1JFG4BtcMUf +-----END CERTIFICATE----- diff --git a/tests/fixtures/tls/client-key.pem b/tests/fixtures/tls/client-key.pem new file mode 100644 index 0000000..d145123 --- /dev/null +++ b/tests/fixtures/tls/client-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDfb4b9FIXk1miY +eOBdP9B59IL3pPmEEJIT+QFO5uV0UddGoRvZv91y7+QFcb6MGlWLZpUwYoMizmR/ +AF+1K1IOETDCP5WoVJrYWrlxZJX1vHJwo/tQRLGIg61x+r5hVfIKZrjYAQvHCnyS +SpPfwPg5rjBzAbjWHvNUoB0E3HonafHN0kWkcfZkKjYbYsf2vWnqxW8bit1xEZdr +JMyWUS4m9GlClxi6xBhSK5U3mvec2e0PUdDW1PulyfyhxwI3Vh/4z9b5PmvhtaL/ +40bG4bgIyD0srhU0vpwbB4073HAOCW9ogC7uSJs1ExhLwfggXmHvoc8VuFhO5MnH +9/guxoWZAgMBAAECggEAE3YPG6WJOQKc1yT2I+EeSzULID9QFVrtIky2urTdSPmk +5su7FYcC5pMy+O9skZ7skwkDFxUJh4NTGQXDEFBe5AdGyDCKHECMQSp7yv7gGeiJ +TjrWNapaDUIs3gfhQ7Cc9Z93W6cRq8tDyOkygpN3+2wq36WHgCzvdFBC3szGXf7Q +XIR8uyGtspriTfUeDCAAUHfpW1DyTrki8sG7VltvtTiXT5GFkDT1PKJuOZ6QLRzR +VxvykmOG+sJo1kwp2vSCXr3aMxLVWXV77roh5jc8OTYG4nIpMY8T1zls5rcfTHX9 +UUps10Qs/tRgvYWIYPTFDe+FEzvI5rJ2ubIB7z/JwQKBgQD9BnQe4jcOJtdAubAW +ROHU+w+fv/BVP6bqNLPspKVY/IgfNgbLd4Kh/AnUqE176CYzQ5cFnS2S4BYop+KY +BOYKME2R+M3GRbPuWfOWTLZYb9R949EdIeZ/7fUalZNshm+j9faVANbW6re4olON +47Fnc3fUAJxQVCFB/c9Sn9ODIQKBgQDiEAQe0L4GsH0KZlT/HOUgMzThs47jeOUL +0q5bNtjj4Tra1OeGJw6V3wg6p41a5GLj94KdSVLa/zOmYSc95MpqYGWdXZYnaxAv +XRFO2e9ZExgL/0zbYkSW0fEFDCerTMr9spGUBYEXwyuTD7ok7QgEpRs23SVxbYAp +dRKniucreQKBgQDAft9q+4kNICmU2XAIkSEKSnLJg1nRUVqoRa44s84DlCPvMsga +lXJxz/Ces+g3AxfE2oATyk94tTjGd2shYmCskbECA7pxRGguRorV5si3IWUU3XQi +6L6Wxy84qWD+KIzYvXB6Tagk228obX6JrRczcBpS1KAXUNn4faLz5hohYQKBgQDM +v7DbsDjMmkFGkBTf0237UiXNXvJGORNLOBDPcMfU7gR+e2MPcISXXaB4b5VqA31F ++MGGcAjpbUd8pHYEaxqiapjehQsgvKm2HFc20dElHlQjWaJk+YYiDBh+d9neHvmj ++n48URfxS8ZFtnLkSwN+IYSaloX5TDJOWkkBEp/6EQKBgH5mtkUnxU1lye7QQqwe +j7fP1Ae0JJS1jx2F8JtaaDf6xkIXt1mbDOcYV+2h/iM+G6kBkFr++uKp+4vRSePu +ooynmgYGufFbVpSEUl4t6hbkyH9vX6Z4iDzgBND97PFT63Nd0SE/TTDG+lvDD+Tg +kRDuj1zcTzjLe6YLrf/QaMfk +-----END PRIVATE KEY-----
