aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--pingora-cache/Cargo.toml3
-rw-r--r--pingora-core/Cargo.toml14
-rw-r--r--pingora-core/src/connectors/http/mod.rs2
-rw-r--r--pingora-core/src/connectors/http/v1.rs2
-rw-r--r--pingora-core/src/connectors/http/v2.rs8
-rw-r--r--pingora-core/src/connectors/mod.rs19
-rw-r--r--pingora-core/src/connectors/tls/boringssl_openssl/mod.rs68
-rw-r--r--pingora-core/src/connectors/tls/mod.rs83
-rw-r--r--pingora-core/src/lib.rs9
-rw-r--r--pingora-core/src/listeners/mod.rs32
-rw-r--r--pingora-core/src/listeners/tls/boringssl_openssl/mod.rs14
-rw-r--r--pingora-core/src/listeners/tls/mod.rs18
-rw-r--r--pingora-core/src/protocols/mod.rs7
-rw-r--r--pingora-core/src/protocols/tls/boringssl_openssl/mod.rs27
-rw-r--r--pingora-core/src/protocols/tls/boringssl_openssl/server.rs23
-rw-r--r--pingora-core/src/protocols/tls/boringssl_openssl/stream.rs217
-rw-r--r--pingora-core/src/protocols/tls/digest.rs35
-rw-r--r--pingora-core/src/protocols/tls/dummy_tls/mod.rs805
-rw-r--r--pingora-core/src/protocols/tls/mod.rs183
-rw-r--r--pingora-core/src/protocols/tls/noop_tls/mod.rs216
-rw-r--r--pingora-core/src/services/listening.rs3
-rw-r--r--pingora-core/src/upstreams/peer.rs8
-rw-r--r--pingora-core/src/utils/mod.rs133
-rw-r--r--pingora-core/src/utils/tls/boringssl_openssl.rs140
-rw-r--r--pingora-core/src/utils/tls/mod.rs19
-rw-r--r--pingora-core/tests/test_basic.rs17
-rw-r--r--pingora-core/tests/utils/mod.rs2
-rw-r--r--pingora-error/src/lib.rs2
-rw-r--r--pingora-load-balancing/Cargo.toml9
-rw-r--r--pingora-load-balancing/src/health_check.rs2
-rw-r--r--pingora-proxy/Cargo.toml11
-rw-r--r--pingora-proxy/examples/load_balancer.rs2
-rw-r--r--pingora-proxy/tests/test_basic.rs39
-rw-r--r--pingora-proxy/tests/utils/cert.rs63
-rw-r--r--pingora-proxy/tests/utils/conf/keys/README.md9
-rw-r--r--pingora-proxy/tests/utils/conf/keys/server_boringssl_openssl.crt13
-rw-r--r--pingora-proxy/tests/utils/conf/keys/server_boringssl_openssl.csr (renamed from pingora-proxy/tests/utils/conf/keys/server.csr)0
-rw-r--r--pingora-proxy/tests/utils/conf/keys/server_rustls.crt14
-rw-r--r--pingora-proxy/tests/utils/conf/origin/conf/nginx.conf7
-rw-r--r--pingora-proxy/tests/utils/mock_origin.rs25
-rw-r--r--pingora-proxy/tests/utils/mod.rs2
-rw-r--r--pingora-proxy/tests/utils/server_utils.rs37
-rw-r--r--pingora-rustls/Cargo.toml17
-rw-r--r--pingora-rustls/LICENSE202
-rw-r--r--pingora-rustls/src/lib.rs17
-rw-r--r--pingora/Cargo.toml13
-rw-r--r--pingora/examples/server.rs79
48 files changed, 1330 insertions, 1341 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 2fdc570..f50a2f5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ members = [
"pingora-openssl",
"pingora-boringssl",
"pingora-runtime",
+ "pingora-rustls",
"pingora-ketama",
"pingora-load-balancing",
"pingora-memory-cache",
diff --git a/pingora-cache/Cargo.toml b/pingora-cache/Cargo.toml
index 6ae6c12..235cd08 100644
--- a/pingora-cache/Cargo.toml
+++ b/pingora-cache/Cargo.toml
@@ -65,6 +65,7 @@ name = "lru_serde"
harness = false
[features]
-default = ["openssl"]
+default = []
openssl = ["pingora-core/openssl"]
boringssl = ["pingora-core/boringssl"]
+rustls = ["pingora-core/rustls"]
diff --git a/pingora-core/Cargo.toml b/pingora-core/Cargo.toml
index 5fa60ae..713bb75 100644
--- a/pingora-core/Cargo.toml
+++ b/pingora-core/Cargo.toml
@@ -26,6 +26,7 @@ pingora-pool = { version = "0.3.0", path = "../pingora-pool" }
pingora-error = { version = "0.3.0", path = "../pingora-error" }
pingora-timeout = { version = "0.3.0", path = "../pingora-timeout" }
pingora-http = { version = "0.3.0", path = "../pingora-http" }
+pingora-rustls = { version = "0.3.0", path = "../pingora-rustls", optional = true }
tokio = { workspace = true, features = ["net", "rt-multi-thread", "signal"] }
futures = "0.3"
async-trait = { workspace = true }
@@ -66,6 +67,7 @@ openssl-probe = "0.1"
tokio-test = "0.4"
zstd = "0"
httpdate = "1"
+x509-parser = { version = "0.16.0", optional = true }
[target.'cfg(unix)'.dependencies]
daemonize = "0.5.0"
@@ -87,9 +89,11 @@ hyperlocal = "0.8"
jemallocator = "0.5"
[features]
-default = ["openssl"]
-openssl = ["pingora-openssl", "some_tls"]
-boringssl = ["pingora-boringssl", "some_tls"]
+default = []
+openssl = ["pingora-openssl", "openssl_derived",]
+boringssl = ["pingora-boringssl", "openssl_derived",]
+rustls = ["pingora-rustls", "any_tls", "dep:x509-parser"]
+patched_http1 = ["pingora-http/patched_http1"]
+openssl_derived = ["any_tls"]
+any_tls = []
sentry = ["dep:sentry"]
-patched_http1 = []
-some_tls = []
diff --git a/pingora-core/src/connectors/http/mod.rs b/pingora-core/src/connectors/http/mod.rs
index 33c5e01..0133990 100644
--- a/pingora-core/src/connectors/http/mod.rs
+++ b/pingora-core/src/connectors/http/mod.rs
@@ -95,7 +95,7 @@ impl Connector {
}
#[cfg(test)]
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "any_tls")]
mod tests {
use super::*;
use crate::protocols::http::v1::client::HttpSession as Http1Session;
diff --git a/pingora-core/src/connectors/http/v1.rs b/pingora-core/src/connectors/http/v1.rs
index fb5dfd0..ffa6df2 100644
--- a/pingora-core/src/connectors/http/v1.rs
+++ b/pingora-core/src/connectors/http/v1.rs
@@ -103,7 +103,7 @@ mod tests {
}
#[tokio::test]
- #[cfg(feature = "some_tls")]
+ #[cfg(feature = "any_tls")]
async fn test_connect_tls() {
let connector = Connector::new(None);
let peer = HttpPeer::new(("1.1.1.1", 443), true, "one.one.one.one".into());
diff --git a/pingora-core/src/connectors/http/v2.rs b/pingora-core/src/connectors/http/v2.rs
index 60e26fb..a1f295a 100644
--- a/pingora-core/src/connectors/http/v2.rs
+++ b/pingora-core/src/connectors/http/v2.rs
@@ -460,7 +460,7 @@ mod tests {
use crate::upstreams::peer::HttpPeer;
#[tokio::test]
- #[cfg(feature = "some_tls")]
+ #[cfg(feature = "any_tls")]
async fn test_connect_h2() {
let connector = Connector::new(None);
let mut peer = HttpPeer::new(("1.1.1.1", 443), true, "one.one.one.one".into());
@@ -473,7 +473,7 @@ mod tests {
}
#[tokio::test]
- #[cfg(feature = "some_tls")]
+ #[cfg(feature = "any_tls")]
async fn test_connect_h1() {
let connector = Connector::new(None);
let mut peer = HttpPeer::new(("1.1.1.1", 443), true, "one.one.one.one".into());
@@ -499,7 +499,7 @@ mod tests {
}
#[tokio::test]
- #[cfg(feature = "some_tls")]
+ #[cfg(feature = "any_tls")]
async fn test_h2_single_stream() {
let connector = Connector::new(None);
let mut peer = HttpPeer::new(("1.1.1.1", 443), true, "one.one.one.one".into());
@@ -531,7 +531,7 @@ mod tests {
}
#[tokio::test]
- #[cfg(feature = "some_tls")]
+ #[cfg(feature = "any_tls")]
async fn test_h2_multiple_stream() {
let connector = Connector::new(None);
let mut peer = HttpPeer::new(("1.1.1.1", 443), true, "one.one.one.one".into());
diff --git a/pingora-core/src/connectors/mod.rs b/pingora-core/src/connectors/mod.rs
index 57c16aa..5a126cc 100644
--- a/pingora-core/src/connectors/mod.rs
+++ b/pingora-core/src/connectors/mod.rs
@@ -17,11 +17,15 @@
pub mod http;
pub mod l4;
mod offload;
+
+#[cfg(feature = "any_tls")]
mod tls;
+#[cfg(not(feature = "any_tls"))]
+use crate::tls::connectors as tls;
+
use crate::protocols::Stream;
use crate::server::configuration::ServerConf;
-use crate::tls::ssl::SslConnector;
use crate::upstreams::peer::{Peer, ALPN};
pub use l4::Connect as L4Connect;
@@ -34,6 +38,7 @@ use pingora_pool::{ConnectionMeta, ConnectionPool};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
+use tls::TlsConnector;
use tokio::sync::Mutex;
/// The options to configure a [TransportConnector]
@@ -293,7 +298,7 @@ async fn do_connect<P: Peer + Send + Sync>(
peer: &P,
bind_to: Option<BindTo>,
alpn_override: Option<ALPN>,
- tls_ctx: &SslConnector,
+ tls_ctx: &TlsConnector,
) -> Result<Stream> {
// Create the future that does the connections, but don't evaluate it until
// we decide if we need a timeout or not
@@ -316,7 +321,7 @@ async fn do_connect_inner<P: Peer + Send + Sync>(
peer: &P,
bind_to: Option<BindTo>,
alpn_override: Option<ALPN>,
- tls_ctx: &SslConnector,
+ tls_ctx: &TlsConnector,
) -> Result<Stream> {
let stream = l4_connect(peer, bind_to).await?;
if peer.tls() {
@@ -383,12 +388,12 @@ fn test_reusable_stream(stream: &mut Stream) -> bool {
}
#[cfg(test)]
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "any_tls")]
mod tests {
use pingora_error::ErrorType;
+ use tls::Connector;
use super::*;
- use crate::tls::ssl::SslMethod;
use crate::upstreams::peer::BasicPeer;
use tokio::io::AsyncWriteExt;
#[cfg(unix)]
@@ -498,8 +503,8 @@ mod tests {
/// This assumes that the connection will fail to on the peer and returns
/// the decomposed error type and message
async fn get_do_connect_failure_with_peer(peer: &BasicPeer) -> (ErrorType, String) {
- let ssl_connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
- let stream = do_connect(peer, None, None, &ssl_connector).await;
+ let tls_connector = Connector::new(None);
+ let stream = do_connect(peer, None, None, &tls_connector.ctx).await;
match stream {
Ok(_) => panic!("should throw an error"),
Err(e) => (
diff --git a/pingora-core/src/connectors/tls/boringssl_openssl/mod.rs b/pingora-core/src/connectors/tls/boringssl_openssl/mod.rs
index f8568e7..a57668f 100644
--- a/pingora-core/src/connectors/tls/boringssl_openssl/mod.rs
+++ b/pingora-core/src/connectors/tls/boringssl_openssl/mod.rs
@@ -16,6 +16,7 @@ use log::debug;
use pingora_error::{Error, ErrorType::*, OrErr, Result};
use std::sync::{Arc, Once};
+use crate::connectors::tls::replace_leftmost_underscore;
use crate::connectors::ConnectorOptions;
use crate::protocols::tls::client::handshake;
use crate::protocols::tls::SslStream;
@@ -31,6 +32,8 @@ use crate::tls::ssl::{SslConnector, SslFiletype, SslMethod, SslVerifyMode, SslVe
use crate::tls::x509::store::X509StoreBuilder;
use crate::upstreams::peer::{Peer, ALPN};
+pub type TlsConnector = SslConnector;
+
const CIPHER_LIST: &str = "AES-128-GCM-SHA256\
:AES-256-GCM-SHA384\
:CHACHA20-POLY1305-SHA256\
@@ -147,33 +150,6 @@ impl Connector {
}
}
-/*
- OpenSSL considers underscores in hostnames non-compliant.
- We replace the underscore in the leftmost label as we must support these
- hostnames for wildcard matches and we have not patched OpenSSL.
-
- https://github.com/openssl/openssl/issues/12566
-
- > The labels must follow the rules for ARPANET host names. They must
- > start with a letter, end with a letter or digit, and have as interior
- > characters only letters, digits, and hyphen. There are also some
- > restrictions on the length. Labels must be 63 characters or less.
- - https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
-*/
-fn replace_leftmost_underscore(sni: &str) -> Option<String> {
- // wildcard is only leftmost label
- if let Some((leftmost, rest)) = sni.split_once('.') {
- // if not a subdomain or leftmost does not contain underscore return
- if !rest.contains('.') || !leftmost.contains('_') {
- return None;
- }
- // we have a subdomain, replace underscores
- let leftmost = leftmost.replace('_', "-");
- return Some(format!("{leftmost}.{rest}"));
- }
- None
-}
-
pub(crate) async fn connect<T, P>(
stream: T,
peer: &P,
@@ -283,41 +259,3 @@ where
None => connect_future.await,
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_replace_leftmost_underscore() {
- let none_cases = [
- "",
- "some",
- "some.com",
- "1.1.1.1:5050",
- "dog.dot.com",
- "dog.d_t.com",
- "dog.dot.c_m",
- "d_g.com",
- "_",
- "dog.c_m",
- ];
-
- for case in none_cases {
- assert!(replace_leftmost_underscore(case).is_none(), "{}", case);
- }
-
- assert_eq!(
- Some("bb-b.some.com".to_string()),
- replace_leftmost_underscore("bb_b.some.com")
- );
- assert_eq!(
- Some("a-a-a.some.com".to_string()),
- replace_leftmost_underscore("a_a_a.some.com")
- );
- assert_eq!(
- Some("-.some.com".to_string()),
- replace_leftmost_underscore("_.some.com")
- );
- }
-}
diff --git a/pingora-core/src/connectors/tls/mod.rs b/pingora-core/src/connectors/tls/mod.rs
index 2a4d469..e250418 100644
--- a/pingora-core/src/connectors/tls/mod.rs
+++ b/pingora-core/src/connectors/tls/mod.rs
@@ -1,5 +1,84 @@
-#[cfg(feature = "some_tls")]
+// Copyright 2024 Cloudflare, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[cfg(feature = "openssl_derived")]
mod boringssl_openssl;
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "openssl_derived")]
pub use boringssl_openssl::*;
+
+/// OpenSSL considers underscores in hostnames non-compliant.
+/// We replace the underscore in the leftmost label as we must support these
+/// hostnames for wildcard matches and we have not patched OpenSSL.
+///
+/// https://github.com/openssl/openssl/issues/12566
+///
+/// > The labels must follow the rules for ARPANET host names. They must
+/// > start with a letter, end with a letter or digit, and have as interior
+/// > characters only letters, digits, and hyphen. There are also some
+/// > restrictions on the length. Labels must be 63 characters or less.
+/// - https://datatracker.ietf.org/doc/html/rfc1034#section-3.5
+#[cfg(feature = "any_tls")]
+pub fn replace_leftmost_underscore(sni: &str) -> Option<String> {
+ // wildcard is only leftmost label
+ if let Some((leftmost, rest)) = sni.split_once('.') {
+ // if not a subdomain or leftmost does not contain underscore return
+ if !rest.contains('.') || !leftmost.contains('_') {
+ return None;
+ }
+ // we have a subdomain, replace underscores
+ let leftmost = leftmost.replace('_', "-");
+ return Some(format!("{leftmost}.{rest}"));
+ }
+ None
+}
+
+#[cfg(feature = "any_tls")]
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_replace_leftmost_underscore() {
+ let none_cases = [
+ "",
+ "some",
+ "some.com",
+ "1.1.1.1:5050",
+ "dog.dot.com",
+ "dog.d_t.com",
+ "dog.dot.c_m",
+ "d_g.com",
+ "_",
+ "dog.c_m",
+ ];
+
+ for case in none_cases {
+ assert!(replace_leftmost_underscore(case).is_none(), "{}", case);
+ }
+
+ assert_eq!(
+ Some("bb-b.some.com".to_string()),
+ replace_leftmost_underscore("bb_b.some.com")
+ );
+ assert_eq!(
+ Some("a-a-a.some.com".to_string()),
+ replace_leftmost_underscore("a_a_a.some.com")
+ );
+ assert_eq!(
+ Some("-.some.com".to_string()),
+ replace_leftmost_underscore("_.some.com")
+ );
+ }
+}
diff --git a/pingora-core/src/lib.rs b/pingora-core/src/lib.rs
index fbc3394..f4b5106 100644
--- a/pingora-core/src/lib.rs
+++ b/pingora-core/src/lib.rs
@@ -55,11 +55,14 @@ pub use pingora_error::{ErrorType::*, *};
#[cfg(feature = "boringssl")]
pub use pingora_boringssl as tls;
-#[cfg(all(not(feature = "boringssl"), feature = "openssl"))]
+#[cfg(feature = "openssl")]
pub use pingora_openssl as tls;
-#[cfg(not(feature = "some_tls"))]
-pub use protocols::tls::dummy_tls as tls;
+#[cfg(feature = "rustls")]
+pub use pingora_rustls as tls;
+
+#[cfg(not(feature = "any_tls"))]
+pub use protocols::tls::noop_tls as tls;
pub mod prelude {
pub use crate::server::configuration::Opt;
diff --git a/pingora-core/src/listeners/mod.rs b/pingora-core/src/listeners/mod.rs
index 35e30d5..4a21632 100644
--- a/pingora-core/src/listeners/mod.rs
+++ b/pingora-core/src/listeners/mod.rs
@@ -15,21 +15,40 @@
//! The listening endpoints (TCP and TLS) and their configurations.
mod l4;
-mod tls;
-use crate::protocols::Stream;
+#[cfg(feature = "any_tls")]
+pub mod tls;
+
+#[cfg(not(feature = "any_tls"))]
+pub use crate::tls::listeners as tls;
+
+use crate::protocols::{tls::TlsRef, Stream};
+
#[cfg(unix)]
use crate::server::ListenFds;
+use async_trait::async_trait;
use pingora_error::Result;
use std::{fs::Permissions, sync::Arc};
use l4::{ListenerEndpoint, Stream as L4Stream};
-use tls::Acceptor;
+use tls::{Acceptor, TlsSettings};
-pub use crate::protocols::tls::server::TlsAccept;
+pub use crate::protocols::tls::ALPN;
pub use l4::{ServerAddress, TcpSocketOptions};
-pub use tls::{TlsSettings, ALPN};
+
+/// The APIs to customize things like certificate during TLS server side handshake
+#[async_trait]
+pub trait TlsAccept {
+ // TODO: return error?
+ /// This function is called in the middle of a TLS handshake. Structs who implement this function
+ /// should provide tls certificate and key to the [TlsRef] via [ext::ssl_use_certificate] and [ext::ssl_use_private_key].
+ async fn certificate_callback(&self, _ssl: &mut TlsRef) -> () {
+ // does nothing by default
+ }
+}
+
+pub type TlsAcceptCallbacks = Box<dyn TlsAccept + Send + Sync>;
struct TransportStackBuilder {
l4: ServerAddress,
@@ -200,6 +219,7 @@ impl Listeners {
#[cfg(test)]
mod test {
use super::*;
+ #[cfg(feature = "any_tls")]
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration};
@@ -233,7 +253,7 @@ mod test {
}
#[tokio::test]
- #[cfg(feature = "some_tls")]
+ #[cfg(feature = "any_tls")]
async fn test_listen_tls() {
use tokio::io::AsyncReadExt;
diff --git a/pingora-core/src/listeners/tls/boringssl_openssl/mod.rs b/pingora-core/src/listeners/tls/boringssl_openssl/mod.rs
index 3780a94..500f959 100644
--- a/pingora-core/src/listeners/tls/boringssl_openssl/mod.rs
+++ b/pingora-core/src/listeners/tls/boringssl_openssl/mod.rs
@@ -16,14 +16,16 @@ use log::debug;
use pingora_error::{ErrorType, OrErr, Result};
use std::ops::{Deref, DerefMut};
-use crate::protocols::tls::{
- server::{handshake, handshake_with_callback, TlsAcceptCallbacks},
- SslStream,
-};
+pub use crate::protocols::tls::ALPN;
use crate::protocols::IO;
use crate::tls::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
-
-pub use crate::protocols::tls::ALPN;
+use crate::{
+ listeners::TlsAcceptCallbacks,
+ protocols::tls::{
+ server::{handshake, handshake_with_callback},
+ SslStream,
+ },
+};
pub const TLS_CONF_ERR: ErrorType = ErrorType::Custom("TLSConfigError");
diff --git a/pingora-core/src/listeners/tls/mod.rs b/pingora-core/src/listeners/tls/mod.rs
index 2a4d469..0bcaeac 100644
--- a/pingora-core/src/listeners/tls/mod.rs
+++ b/pingora-core/src/listeners/tls/mod.rs
@@ -1,5 +1,19 @@
-#[cfg(feature = "some_tls")]
+// Copyright 2024 Cloudflare, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[cfg(feature = "openssl_derived")]
mod boringssl_openssl;
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "openssl_derived")]
pub use boringssl_openssl::*;
diff --git a/pingora-core/src/protocols/mod.rs b/pingora-core/src/protocols/mod.rs
index 62efb9c..c9218b9 100644
--- a/pingora-core/src/protocols/mod.rs
+++ b/pingora-core/src/protocols/mod.rs
@@ -55,7 +55,7 @@ pub trait UniqueID {
/// Interface to get TLS info
pub trait Ssl {
/// Return the TLS info if the connection is over TLS
- fn get_ssl(&self) -> Option<&crate::tls::ssl::SslRef> {
+ fn get_ssl(&self) -> Option<&TlsRef> {
None
}
@@ -66,8 +66,7 @@ pub trait Ssl {
/// Return selected ALPN if any
fn selected_alpn_proto(&self) -> Option<ALPN> {
- let ssl = self.get_ssl()?;
- ALPN::from_wire_selected(ssl.selected_alpn_protocol()?)
+ None
}
}
@@ -249,6 +248,8 @@ use std::os::unix::prelude::AsRawFd;
use std::os::windows::io::AsRawSocket;
use std::{net::SocketAddr as InetSocketAddr, path::Path};
+use crate::protocols::tls::TlsRef;
+
#[cfg(unix)]
impl ConnFdReusable for SocketAddr {
fn check_fd_match<V: AsRawFd>(&self, fd: V) -> bool {
diff --git a/pingora-core/src/protocols/tls/boringssl_openssl/mod.rs b/pingora-core/src/protocols/tls/boringssl_openssl/mod.rs
index c07f47e..3c6584e 100644
--- a/pingora-core/src/protocols/tls/boringssl_openssl/mod.rs
+++ b/pingora-core/src/protocols/tls/boringssl_openssl/mod.rs
@@ -1,2 +1,29 @@
+// Copyright 2024 Cloudflare, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
pub mod client;
pub mod server;
+mod stream;
+
+#[cfg(feature = "boringssl")]
+use pingora_boringssl as ssl_lib;
+
+#[cfg(feature = "openssl")]
+use pingora_openssl as ssl_lib;
+
+use ssl_lib::{ssl::SslRef, x509::X509};
+pub use stream::*;
+
+pub type TlsRef = SslRef;
+pub type CaType = Box<[X509]>;
diff --git a/pingora-core/src/protocols/tls/boringssl_openssl/server.rs b/pingora-core/src/protocols/tls/boringssl_openssl/server.rs
index b7f2f6d..024565a 100644
--- a/pingora-core/src/protocols/tls/boringssl_openssl/server.rs
+++ b/pingora-core/src/protocols/tls/boringssl_openssl/server.rs
@@ -14,12 +14,13 @@
//! TLS server specific implementation
+use crate::listeners::TlsAcceptCallbacks;
use crate::protocols::tls::SslStream;
use crate::protocols::{Shutdown, IO};
use crate::tls::ext;
use crate::tls::ext::ssl_from_acceptor;
use crate::tls::ssl;
-use crate::tls::ssl::{SslAcceptor, SslRef};
+use crate::tls::ssl::SslAcceptor;
use async_trait::async_trait;
use log::warn;
@@ -69,19 +70,6 @@ pub async fn handshake_with_callback<S: IO>(
}
}
-/// The APIs to customize things like certificate during TLS server side handshake
-#[async_trait]
-pub trait TlsAccept {
- // TODO: return error?
- /// This function is called in the middle of a TLS handshake. Structs who implement this function
- /// should provide tls certificate and key to the [SslRef] via [ext::ssl_use_certificate] and [ext::ssl_use_private_key].
- async fn certificate_callback(&self, _ssl: &mut SslRef) -> () {
- // does nothing by default
- }
-}
-
-pub type TlsAcceptCallbacks = Box<dyn TlsAccept + Send + Sync>;
-
#[async_trait]
impl<S> Shutdown for SslStream<S>
where
@@ -143,9 +131,12 @@ impl<S: AsyncRead + AsyncWrite + Send + Unpin> ResumableAccept for SslStream<S>
}
#[tokio::test]
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "any_tls")]
async fn test_async_cert() {
+ use crate::protocols::tls::TlsRef;
use tokio::io::AsyncReadExt;
+
+ use crate::listeners::{TlsAccept, TlsAcceptCallbacks};
let acceptor = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls())
.unwrap()
.build();
@@ -153,7 +144,7 @@ async fn test_async_cert() {
struct Callback;
#[async_trait]
impl TlsAccept for Callback {
- async fn certificate_callback(&self, ssl: &mut SslRef) -> () {
+ async fn certificate_callback(&self, ssl: &mut TlsRef) -> () {
assert_eq!(
ssl.servername(ssl::NameType::HOST_NAME).unwrap(),
"pingora.org"
diff --git a/pingora-core/src/protocols/tls/boringssl_openssl/stream.rs b/pingora-core/src/protocols/tls/boringssl_openssl/stream.rs
new file mode 100644
index 0000000..38c3027
--- /dev/null
+++ b/pingora-core/src/protocols/tls/boringssl_openssl/stream.rs
@@ -0,0 +1,217 @@
+// Copyright 2024 Cloudflare, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::protocols::digest::TimingDigest;
+use crate::protocols::tls::{SslDigest, ALPN};
+use crate::protocols::{Peek, Ssl, UniqueID, UniqueIDType};
+use crate::tls::{self, ssl, tokio_ssl::SslStream as InnerSsl};
+use crate::utils::tls::{get_organization, get_serial};
+use log::warn;
+use pingora_error::{ErrorType::*, OrErr, Result};
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::{Context, Poll};
+use std::time::SystemTime;
+use tokio::io::{self, AsyncRead, AsyncWrite, ReadBuf};
+
+#[cfg(feature = "boringssl")]
+use pingora_boringssl as ssl_lib;
+
+#[cfg(feature = "openssl")]
+use pingora_openssl as ssl_lib;
+
+use ssl_lib::{hash::MessageDigest, ssl::SslRef};
+
+/// The TLS connection
+#[derive(Debug)]
+pub struct SslStream<T> {
+ ssl: InnerSsl<T>,
+ digest: Option<Arc<SslDigest>>,
+ pub(super) timing: TimingDigest,
+}
+
+impl<T> SslStream<T>
+where
+ T: AsyncRead + AsyncWrite + std::marker::Unpin,
+{
+ /// Create a new TLS connection from the given `stream`
+ ///
+ /// The caller needs to perform [`Self::connect()`] or [`Self::accept()`] to perform TLS
+ /// handshake after.
+ pub fn new(ssl: ssl::Ssl, stream: T) -> Result<Self> {
+ let ssl = InnerSsl::new(ssl, stream)
+ .explain_err(TLSHandshakeFailure, |e| format!("ssl stream error: {e}"))?;
+
+ Ok(SslStream {
+ ssl,
+ digest: None,
+ timing: Default::default(),
+ })
+ }
+
+ /// Connect to the remote TLS server as a client
+ pub async fn connect(&mut self) -> Result<(), ssl::Error> {
+ Self::clear_error();
+ Pin::new(&mut self.ssl).connect().await?;
+ self.timing.established_ts = SystemTime::now();
+ self.digest = Some(Arc::new(SslDigest::from_ssl(self.ssl())));
+ Ok(())
+ }
+
+ /// Finish the TLS handshake from client as a server
+ pub async fn accept(&mut self) -> Result<(), ssl::Error> {
+ Self::clear_error();
+ Pin::new(&mut self.ssl).accept().await?;
+ self.timing.established_ts = SystemTime::now();
+ self.digest = Some(Arc::new(SslDigest::from_ssl(self.ssl())));
+ Ok(())
+ }
+
+ #[inline]
+ fn clear_error() {
+ let errs = tls::error::ErrorStack::get();
+ if !errs.errors().is_empty() {
+ warn!("Clearing dirty TLS error stack: {}", errs);
+ }
+ }
+}
+
+impl<T> SslStream<T> {
+ pub fn ssl_digest(&self) -> Option<Arc<SslDigest>> {
+ self.digest.clone()
+ }
+}
+
+use std::ops::{Deref, DerefMut};
+
+impl<T> Deref for SslStream<T> {
+ type Target = InnerSsl<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.ssl
+ }
+}
+
+impl<T> DerefMut for SslStream<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.ssl
+ }
+}
+
+impl<T> AsyncRead for SslStream<T>
+where
+ T: AsyncRead + AsyncWrite + Unpin,
+{
+ fn poll_read(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<io::Result<()>> {
+ Self::clear_error();
+ Pin::new(&mut self.ssl).poll_read(cx, buf)
+ }
+}
+
+impl<T> AsyncWrite for SslStream<T>
+where
+ T: AsyncRead + AsyncWrite + Unpin,
+{
+ fn poll_write(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &[u8],
+ ) -> Poll<io::Result<usize>> {
+ Self::clear_error();
+ Pin::new(&mut self.ssl).poll_write(cx, buf)
+ }
+
+ fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
+ Self::clear_error();
+ Pin::new(&mut self.ssl).poll_flush(cx)
+ }
+
+ fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
+ Self::clear_error();
+ Pin::new(&mut self.ssl).poll_shutdown(cx)
+ }
+
+ fn poll_write_vectored(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ bufs: &[std::io::IoSlice<'_>],
+ ) -> Poll<io::Result<usize>> {
+ Self::clear_error();
+ Pin::new(&mut self.ssl).poll_write_vectored(cx, bufs)
+ }
+
+ fn is_write_vectored(&self) -> bool {
+ true
+ }
+}
+
+impl<T> UniqueID for SslStream<T>
+where
+ T: UniqueID,
+{
+ fn id(&self) -> UniqueIDType {
+ self.ssl.get_ref().id()
+ }
+}
+
+impl<T> Ssl for SslStream<T> {
+ fn get_ssl(&self) -> Option<&ssl::SslRef> {
+ Some(self.ssl())
+ }
+
+ fn get_ssl_digest(&self) -> Option<Arc<SslDigest>> {
+ self.ssl_digest()
+ }
+
+ /// Return selected ALPN if any
+ fn selected_alpn_proto(&self) -> Option<ALPN> {
+ let ssl = self.get_ssl()?;
+ ALPN::from_wire_selected(ssl.selected_alpn_protocol()?)
+ }
+}
+
+impl SslDigest {
+ pub fn from_ssl(ssl: &SslRef) -> Self {
+ let cipher = match ssl.current_cipher() {
+ Some(c) => c.name(),
+ None => "",
+ };
+
+ let (cert_digest, org, sn) = match ssl.peer_certificate() {
+ Some(cert) => {
+ let cert_digest = match cert.digest(MessageDigest::sha256()) {
+ Ok(c) => c.as_ref().to_vec(),
+ Err(_) => Vec::new(),
+ };
+ (cert_digest, get_organization(&cert), get_serial(&cert).ok())
+ }
+ None => (Vec::new(), None, None),
+ };
+
+ SslDigest {
+ cipher,
+ version: ssl.version_str(),
+ organization: org,
+ serial_number: sn,
+ cert_digest,
+ }
+ }
+}
+
+// TODO: implement Peek if needed
+impl<T> Peek for SslStream<T> {}
diff --git a/pingora-core/src/protocols/tls/digest.rs b/pingora-core/src/protocols/tls/digest.rs
index 3cdb7aa..72adae6 100644
--- a/pingora-core/src/protocols/tls/digest.rs
+++ b/pingora-core/src/protocols/tls/digest.rs
@@ -14,9 +14,6 @@
//! TLS information from the TLS connection
-use crate::tls::{hash::MessageDigest, ssl::SslRef};
-use crate::utils;
-
/// The TLS connection information
#[derive(Clone, Debug)]
pub struct SslDigest {
@@ -31,35 +28,3 @@ pub struct SslDigest {
/// The digest of the peer's certificate
pub cert_digest: Vec<u8>,
}
-
-impl SslDigest {
- pub fn from_ssl(ssl: &SslRef) -> Self {
- let cipher = match ssl.current_cipher() {
- Some(c) => c.name(),
- None => "",
- };
-
- let (cert_digest, org, sn) = match ssl.peer_certificate() {
- Some(cert) => {
- let cert_digest = match cert.digest(MessageDigest::sha256()) {
- Ok(c) => c.as_ref().to_vec(),
- Err(_) => Vec::new(),
- };
- (
- cert_digest,
- utils::get_organization(&cert),
- utils::get_serial(&cert).ok(),
- )
- }
- None => (Vec::new(), None, None),
- };
-
- SslDigest {
- cipher,
- version: ssl.version_str(),
- organization: org,
- serial_number: sn,
- cert_digest,
- }
- }
-}
diff --git a/pingora-core/src/protocols/tls/dummy_tls/mod.rs b/pingora-core/src/protocols/tls/dummy_tls/mod.rs
deleted file mode 100644
index 9a841d6..0000000
--- a/pingora-core/src/protocols/tls/dummy_tls/mod.rs
+++ /dev/null
@@ -1,805 +0,0 @@
-// Copyright 2024 Cloudflare, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! This module contains a dummy TLS implementation for the scenarios where real TLS
-//! implementations are unavailable.
-
-macro_rules! impl_display {
- ($ty:ty) => {
- impl std::fmt::Display for $ty {
- fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
- Ok(())
- }
- }
- };
-}
-
-macro_rules! impl_deref {
- ($from:ty => $to:ty) => {
- impl std::ops::Deref for $from {
- type Target = $to;
- fn deref(&self) -> &$to {
- unimplemented!();
- }
- }
- impl std::ops::DerefMut for $from {
- fn deref_mut(&mut self) -> &mut $to {
- unimplemented!();
- }
- }
- };
-}
-
-pub mod ssl {
- use super::error::ErrorStack;
- use super::x509::verify::X509VerifyParamRef;
- use super::x509::{X509VerifyResult, X509};
-
- /// An error returned from an ALPN selection callback.
- pub struct AlpnError;
- impl AlpnError {
- /// Terminate the handshake with a fatal alert.
- pub const ALERT_FATAL: AlpnError = Self {};
-
- /// Do not select a protocol, but continue the handshake.
- pub const NOACK: AlpnError = Self {};
- }
-
- /// A type which allows for configuration of a client-side TLS session before connection.
- pub struct ConnectConfiguration;
- impl_deref! {ConnectConfiguration => SslRef}
- impl ConnectConfiguration {
- /// Configures the use of Server Name Indication (SNI) when connecting.
- pub fn set_use_server_name_indication(&mut self, _use_sni: bool) {
- unimplemented!();
- }
-
- /// Configures the use of hostname verification when connecting.
- pub fn set_verify_hostname(&mut self, _verify_hostname: bool) {
- unimplemented!();
- }
-
- /// Returns an `Ssl` configured to connect to the provided domain.
- pub fn into_ssl(self, _domain: &str) -> Result<Ssl, ErrorStack> {
- unimplemented!();
- }
-
- /// Like `SslContextBuilder::set_verify`.
- pub fn set_verify(&mut self, _mode: SslVerifyMode) {
- unimplemented!();
- }
-
- /// Like `SslContextBuilder::set_alpn_protos`.
- pub fn set_alpn_protos(&mut self, _protocols: &[u8]) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Returns a mutable reference to the X509 verification configuration.
- pub fn param_mut(&mut self) -> &mut X509VerifyParamRef {
- unimplemented!();
- }
- }
-
- /// An SSL error.
- #[derive(Debug)]
- pub struct Error;
- impl_display!(Error);
- impl Error {
- pub fn code(&self) -> ErrorCode {
- unimplemented!();
- }
- }
-
- /// An error code returned from SSL functions.
- #[derive(PartialEq)]
- pub struct ErrorCode(i32);
- impl ErrorCode {
- /// An error occurred in the SSL library.
- pub const SSL: ErrorCode = Self(0);
- }
-
- /// An identifier of a session name type.
- pub struct NameType;
- impl NameType {
- pub const HOST_NAME: NameType = Self {};
- }
-
- /// The state of an SSL/TLS session.
- pub struct Ssl;
- impl Ssl {
- /// Creates a new `Ssl`.
- pub fn new(_ctx: &SslContextRef) -> Result<Ssl, ErrorStack> {
- unimplemented!();
- }
- }
- impl_deref! {Ssl => SslRef}
-
- /// A type which wraps server-side streams in a TLS session.
- pub struct SslAcceptor;
- impl SslAcceptor {
- /// Creates a new builder configured to connect to non-legacy clients. This should
- /// generally be considered a reasonable default choice.
- pub fn mozilla_intermediate_v5(
- _method: SslMethod,
- ) -> Result<SslAcceptorBuilder, ErrorStack> {
- unimplemented!();
- }
- }
-
- /// A builder for `SslAcceptor`s.
- pub struct SslAcceptorBuilder;
- impl SslAcceptorBuilder {
- /// Consumes the builder, returning a `SslAcceptor`.
- pub fn build(self) -> SslAcceptor {
- unimplemented!();
- }
-
- /// Sets the callback used by a server to select a protocol for Application Layer Protocol
- /// Negotiation (ALPN).
- pub fn set_alpn_select_callback<F>(&mut self, _callback: F)
- where
- F: for<'a> Fn(&mut SslRef, &'a [u8]) -> Result<&'a [u8], AlpnError>
- + 'static
- + Sync
- + Send,
- {
- unimplemented!();
- }
-
- /// Loads a certificate chain from a file.
- pub fn set_certificate_chain_file<P: AsRef<std::path::Path>>(
- &mut self,
- _file: P,
- ) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Loads the private key from a file.
- pub fn set_private_key_file<P: AsRef<std::path::Path>>(
- &mut self,
- _file: P,
- _file_type: SslFiletype,
- ) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Sets the maximum supported protocol version.
- pub fn set_max_proto_version(
- &mut self,
- _version: Option<SslVersion>,
- ) -> Result<(), ErrorStack> {
- unimplemented!();
- }
- }
-
- /// Reference to an [`SslCipher`].
- pub struct SslCipherRef;
- impl SslCipherRef {
- /// Returns the name of the cipher.
- pub fn name(&self) -> &'static str {
- unimplemented!();
- }
- }
-
- /// A type which wraps client-side streams in a TLS session.
- pub struct SslConnector;
- impl SslConnector {
- /// Creates a new builder for TLS connections.
- pub fn builder(_method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> {
- Ok(SslConnectorBuilder)
- }
-
- /// Returns a structure allowing for configuration of a single TLS session before connection.
- pub fn configure(&self) -> Result<ConnectConfiguration, ErrorStack> {
- Ok(ConnectConfiguration)
- }
-
- /// Returns a shared reference to the inner raw `SslContext`.
- pub fn context(&self) -> &SslContextRef {
- &SslContextRef
- }
- }
-
- /// A builder for `SslConnector`s.
- pub struct SslConnectorBuilder;
- impl SslConnectorBuilder {
- /// Consumes the builder, returning an `SslConnector`.
- pub fn build(self) -> SslConnector {
- SslConnector
- }
-
- /// Sets the list of supported ciphers for protocols before TLSv1.3.
- pub fn set_cipher_list(&mut self, _cipher_list: &str) -> Result<(), ErrorStack> {
- Ok(())
- }
-
- /// Sets the context’s supported signature algorithms.
- pub fn set_sigalgs_list(&mut self, _sigalgs: &str) -> Result<(), ErrorStack> {
- Ok(())
- }
-
- /// Sets the minimum supported protocol version.
- pub fn set_min_proto_version(
- &mut self,
- _version: Option<SslVersion>,
- ) -> Result<(), ErrorStack> {
- Ok(())
- }
-
- /// Sets the maximum supported protocol version.
- pub fn set_max_proto_version(
- &mut self,
- _version: Option<SslVersion>,
- ) -> Result<(), ErrorStack> {
- Ok(())
- }
-
- /// Use the default locations of trusted certificates for verification.
- pub fn set_default_verify_paths(&mut self) -> Result<(), ErrorStack> {
- Ok(())
- }
-
- /// Loads trusted root certificates from a file.
- pub fn set_ca_file<P: AsRef<std::path::Path>>(
- &mut self,
- _file: P,
- ) -> Result<(), ErrorStack> {
- Ok(())
- }
-
- /// Loads a leaf certificate from a file.
- pub fn set_certificate_file<P: AsRef<std::path::Path>>(
- &mut self,
- _file: P,
- _file_type: SslFiletype,
- ) -> Result<(), ErrorStack> {
- Ok(())
- }
-
- /// Loads the private key from a file.
- pub fn set_private_key_file<P: AsRef<std::path::Path>>(
- &mut self,
- _file: P,
- _file_type: SslFiletype,
- ) -> Result<(), ErrorStack> {
- Ok(())
- }
-
- /// Sets the TLS key logging callback.
- pub fn set_keylog_callback<F>(&mut self, _callback: F)
- where
- F: Fn(&SslRef, &str) + 'static + Sync + Send,
- {
- }
- }
-
- /// A context object for TLS streams.
- pub struct SslContext;
- impl SslContext {
- /// Creates a new builder object for an `SslContext`.
- pub fn builder(_method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
- unimplemented!();
- }
- }
- impl_deref! {SslContext => SslContextRef}
-
- /// A builder for `SslContext`s.
- pub struct SslContextBuilder;
- impl SslContextBuilder {
- /// Consumes the builder, returning a new `SslContext`.
- pub fn build(self) -> SslContext {
- unimplemented!();
- }
- }
-
- /// Reference to [`SslContext`]
- pub struct SslContextRef;
-
- /// An identifier of the format of a certificate or key file.
- pub struct SslFiletype;
- impl SslFiletype {
- /// The PEM format.
- pub const PEM: SslFiletype = Self {};
- }
-
- /// A type specifying the kind of protocol an `SslContext`` will speak.
- pub struct SslMethod;
- impl SslMethod {
- /// Support all versions of the TLS protocol.
- pub fn tls() -> SslMethod {
- Self
- }
- }
-
- /// Reference to an [`Ssl`].
- pub struct SslRef;
- impl SslRef {
- /// Like [`SslContextBuilder::set_verify`].
- pub fn set_verify(&mut self, _mode: SslVerifyMode) {
- unimplemented!();
- }
-
- /// Returns the current cipher if the session is active.
- pub fn current_cipher(&self) -> Option<&SslCipherRef> {
- unimplemented!();
- }
-
- /// Sets the host name to be sent to the server for Server Name Indication (SNI).
- pub fn set_hostname(&mut self, _hostname: &str) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Returns the peer’s certificate, if present.
- pub fn peer_certificate(&self) -> Option<X509> {
- unimplemented!();
- }
-
- /// Returns the certificate verification result.
- pub fn verify_result(&self) -> X509VerifyResult {
- unimplemented!();
- }
-
- /// Returns a string describing the protocol version of the session.
- pub fn version_str(&self) -> &'static str {
- unimplemented!();
- }
-
- /// Returns the protocol selected via Application Layer Protocol Negotiation (ALPN).
- pub fn selected_alpn_protocol(&self) -> Option<&[u8]> {
- unimplemented!();
- }
-
- /// Returns the servername sent by the client via Server Name Indication (SNI).
- pub fn servername(&self, _type_: NameType) -> Option<&str> {
- unimplemented!();
- }
- }
-
- /// Options controlling the behavior of certificate verification.
- pub struct SslVerifyMode;
- impl SslVerifyMode {
- /// Verifies that the peer’s certificate is trusted.
- pub const PEER: Self = Self {};
-
- /// Disables verification of the peer’s certificate.
- pub const NONE: Self = Self {};
- }
-
- /// An SSL/TLS protocol version.
- pub struct SslVersion;
- impl SslVersion {
- /// TLSv1.0
- pub const TLS1: SslVersion = Self {};
-
- /// TLSv1.2
- pub const TLS1_2: SslVersion = Self {};
-
- /// TLSv1.3
- pub const TLS1_3: SslVersion = Self {};
- }
-
- /// A standard implementation of protocol selection for Application Layer Protocol Negotiation
- /// (ALPN).
- pub fn select_next_proto<'a>(_server: &[u8], _client: &'a [u8]) -> Option<&'a [u8]> {
- unimplemented!();
- }
-}
-
-pub mod ssl_sys {
- pub const X509_V_OK: i32 = 0;
- pub const X509_V_ERR_INVALID_CALL: i32 = 69;
-}
-
-pub mod error {
- use super::ssl::Error;
-
- /// Collection of [`Errors`] from OpenSSL.
- #[derive(Debug)]
- pub struct ErrorStack;
- impl_display!(ErrorStack);
- impl std::error::Error for ErrorStack {}
- impl ErrorStack {
- /// Returns the contents of the OpenSSL error stack.
- pub fn get() -> ErrorStack {
- unimplemented!();
- }
-
- /// Returns the errors in the stack.
- pub fn errors(&self) -> &[Error] {
- unimplemented!();
- }
- }
-}
-
-pub mod x509 {
- use super::asn1::{Asn1IntegerRef, Asn1StringRef, Asn1TimeRef};
- use super::error::ErrorStack;
- use super::hash::{DigestBytes, MessageDigest};
- use super::nid::Nid;
-
- /// An `X509` public key certificate.
- #[derive(Debug, Clone)]
- pub struct X509;
- impl_deref! {X509 => X509Ref}
- impl X509 {
- /// Deserializes a PEM-encoded X509 structure.
- pub fn from_pem(_pem: &[u8]) -> Result<X509, ErrorStack> {
- unimplemented!();
- }
- }
-
- /// A type to destructure and examine an `X509Name`.
- pub struct X509NameEntries<'a> {
- marker: std::marker::PhantomData<&'a ()>,
- }
- impl<'a> Iterator for X509NameEntries<'a> {
- type Item = &'a X509NameEntryRef;
- fn next(&mut self) -> Option<&'a X509NameEntryRef> {
- unimplemented!();
- }
- }
-
- /// Reference to `X509NameEntry`.
- pub struct X509NameEntryRef;
- impl X509NameEntryRef {
- pub fn data(&self) -> &Asn1StringRef {
- unimplemented!();
- }
- }
-
- /// Reference to `X509Name`.
- pub struct X509NameRef;
- impl X509NameRef {
- /// Returns the name entries by the nid.
- pub fn entries_by_nid(&self, _nid: Nid) -> X509NameEntries<'_> {
- unimplemented!();
- }
- }
-
- /// Reference to `X509`.
- pub struct X509Ref;
- impl X509Ref {
- /// Returns this certificate’s subject name.
- pub fn subject_name(&self) -> &X509NameRef {
- unimplemented!();
- }
-
- /// Returns a digest of the DER representation of the certificate.
- pub fn digest(&self, _hash_type: MessageDigest) -> Result<DigestBytes, ErrorStack> {
- unimplemented!();
- }
-
- /// Returns the certificate’s Not After validity period.
- pub fn not_after(&self) -> &Asn1TimeRef {
- unimplemented!();
- }
-
- /// Returns this certificate’s serial number.
- pub fn serial_number(&self) -> &Asn1IntegerRef {
- unimplemented!();
- }
- }
-
- /// The result of peer certificate verification.
- pub struct X509VerifyResult;
- impl X509VerifyResult {
- /// Return the integer representation of an `X509VerifyResult`.
- pub fn as_raw(&self) -> i32 {
- unimplemented!();
- }
- }
-
- pub mod store {
- use super::super::error::ErrorStack;
- use super::X509;
-
- /// A builder type used to construct an `X509Store`.
- pub struct X509StoreBuilder;
- impl X509StoreBuilder {
- /// Returns a builder for a certificate store..
- pub fn new() -> Result<X509StoreBuilder, ErrorStack> {
- unimplemented!();
- }
-
- /// Constructs the `X509Store`.
- pub fn build(self) -> X509Store {
- unimplemented!();
- }
-
- /// Adds a certificate to the certificate store.
- pub fn add_cert(&mut self, _cert: X509) -> Result<(), ErrorStack> {
- unimplemented!();
- }
- }
-
- /// A certificate store to hold trusted X509 certificates.
- pub struct X509Store;
- impl_deref! {X509Store => X509StoreRef}
-
- /// Reference to an `X509Store`.
- pub struct X509StoreRef;
- }
-
- pub mod verify {
- /// Reference to `X509VerifyParam`.
- pub struct X509VerifyParamRef;
- }
-}
-
-pub mod nid {
- /// A numerical identifier for an OpenSSL object.
- pub struct Nid;
- impl Nid {
- pub const COMMONNAME: Nid = Self {};
- pub const ORGANIZATIONNAME: Nid = Self {};
- pub const ORGANIZATIONALUNITNAME: Nid = Self {};
- }
-}
-
-pub mod pkey {
- use super::error::ErrorStack;
-
- /// A public or private key.
- #[derive(Clone)]
- pub struct PKey<T> {
- marker: std::marker::PhantomData<T>,
- }
- impl<T> std::ops::Deref for PKey<T> {
- type Target = PKeyRef<T>;
- fn deref(&self) -> &PKeyRef<T> {
- unimplemented!();
- }
- }
- impl<T> std::ops::DerefMut for PKey<T> {
- fn deref_mut(&mut self) -> &mut PKeyRef<T> {
- unimplemented!();
- }
- }
- impl PKey<Private> {
- pub fn private_key_from_pem(_pem: &[u8]) -> Result<PKey<Private>, ErrorStack> {
- unimplemented!();
- }
- }
-
- /// Reference to `PKey`.
- pub struct PKeyRef<T> {
- marker: std::marker::PhantomData<T>,
- }
-
- /// A tag type indicating that a key has private components.
- #[derive(Clone)]
- pub enum Private {}
- unsafe impl HasPrivate for Private {}
-
- /// A trait indicating that a key has private components.
- pub unsafe trait HasPrivate {}
-}
-
-pub mod hash {
- /// A message digest algorithm.
- pub struct MessageDigest;
- impl MessageDigest {
- pub fn sha256() -> MessageDigest {
- unimplemented!();
- }
- }
-
- /// The resulting bytes of a digest.
- pub struct DigestBytes;
- impl AsRef<[u8]> for DigestBytes {
- fn as_ref(&self) -> &[u8] {
- unimplemented!();
- }
- }
-}
-
-pub mod asn1 {
- use super::bn::BigNum;
- use super::error::ErrorStack;
-
- /// A reference to an `Asn1Integer`.
- pub struct Asn1IntegerRef;
- impl Asn1IntegerRef {
- /// Converts the integer to a `BigNum`.
- pub fn to_bn(&self) -> Result<BigNum, ErrorStack> {
- unimplemented!();
- }
- }
-
- /// A reference to an `Asn1String`.
- pub struct Asn1StringRef;
- impl Asn1StringRef {
- pub fn as_utf8(&self) -> Result<&str, ErrorStack> {
- unimplemented!();
- }
- }
-
- /// Reference to an `Asn1Time`
- pub struct Asn1TimeRef;
- impl_display! {Asn1TimeRef}
-}
-
-pub mod bn {
- use super::error::ErrorStack;
-
- /// Dynamically sized large number implementation
- pub struct BigNum;
- impl BigNum {
- /// Returns a hexadecimal string representation of `self`.
- pub fn to_hex_str(&self) -> Result<&str, ErrorStack> {
- unimplemented!();
- }
- }
-}
-
-pub mod ext {
- use super::error::ErrorStack;
- use super::pkey::{HasPrivate, PKeyRef};
- use super::ssl::{Ssl, SslAcceptor, SslRef};
- use super::x509::store::X509StoreRef;
- use super::x509::verify::X509VerifyParamRef;
- use super::x509::X509Ref;
-
- /// Add name as an additional reference identifier that can match the peer's certificate
- pub fn add_host(_verify_param: &mut X509VerifyParamRef, _host: &str) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Set the verify cert store of `_ssl`
- pub fn ssl_set_verify_cert_store(
- _ssl: &mut SslRef,
- _cert_store: &X509StoreRef,
- ) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Load the certificate into `_ssl`
- pub fn ssl_use_certificate(_ssl: &mut SslRef, _cert: &X509Ref) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Load the private key into `_ssl`
- pub fn ssl_use_private_key<T>(_ssl: &mut SslRef, _key: &PKeyRef<T>) -> Result<(), ErrorStack>
- where
- T: HasPrivate,
- {
- unimplemented!();
- }
-
- /// Clear the error stack
- pub fn clear_error_stack() {}
-
- /// Create a new [Ssl] from &[SslAcceptor]
- pub fn ssl_from_acceptor(_acceptor: &SslAcceptor) -> Result<Ssl, ErrorStack> {
- unimplemented!();
- }
-
- /// Suspend the TLS handshake when a certificate is needed.
- pub fn suspend_when_need_ssl_cert(_ssl: &mut SslRef) {
- unimplemented!();
- }
-
- /// Unblock a TLS handshake after the certificate is set.
- pub fn unblock_ssl_cert(_ssl: &mut SslRef) {
- unimplemented!();
- }
-
- /// Whether the TLS error is SSL_ERROR_WANT_X509_LOOKUP
- pub fn is_suspended_for_cert(_error: &super::ssl::Error) -> bool {
- unimplemented!();
- }
-
- /// Add the certificate into the cert chain of `_ssl`
- pub fn ssl_add_chain_cert(_ssl: &mut SslRef, _cert: &X509Ref) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Set renegotiation
- pub fn ssl_set_renegotiate_mode_freely(_ssl: &mut SslRef) {}
-
- /// Set the curves/groups of `_ssl`
- pub fn ssl_set_groups_list(_ssl: &mut SslRef, _groups: &str) -> Result<(), ErrorStack> {
- unimplemented!();
- }
-
- /// Sets whether a second keyshare to be sent in client hello when PQ is used.
- pub fn ssl_use_second_key_share(_ssl: &mut SslRef, _enabled: bool) {}
-
- /// Get a mutable SslRef ouf of SslRef, which is a missing functionality even when holding &mut SslStream
- /// # Safety
- pub unsafe fn ssl_mut(_ssl: &SslRef) -> &mut SslRef {
- unimplemented!();
- }
-}
-
-pub mod tokio_ssl {
- use std::pin::Pin;
- use std::task::{Context, Poll};
- use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
-
- use super::error::ErrorStack;
- use super::ssl::{Error, Ssl, SslRef};
-
- /// A TLS session over a stream.
- #[derive(Debug)]
- pub struct SslStream<S> {
- marker: std::marker::PhantomData<S>,
- }
- impl<S> SslStream<S> {
- /// Creates a new `SslStream`.
- pub fn new(_ssl: Ssl, _stream: S) -> Result<Self, ErrorStack> {
- unimplemented!();
- }
-
- /// Initiates a client-side TLS handshake.
- pub async fn connect(self: Pin<&mut Self>) -> Result<(), Error> {
- unimplemented!();
- }
-
- /// Initiates a server-side TLS handshake.
- pub async fn accept(self: Pin<&mut Self>) -> Result<(), Error> {
- unimplemented!();
- }
-
- /// Returns a shared reference to the `Ssl` object associated with this stream.
- pub fn ssl(&self) -> &SslRef {
- unimplemented!();
- }
-
- /// Returns a shared reference to the underlying stream.
- pub fn get_ref(&self) -> &S {
- unimplemented!();
- }
-
- /// Returns a mutable reference to the underlying stream.
- pub fn get_mut(&mut self) -> &mut S {
- unimplemented!();
- }
- }
- impl<S> AsyncRead for SslStream<S>
- where
- S: AsyncRead + AsyncWrite,
- {
- fn poll_read(
- self: Pin<&mut Self>,
- _ctx: &mut Context<'_>,
- _buf: &mut ReadBuf<'_>,
- ) -> Poll<std::io::Result<()>> {
- unimplemented!();
- }
- }
- impl<S> AsyncWrite for SslStream<S>
- where
- S: AsyncRead + AsyncWrite,
- {
- fn poll_write(
- self: Pin<&mut Self>,
- _ctx: &mut Context<'_>,
- _buf: &[u8],
- ) -> Poll<std::io::Result<usize>> {
- unimplemented!();
- }
-
- fn poll_flush(self: Pin<&mut Self>, _ctx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
- unimplemented!();
- }
-
- fn poll_shutdown(
- self: Pin<&mut Self>,
- _ctx: &mut Context<'_>,
- ) -> Poll<std::io::Result<()>> {
- unimplemented!();
- }
- }
-}
diff --git a/pingora-core/src/protocols/tls/mod.rs b/pingora-core/src/protocols/tls/mod.rs
index fceb0a5..c479a2d 100644
--- a/pingora-core/src/protocols/tls/mod.rs
+++ b/pingora-core/src/protocols/tls/mod.rs
@@ -15,177 +15,19 @@
//! The TLS layer implementations
pub mod digest;
+pub use digest::*;
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "openssl_derived")]
mod boringssl_openssl;
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "openssl_derived")]
pub use boringssl_openssl::*;
-#[cfg(not(feature = "some_tls"))]
-pub mod dummy_tls;
+#[cfg(not(feature = "any_tls"))]
+pub mod noop_tls;
-use crate::protocols::digest::TimingDigest;
-use crate::protocols::{Peek, Ssl, UniqueID, UniqueIDType};
-use crate::tls::{self, ssl, tokio_ssl::SslStream as InnerSsl};
-use log::warn;
-use pingora_error::{ErrorType::*, OrErr, Result};
-use std::pin::Pin;
-use std::sync::Arc;
-use std::task::{Context, Poll};
-use std::time::SystemTime;
-use tokio::io::{self, AsyncRead, AsyncWrite, ReadBuf};
-
-pub use digest::SslDigest;
-
-/// The TLS connection
-#[derive(Debug)]
-pub struct SslStream<T> {
- ssl: InnerSsl<T>,
- digest: Option<Arc<SslDigest>>,
- timing: TimingDigest,
-}
-
-impl<T> SslStream<T>
-where
- T: AsyncRead + AsyncWrite + std::marker::Unpin,
-{
- /// Create a new TLS connection from the given `stream`
- ///
- /// The caller needs to perform [`Self::connect()`] or [`Self::accept()`] to perform TLS
- /// handshake after.
- pub fn new(ssl: ssl::Ssl, stream: T) -> Result<Self> {
- let ssl = InnerSsl::new(ssl, stream)
- .explain_err(TLSHandshakeFailure, |e| format!("ssl stream error: {e}"))?;
-
- Ok(SslStream {
- ssl,
- digest: None,
- timing: Default::default(),
- })
- }
-
- /// Connect to the remote TLS server as a client
- pub async fn connect(&mut self) -> Result<(), ssl::Error> {
- Self::clear_error();
- Pin::new(&mut self.ssl).connect().await?;
- self.timing.established_ts = SystemTime::now();
- self.digest = Some(Arc::new(SslDigest::from_ssl(self.ssl())));
- Ok(())
- }
-
- /// Finish the TLS handshake from client as a server
- pub async fn accept(&mut self) -> Result<(), ssl::Error> {
- Self::clear_error();
- Pin::new(&mut self.ssl).accept().await?;
- self.timing.established_ts = SystemTime::now();
- self.digest = Some(Arc::new(SslDigest::from_ssl(self.ssl())));
- Ok(())
- }
-
- #[inline]
- fn clear_error() {
- let errs = tls::error::ErrorStack::get();
- if !errs.errors().is_empty() {
- warn!("Clearing dirty TLS error stack: {}", errs);
- }
- }
-}
-
-impl<T> SslStream<T> {
- pub fn ssl_digest(&self) -> Option<Arc<SslDigest>> {
- self.digest.clone()
- }
-}
-
-use std::ops::{Deref, DerefMut};
-
-impl<T> Deref for SslStream<T> {
- type Target = InnerSsl<T>;
-
- fn deref(&self) -> &Self::Target {
- &self.ssl
- }
-}
-
-impl<T> DerefMut for SslStream<T> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.ssl
- }
-}
-
-impl<T> AsyncRead for SslStream<T>
-where
- T: AsyncRead + AsyncWrite + Unpin,
-{
- fn poll_read(
- mut self: Pin<&mut Self>,
- cx: &mut Context<'_>,
- buf: &mut ReadBuf<'_>,
- ) -> Poll<io::Result<()>> {
- Self::clear_error();
- Pin::new(&mut self.ssl).poll_read(cx, buf)
- }
-}
-
-impl<T> AsyncWrite for SslStream<T>
-where
- T: AsyncRead + AsyncWrite + Unpin,
-{
- fn poll_write(
- mut self: Pin<&mut Self>,
- cx: &mut Context,
- buf: &[u8],
- ) -> Poll<io::Result<usize>> {
- Self::clear_error();
- Pin::new(&mut self.ssl).poll_write(cx, buf)
- }
-
- fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
- Self::clear_error();
- Pin::new(&mut self.ssl).poll_flush(cx)
- }
-
- fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
- Self::clear_error();
- Pin::new(&mut self.ssl).poll_shutdown(cx)
- }
-
- fn poll_write_vectored(
- mut self: Pin<&mut Self>,
- cx: &mut Context<'_>,
- bufs: &[std::io::IoSlice<'_>],
- ) -> Poll<io::Result<usize>> {
- Self::clear_error();
- Pin::new(&mut self.ssl).poll_write_vectored(cx, bufs)
- }
-
- fn is_write_vectored(&self) -> bool {
- true
- }
-}
-
-impl<T> UniqueID for SslStream<T>
-where
- T: UniqueID,
-{
- fn id(&self) -> UniqueIDType {
- self.ssl.get_ref().id()
- }
-}
-
-impl<T> Ssl for SslStream<T> {
- fn get_ssl(&self) -> Option<&ssl::SslRef> {
- Some(self.ssl())
- }
-
- fn get_ssl_digest(&self) -> Option<Arc<SslDigest>> {
- self.ssl_digest()
- }
-}
-
-// TODO: implement Peek if needed
-impl<T> Peek for SslStream<T> {}
+#[cfg(not(feature = "any_tls"))]
+pub use noop_tls::*;
/// The protocol for Application-Layer Protocol Negotiation
#[derive(Hash, Clone, Debug)]
@@ -236,6 +78,7 @@ impl ALPN {
}
}
+ #[cfg(feature = "openssl_derived")]
pub(crate) fn to_wire_preference(&self) -> &[u8] {
// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_select_cb.html
// "vector of nonempty, 8-bit length-prefixed, byte strings"
@@ -246,6 +89,7 @@ impl ALPN {
}
}
+ #[cfg(feature = "any_tls")]
pub(crate) fn from_wire_selected(raw: &[u8]) -> Option<Self> {
match raw {
b"http/1.1" => Some(Self::H1),
@@ -253,4 +97,13 @@ impl ALPN {
_ => None,
}
}
+
+ #[cfg(feature = "rustls")]
+ pub(crate) fn to_wire_protocols(&self) -> Vec<Vec<u8>> {
+ match self {
+ ALPN::H1 => vec![b"http/1.1".to_vec()],
+ ALPN::H2 => vec![b"h2".to_vec()],
+ ALPN::H2H1 => vec![b"h2".to_vec(), b"http/1.1".to_vec()],
+ }
+ }
}
diff --git a/pingora-core/src/protocols/tls/noop_tls/mod.rs b/pingora-core/src/protocols/tls/noop_tls/mod.rs
new file mode 100644
index 0000000..5e8a701
--- /dev/null
+++ b/pingora-core/src/protocols/tls/noop_tls/mod.rs
@@ -0,0 +1,216 @@
+// Copyright 2024 Cloudflare, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This is a set of stubs that provides the minimum types to let pingora work
+//! without any tls providers configured
+
+pub struct TlsRef;
+
+pub type CaType = [CertWrapper];
+
+#[derive(Debug)]
+pub struct CertWrapper;
+
+impl CertWrapper {
+ pub fn not_after(&self) -> &str {
+ ""
+ }
+}
+
+pub mod connectors {
+ use pingora_error::Result;
+
+ use crate::{
+ connectors::ConnectorOptions,
+ protocols::{ALPN, IO},
+ upstreams::peer::Peer,
+ };
+
+ use super::stream::SslStream;
+
+ #[derive(Clone)]
+ pub struct Connector {
+ pub ctx: TlsConnector,
+ }
+
+ #[derive(Clone)]
+ pub struct TlsConnector;
+
+ pub struct TlsSettings;
+
+ impl Connector {
+ pub fn new(_: Option<ConnectorOptions>) -> Self {
+ Self { ctx: TlsConnector }
+ }
+ }
+
+ pub async fn connect<T, P>(
+ _: T,
+ _: &P,
+ _: Option<ALPN>,
+ _: &TlsConnector,
+ ) -> Result<SslStream<T>>
+ where
+ T: IO,
+ P: Peer + Send + Sync,
+ {
+ Ok(SslStream::default())
+ }
+}
+
+pub mod listeners {
+ use pingora_error::Result;
+ use tokio::io::{AsyncRead, AsyncWrite};
+
+ use super::stream::SslStream;
+
+ pub struct Acceptor;
+
+ pub struct TlsSettings;
+
+ impl TlsSettings {
+ pub fn build(&self) -> Acceptor {
+ Acceptor
+ }
+
+ pub fn intermediate(_: &str, _: &str) -> Result<Self> {
+ Ok(Self)
+ }
+
+ pub fn enable_h2(&mut self) {}
+ }
+
+ impl Acceptor {
+ pub async fn tls_handshake<S: AsyncRead + AsyncWrite>(&self, _: S) -> Result<SslStream<S>> {
+ unimplemented!("No tls feature was specified")
+ }
+ }
+}
+
+pub mod stream {
+ use std::{
+ pin::Pin,
+ task::{Context, Poll},
+ };
+
+ use async_trait::async_trait;
+ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+
+ use crate::protocols::{
+ GetProxyDigest, GetSocketDigest, GetTimingDigest, Shutdown, Ssl, UniqueID,
+ };
+
+ /// A TLS session over a stream.
+ #[derive(Debug)]
+ pub struct SslStream<S> {
+ marker: std::marker::PhantomData<S>,
+ }
+
+ impl<S> Default for SslStream<S> {
+ fn default() -> Self {
+ Self {
+ marker: Default::default(),
+ }
+ }
+ }
+
+ impl<S> AsyncRead for SslStream<S>
+ where
+ S: AsyncRead + AsyncWrite,
+ {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ _ctx: &mut Context<'_>,
+ _buf: &mut ReadBuf<'_>,
+ ) -> Poll<std::io::Result<()>> {
+ Poll::Ready(Ok(()))
+ }
+ }
+
+ impl<S> AsyncWrite for SslStream<S>
+ where
+ S: AsyncRead + AsyncWrite,
+ {
+ fn poll_write(
+ self: Pin<&mut Self>,
+ _ctx: &mut Context<'_>,
+ buf: &[u8],
+ ) -> Poll<std::io::Result<usize>> {
+ Poll::Ready(Ok(buf.len()))
+ }
+
+ fn poll_flush(self: Pin<&mut Self>, _ctx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn poll_shutdown(
+ self: Pin<&mut Self>,
+ _ctx: &mut Context<'_>,
+ ) -> Poll<std::io::Result<()>> {
+ Poll::Ready(Ok(()))
+ }
+ }
+
+ #[async_trait]
+ impl<S: Send> Shutdown for SslStream<S> {
+ async fn shutdown(&mut self) {}
+ }
+
+ impl<S> UniqueID for SslStream<S> {
+ fn id(&self) -> crate::protocols::UniqueIDType {
+ 0
+ }
+ }
+
+ impl<S> Ssl for SslStream<S> {}
+
+ impl<S> GetTimingDigest for SslStream<S> {
+ fn get_timing_digest(&self) -> Vec<Option<crate::protocols::TimingDigest>> {
+ vec![]
+ }
+ }
+
+ impl<S> GetProxyDigest for SslStream<S> {
+ fn get_proxy_digest(
+ &self,
+ ) -> Option<std::sync::Arc<crate::protocols::raw_connect::ProxyDigest>> {
+ None
+ }
+ }
+
+ impl<S> GetSocketDigest for SslStream<S> {
+ fn get_socket_digest(&self) -> Option<std::sync::Arc<crate::protocols::SocketDigest>> {
+ None
+ }
+ }
+}
+
+pub mod utils {
+ use std::fmt::Display;
+
+ use super::CertWrapper;
+
+ #[derive(Debug, Clone, Hash)]
+ pub struct CertKey;
+
+ impl Display for CertKey {
+ fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Ok(())
+ }
+ }
+
+ pub fn get_organization_unit(_: &CertWrapper) -> Option<String> {
+ None
+ }
+}
diff --git a/pingora-core/src/services/listening.rs b/pingora-core/src/services/listening.rs
index b1b2800..3cdb3b8 100644
--- a/pingora-core/src/services/listening.rs
+++ b/pingora-core/src/services/listening.rs
@@ -19,7 +19,8 @@
//! more endpoints to listen to.
use crate::apps::ServerApp;
-use crate::listeners::{Listeners, ServerAddress, TcpSocketOptions, TlsSettings, TransportStack};
+use crate::listeners::tls::TlsSettings;
+use crate::listeners::{Listeners, ServerAddress, TcpSocketOptions, TransportStack};
use crate::protocols::Stream;
#[cfg(unix)]
use crate::server::ListenFds;
diff --git a/pingora-core/src/upstreams/peer.rs b/pingora-core/src/upstreams/peer.rs
index 4030667..87bfab6 100644
--- a/pingora-core/src/upstreams/peer.rs
+++ b/pingora-core/src/upstreams/peer.rs
@@ -33,11 +33,11 @@ use std::time::Duration;
use crate::connectors::{l4::BindTo, L4Connect};
use crate::protocols::l4::socket::SocketAddr;
+use crate::protocols::tls::CaType;
#[cfg(unix)]
use crate::protocols::ConnFdReusable;
use crate::protocols::TcpKeepalive;
-use crate::tls::x509::X509;
-use crate::utils::{get_organization_unit, CertKey};
+use crate::utils::tls::{get_organization_unit, CertKey};
pub use crate::protocols::tls::ALPN;
@@ -148,7 +148,7 @@ pub trait Peer: Display + Clone {
/// Get the CA cert to use to validate the server cert.
///
/// If not set, the default CAs will be used.
- fn get_ca(&self) -> Option<&Arc<Box<[X509]>>> {
+ fn get_ca(&self) -> Option<&Arc<CaType>> {
match self.get_peer_options() {
Some(opt) => opt.ca.as_ref(),
None => None,
@@ -316,7 +316,7 @@ pub struct PeerOptions {
/* accept the cert if it's CN matches the SNI or this name */
pub alternative_cn: Option<String>,
pub alpn: ALPN,
- pub ca: Option<Arc<Box<[X509]>>>,
+ pub ca: Option<Arc<CaType>>,
pub tcp_keepalive: Option<TcpKeepalive>,
pub tcp_recv_buf: Option<usize>,
pub dscp: Option<u8>,
diff --git a/pingora-core/src/utils/mod.rs b/pingora-core/src/utils/mod.rs
index c36f7c8..9826cf9 100644
--- a/pingora-core/src/utils/mod.rs
+++ b/pingora-core/src/utils/mod.rs
@@ -15,12 +15,13 @@
//! This module contains various types that make it easier to work with bytes and X509
//! certificates.
-// TODO: move below to its own mod
-use crate::tls::{nid::Nid, pkey::PKey, pkey::Private, x509::X509};
-use crate::Result;
+#[cfg(feature = "any_tls")]
+pub mod tls;
+
+#[cfg(not(feature = "any_tls"))]
+pub use crate::tls::utils as tls;
+
use bytes::Bytes;
-use pingora_error::{ErrorType::*, OrErr};
-use std::hash::{Hash, Hasher};
/// A `BufRef` is a reference to a buffer of bytes. It removes the need for self-referential data
/// structures. It is safe to use as long as the underlying buffer does not get mutated.
@@ -108,125 +109,3 @@ pub const EMPTY_KV_REF: KVRef = KVRef {
name: BufRef(0, 0),
value: BufRef(0, 0),
};
-
-fn get_subject_name(cert: &X509, name_type: Nid) -> Option<String> {
- cert.subject_name()
- .entries_by_nid(name_type)
- .next()
- .map(|name| {
- name.data()
- .as_utf8()
- .map(|s| s.to_string())
- .unwrap_or_default()
- })
-}
-
-/// Return the organization associated with the X509 certificate.
-pub fn get_organization(cert: &X509) -> Option<String> {
- get_subject_name(cert, Nid::ORGANIZATIONNAME)
-}
-
-/// Return the common name associated with the X509 certificate.
-pub fn get_common_name(cert: &X509) -> Option<String> {
- get_subject_name(cert, Nid::COMMONNAME)
-}
-
-/// Return the common name associated with the X509 certificate.
-pub fn get_organization_unit(cert: &X509) -> Option<String> {
- get_subject_name(cert, Nid::ORGANIZATIONALUNITNAME)
-}
-
-/// Return the serial number associated with the X509 certificate as a hexadecimal value.
-pub fn get_serial(cert: &X509) -> Result<String> {
- let bn = cert
- .serial_number()
- .to_bn()
- .or_err(InvalidCert, "Invalid serial")?;
- let hex = bn.to_hex_str().or_err(InvalidCert, "Invalid serial")?;
-
- let hex_str: &str = hex.as_ref();
- Ok(hex_str.to_owned())
-}
-
-/// This type contains a list of one or more certificates and an associated private key. The leaf
-/// certificate should always be first.
-#[derive(Clone)]
-pub struct CertKey {
- certificates: Vec<X509>,
- key: PKey<Private>,
-}
-
-impl CertKey {
- /// Create a new `CertKey` given a list of certificates and a private key.
- pub fn new(certificates: Vec<X509>, key: PKey<Private>) -> CertKey {
- assert!(
- !certificates.is_empty(),
- "expected a non-empty vector of certificates in CertKey::new"
- );
-
- CertKey { certificates, key }
- }
-
- /// Peek at the leaf certificate.
- pub fn leaf(&self) -> &X509 {
- // This is safe due to the assertion above.
- &self.certificates[0]
- }
-
- /// Return the key.
- pub fn key(&self) -> &PKey<Private> {
- &self.key
- }
-
- /// Return a slice of intermediate certificates. An empty slice means there are none.
- pub fn intermediates(&self) -> &[X509] {
- if self.certificates.len() <= 1 {
- return &[];
- }
- &self.certificates[1..]
- }
-
- /// Return the organization from the leaf certificate.
- pub fn organization(&self) -> Option<String> {
- get_organization(self.leaf())
- }
-
- /// Return the serial from the leaf certificate.
- pub fn serial(&self) -> Result<String> {
- get_serial(self.leaf())
- }
-}
-
-impl Hash for CertKey {
- fn hash<H: Hasher>(&self, state: &mut H) {
- for certificate in &self.certificates {
- if let Ok(serial) = get_serial(certificate) {
- serial.hash(state)
- }
- }
- }
-}
-
-// hide private key
-impl std::fmt::Debug for CertKey {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("CertKey")
- .field("X509", &self.leaf())
- .finish()
- }
-}
-
-impl std::fmt::Display for CertKey {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let leaf = self.leaf();
- if let Some(cn) = get_common_name(leaf) {
- // Write CN if it exists
- write!(f, "CN: {cn},")?;
- } else if let Some(org_unit) = get_organization_unit(leaf) {
- // CA cert might not have CN, so print its unit name instead
- write!(f, "Org Unit: {org_unit},")?;
- }
- write!(f, ", expire: {}", leaf.not_after())
- // ignore the details of the private key
- }
-}
diff --git a/pingora-core/src/utils/tls/boringssl_openssl.rs b/pingora-core/src/utils/tls/boringssl_openssl.rs
new file mode 100644
index 0000000..8842c3c
--- /dev/null
+++ b/pingora-core/src/utils/tls/boringssl_openssl.rs
@@ -0,0 +1,140 @@
+// Copyright 2024 Cloudflare, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::tls::{nid::Nid, pkey::PKey, pkey::Private, x509::X509};
+use crate::Result;
+use pingora_error::{ErrorType::*, OrErr};
+use std::hash::{Hash, Hasher};
+
+fn get_subject_name(cert: &X509, name_type: Nid) -> Option<String> {
+ cert.subject_name()
+ .entries_by_nid(name_type)
+ .next()
+ .map(|name| {
+ name.data()
+ .as_utf8()
+ .map(|s| s.to_string())
+ .unwrap_or_default()
+ })
+}
+
+/// Return the organization associated with the X509 certificate.
+pub fn get_organization(cert: &X509) -> Option<String> {
+ get_subject_name(cert, Nid::ORGANIZATIONNAME)
+}
+
+/// Return the common name associated with the X509 certificate.
+pub fn get_common_name(cert: &X509) -> Option<String> {
+ get_subject_name(cert, Nid::COMMONNAME)
+}
+
+/// Return the common name associated with the X509 certificate.
+pub fn get_organization_unit(cert: &X509) -> Option<String> {
+ get_subject_name(cert, Nid::ORGANIZATIONALUNITNAME)
+}
+
+/// Return the serial number associated with the X509 certificate as a hexadecimal value.
+pub fn get_serial(cert: &X509) -> Result<String> {
+ let bn = cert
+ .serial_number()
+ .to_bn()
+ .or_err(InvalidCert, "Invalid serial")?;
+ let hex = bn.to_hex_str().or_err(InvalidCert, "Invalid serial")?;
+
+ let hex_str: &str = hex.as_ref();
+ Ok(hex_str.to_owned())
+}
+
+/// This type contains a list of one or more certificates and an associated private key. The leaf
+/// certificate should always be first.
+#[derive(Clone)]
+pub struct CertKey {
+ certificates: Vec<X509>,
+ key: PKey<Private>,
+}
+
+impl CertKey {
+ /// Create a new `CertKey` given a list of certificates and a private key.
+ pub fn new(certificates: Vec<X509>, key: PKey<Private>) -> CertKey {
+ assert!(
+ !certificates.is_empty(),
+ "expected a non-empty vector of certificates in CertKey::new"
+ );
+
+ CertKey { certificates, key }
+ }
+
+ /// Peek at the leaf certificate.
+ pub fn leaf(&self) -> &X509 {
+ // This is safe due to the assertion above.
+ &self.certificates[0]
+ }
+
+ /// Return the key.
+ pub fn key(&self) -> &PKey<Private> {
+ &self.key
+ }
+
+ /// Return a slice of intermediate certificates. An empty slice means there are none.
+ pub fn intermediates(&self) -> &[X509] {
+ if self.certificates.len() <= 1 {
+ return &[];
+ }
+ &self.certificates[1..]
+ }
+
+ /// Return the organization from the leaf certificate.
+ pub fn organization(&self) -> Option<String> {
+ get_organization(self.leaf())
+ }
+
+ /// Return the serial from the leaf certificate.
+ pub fn serial(&self) -> Result<String> {
+ get_serial(self.leaf())
+ }
+}
+
+impl Hash for CertKey {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ for certificate in &self.certificates {
+ if let Ok(serial) = get_serial(certificate) {
+ serial.hash(state)
+ }
+ }
+ }
+}
+
+// hide private key
+impl std::fmt::Debug for CertKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CertKey")
+ .field("X509", &self.leaf())
+ .finish()
+ }
+}
+
+impl std::fmt::Display for CertKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let leaf = self.leaf();
+ if let Some(cn) = get_common_name(leaf) {
+ // Write CN if it exists
+ write!(f, "CN: {cn},")?;
+ } else if let Some(org_unit) = get_organization_unit(leaf) {
+ // CA cert might not have CN, so print its unit name instead
+ write!(f, "Org Unit: {org_unit},")?;
+ }
+ write!(f, ", expire: {}", leaf.not_after())
+ // ignore the details of the private key
+ }
+}
diff --git a/pingora-core/src/utils/tls/mod.rs b/pingora-core/src/utils/tls/mod.rs
new file mode 100644
index 0000000..0bcaeac
--- /dev/null
+++ b/pingora-core/src/utils/tls/mod.rs
@@ -0,0 +1,19 @@
+// Copyright 2024 Cloudflare, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[cfg(feature = "openssl_derived")]
+mod boringssl_openssl;
+
+#[cfg(feature = "openssl_derived")]
+pub use boringssl_openssl::*;
diff --git a/pingora-core/tests/test_basic.rs b/pingora-core/tests/test_basic.rs
index ace35cb..32954de 100644
--- a/pingora-core/tests/test_basic.rs
+++ b/pingora-core/tests/test_basic.rs
@@ -14,23 +14,20 @@
mod utils;
-use hyper::Client;
-#[cfg(unix)]
+#[cfg(all(unix, feature = "any_tls"))]
use hyperlocal::{UnixClientExt, Uri};
-use utils::init;
-#[cfg(feature = "some_tls")]
#[tokio::test]
async fn test_http() {
- init();
+ utils::init();
let res = reqwest::get("http://127.0.0.1:6145").await.unwrap();
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_https_http2() {
- init();
+ utils::init();
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
@@ -53,12 +50,12 @@ async fn test_https_http2() {
}
#[cfg(unix)]
-#[cfg(feature = "some_tls")]
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_uds() {
- init();
+ utils::init();
let url = Uri::new("/tmp/echo.sock", "/").into();
- let client = Client::unix();
+ let client = hyper::Client::unix();
let res = client.get(url).await.unwrap();
assert_eq!(res.status(), reqwest::StatusCode::OK);
diff --git a/pingora-core/tests/utils/mod.rs b/pingora-core/tests/utils/mod.rs
index 46f050f..a832c4a 100644
--- a/pingora-core/tests/utils/mod.rs
+++ b/pingora-core/tests/utils/mod.rs
@@ -82,7 +82,7 @@ fn entry_point(opt: Option<Opt>) {
listeners.add_uds("/tmp/echo.sock", None);
let mut tls_settings =
- pingora_core::listeners::TlsSettings::intermediate(&cert_path, &key_path).unwrap();
+ pingora_core::listeners::tls::TlsSettings::intermediate(&cert_path, &key_path).unwrap();
tls_settings.enable_h2();
listeners.add_tls_with_settings("0.0.0.0:6146", None, tls_settings);
diff --git a/pingora-error/src/lib.rs b/pingora-error/src/lib.rs
index d856763..935b252 100644
--- a/pingora-error/src/lib.rs
+++ b/pingora-error/src/lib.rs
@@ -105,6 +105,7 @@ pub enum ErrorType {
ConnectTimedout,
ConnectRefused,
ConnectNoRoute,
+ TLSWantX509Lookup,
TLSHandshakeFailure,
TLSHandshakeTimedout,
InvalidCert,
@@ -164,6 +165,7 @@ impl ErrorType {
ErrorType::ConnectRefused => "ConnectRefused",
ErrorType::ConnectNoRoute => "ConnectNoRoute",
ErrorType::ConnectProxyFailure => "ConnectProxyFailure",
+ ErrorType::TLSWantX509Lookup => "TLSWantX509Lookup",
ErrorType::TLSHandshakeFailure => "TLSHandshakeFailure",
ErrorType::TLSHandshakeTimedout => "TLSHandshakeTimedout",
ErrorType::InvalidCert => "InvalidCert",
diff --git a/pingora-load-balancing/Cargo.toml b/pingora-load-balancing/Cargo.toml
index 523e2f4..e87c598 100644
--- a/pingora-load-balancing/Cargo.toml
+++ b/pingora-load-balancing/Cargo.toml
@@ -35,6 +35,9 @@ derivative = "2.2.0"
[dev-dependencies]
[features]
-default = ["openssl"]
-openssl = ["pingora-core/openssl"]
-boringssl = ["pingora-core/boringssl"]
+default = []
+openssl = ["pingora-core/openssl", "openssl_derived"]
+boringssl = ["pingora-core/boringssl", "openssl_derived"]
+rustls = ["pingora-core/rustls", "any_tls"]
+openssl_derived = ["any_tls"]
+any_tls = []
diff --git a/pingora-load-balancing/src/health_check.rs b/pingora-load-balancing/src/health_check.rs
index ac63579..4e658bb 100644
--- a/pingora-load-balancing/src/health_check.rs
+++ b/pingora-load-balancing/src/health_check.rs
@@ -375,6 +375,7 @@ mod test {
assert!(tcp_check.check(&backend).await.is_err());
}
+ #[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_check() {
let tls_check = TcpHealthCheck::new_tls("one.one.one.one");
@@ -387,6 +388,7 @@ mod test {
assert!(tls_check.check(&backend).await.is_ok());
}
+ #[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_https_check() {
let https_check = HttpHealthCheck::new("one.one.one.one", true);
diff --git a/pingora-proxy/Cargo.toml b/pingora-proxy/Cargo.toml
index 13d4804..b65e0d8 100644
--- a/pingora-proxy/Cargo.toml
+++ b/pingora-proxy/Cargo.toml
@@ -44,7 +44,7 @@ env_logger = "0.9"
hyper = "0.14"
tokio-tungstenite = "0.20.1"
pingora-limits = { version = "0.3.0", path = "../pingora-limits" }
-pingora-load-balancing = { version = "0.3.0", path = "../pingora-load-balancing" }
+pingora-load-balancing = { version = "0.3.0", path = "../pingora-load-balancing", default-features=false }
prometheus = "0"
futures-util = "0.3"
serde = { version = "1.0", features = ["derive"] }
@@ -55,9 +55,12 @@ serde_yaml = "0.8"
hyperlocal = "0.8"
[features]
-default = ["openssl"]
-openssl = ["pingora-core/openssl", "pingora-cache/openssl"]
-boringssl = ["pingora-core/boringssl", "pingora-cache/boringssl"]
+default = []
+openssl = ["pingora-core/openssl", "pingora-cache/openssl", "openssl_derived"]
+boringssl = ["pingora-core/boringssl", "pingora-cache/boringssl", "openssl_derived"]
+rustls = ["pingora-core/rustls", "pingora-cache/rustls", "any_tls"]
+openssl_derived = ["any_tls"]
+any_tls = []
sentry = ["pingora-core/sentry"]
# or locally cargo doc --config "build.rustdocflags='--cfg doc_async_trait'"
diff --git a/pingora-proxy/examples/load_balancer.rs b/pingora-proxy/examples/load_balancer.rs
index 614981d..a428b9b 100644
--- a/pingora-proxy/examples/load_balancer.rs
+++ b/pingora-proxy/examples/load_balancer.rs
@@ -86,7 +86,7 @@ fn main() {
let key_path = format!("{}/tests/keys/key.pem", env!("CARGO_MANIFEST_DIR"));
let mut tls_settings =
- pingora_core::listeners::TlsSettings::intermediate(&cert_path, &key_path).unwrap();
+ pingora_core::listeners::tls::TlsSettings::intermediate(&cert_path, &key_path).unwrap();
tls_settings.enable_h2();
lb.add_tls_with_settings("0.0.0.0:6189", None, tls_settings);
diff --git a/pingora-proxy/tests/test_basic.rs b/pingora-proxy/tests/test_basic.rs
index 569e51a..f33a247 100644
--- a/pingora-proxy/tests/test_basic.rs
+++ b/pingora-proxy/tests/test_basic.rs
@@ -67,6 +67,7 @@ async fn test_simple_proxy() {
}
#[tokio::test]
+#[cfg(feature = "any_tls")]
async fn test_h2_to_h1() {
init();
let client = reqwest::Client::builder()
@@ -74,7 +75,12 @@ async fn test_h2_to_h1() {
.build()
.unwrap();
- let res = client.get("https://127.0.0.1:6150").send().await.unwrap();
+ let res = client
+ .get("https://127.0.0.1:6150")
+ .header("sni", "openrusty.org")
+ .send()
+ .await
+ .unwrap();
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.version(), reqwest::Version::HTTP_2);
@@ -104,6 +110,7 @@ async fn test_h2_to_h1() {
}
#[tokio::test]
+#[cfg(feature = "any_tls")]
async fn test_h2_to_h2() {
init();
let client = reqwest::Client::builder()
@@ -113,6 +120,7 @@ async fn test_h2_to_h2() {
let res = client
.get("https://127.0.0.1:6150")
+ .header("sni", "openrusty.org")
.header("x-h2", "true")
.send()
.await
@@ -189,6 +197,7 @@ async fn test_h1_on_h2c_port() {
}
#[tokio::test]
+#[cfg(feature = "openssl_derived")]
async fn test_h2_to_h2_host_override() {
init();
let client = reqwest::Client::builder()
@@ -212,6 +221,7 @@ async fn test_h2_to_h2_host_override() {
}
#[tokio::test]
+#[cfg(feature = "any_tls")]
async fn test_h2_to_h2_upload() {
init();
let client = reqwest::Client::builder()
@@ -223,6 +233,7 @@ async fn test_h2_to_h2_upload() {
let res = client
.get("https://127.0.0.1:6150/echo")
+ .header("sni", "openrusty.org")
.header("x-h2", "true")
.body(payload)
.send()
@@ -235,6 +246,7 @@ async fn test_h2_to_h2_upload() {
}
#[tokio::test]
+#[cfg(feature = "any_tls")]
async fn test_h2_to_h1_upload() {
init();
let client = reqwest::Client::builder()
@@ -246,6 +258,7 @@ async fn test_h2_to_h1_upload() {
let res = client
.get("https://127.0.0.1:6150/echo")
+ .header("sni", "openrusty.org")
.body(payload)
.send()
.await
@@ -313,7 +326,10 @@ async fn test_simple_proxy_uds_peer() {
assert!(is_specified_port(sockaddr.port()));
assert_eq!(headers["x-upstream-client-addr"], "unset"); // unnamed UDS
- assert_eq!(headers["x-upstream-server-addr"], "/tmp/nginx-test.sock");
+ assert_eq!(
+ headers["x-upstream-server-addr"],
+ "/tmp/pingora_nginx_test.sock"
+ );
let body = res.text().await.unwrap();
assert_eq!(body, "Hello World!\n");
@@ -444,6 +460,8 @@ async fn test_dropped_conn() {
test_dropped_conn_post_body_over().await;
}
+// currently not supported with Rustls implementation
+#[cfg(feature = "openssl_derived")]
#[tokio::test]
async fn test_tls_no_verify() {
init();
@@ -457,6 +475,7 @@ async fn test_tls_no_verify() {
assert_eq!(res.status(), StatusCode::OK);
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_verify_sni_not_host() {
init();
@@ -473,6 +492,8 @@ async fn test_tls_verify_sni_not_host() {
assert_eq!(res.status(), StatusCode::OK);
}
+// currently not supported with Rustls implementation
+#[cfg(feature = "openssl_derived")]
#[tokio::test]
async fn test_tls_none_verify_host() {
init();
@@ -489,6 +510,7 @@ async fn test_tls_none_verify_host() {
assert_eq!(res.status(), StatusCode::OK);
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_verify_sni_host() {
init();
@@ -506,6 +528,7 @@ async fn test_tls_verify_sni_host() {
assert_eq!(res.status(), StatusCode::OK);
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_underscore_sub_sni_verify_host() {
init();
@@ -523,6 +546,7 @@ async fn test_tls_underscore_sub_sni_verify_host() {
assert_eq!(res.status(), StatusCode::OK);
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_underscore_non_sub_sni_verify_host() {
init();
@@ -542,6 +566,7 @@ async fn test_tls_underscore_non_sub_sni_verify_host() {
assert_eq!(headers[header::CONNECTION], "close");
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_alt_verify_host() {
init();
@@ -560,6 +585,7 @@ async fn test_tls_alt_verify_host() {
assert_eq!(res.status(), StatusCode::OK);
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_underscore_sub_alt_verify_host() {
init();
@@ -578,6 +604,7 @@ async fn test_tls_underscore_sub_alt_verify_host() {
assert_eq!(res.status(), StatusCode::OK);
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_underscore_non_sub_alt_verify_host() {
init();
@@ -691,6 +718,7 @@ async fn test_connect_close() {
}
#[tokio::test]
+#[cfg(feature = "any_tls")]
async fn test_mtls_no_client_cert() {
init();
let client = reqwest::Client::new();
@@ -709,6 +737,7 @@ async fn test_mtls_no_client_cert() {
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_mtls_no_intermediate_cert() {
init();
@@ -730,6 +759,7 @@ async fn test_mtls_no_intermediate_cert() {
}
#[tokio::test]
+#[cfg(feature = "any_tls")]
async fn test_mtls() {
init();
let client = reqwest::Client::new();
@@ -748,6 +778,7 @@ async fn test_mtls() {
assert_eq!(res.status(), StatusCode::OK);
}
+#[cfg(feature = "any_tls")]
async fn assert_reuse(req: reqwest::RequestBuilder) {
req.try_clone().unwrap().send().await.unwrap();
let res = req.send().await.unwrap();
@@ -755,6 +786,7 @@ async fn assert_reuse(req: reqwest::RequestBuilder) {
assert!(headers.get("x-conn-reuse").is_some());
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_mtls_diff_cert_no_reuse() {
init();
@@ -789,6 +821,7 @@ async fn test_mtls_diff_cert_no_reuse() {
assert!(headers.get("x-conn-reuse").is_none());
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_diff_verify_no_reuse() {
init();
@@ -815,6 +848,7 @@ async fn test_tls_diff_verify_no_reuse() {
assert!(headers.get("x-conn-reuse").is_none());
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_diff_verify_host_no_reuse() {
init();
@@ -843,6 +877,7 @@ async fn test_tls_diff_verify_host_no_reuse() {
assert!(headers.get("x-conn-reuse").is_none());
}
+#[cfg(feature = "any_tls")]
#[tokio::test]
async fn test_tls_diff_alt_cnt_no_reuse() {
init();
diff --git a/pingora-proxy/tests/utils/cert.rs b/pingora-proxy/tests/utils/cert.rs
index 674a3ac..fb6f54c 100644
--- a/pingora-proxy/tests/utils/cert.rs
+++ b/pingora-proxy/tests/utils/cert.rs
@@ -13,35 +13,64 @@
// limitations under the License.
use once_cell::sync::Lazy;
-use pingora_core::tls::pkey::{PKey, Private};
-use pingora_core::tls::x509::X509;
+#[cfg(feature = "rustls")]
+use pingora_core::tls::{load_pem_file_ca, load_pem_file_private_key};
+#[cfg(feature = "openssl_derived")]
+use pingora_core::tls::{
+ pkey::{PKey, Private},
+ x509::X509,
+};
use std::fs;
-pub static ROOT_CERT: Lazy<X509> = Lazy::new(|| load_cert("keys/root.crt"));
-pub static ROOT_KEY: Lazy<PKey<Private>> = Lazy::new(|| load_key("keys/root.key"));
-pub static INTERMEDIATE_CERT: Lazy<X509> = Lazy::new(|| load_cert("keys/intermediate.crt"));
-pub static INTERMEDIATE_KEY: Lazy<PKey<Private>> = Lazy::new(|| load_key("keys/intermediate.key"));
-pub static LEAF_CERT: Lazy<X509> = Lazy::new(|| load_cert("keys/leaf.crt"));
-pub static LEAF2_CERT: Lazy<X509> = Lazy::new(|| load_cert("keys/leaf2.crt"));
-pub static LEAF_KEY: Lazy<PKey<Private>> = Lazy::new(|| load_key("keys/leaf.key"));
-pub static LEAF2_KEY: Lazy<PKey<Private>> = Lazy::new(|| load_key("keys/leaf2.key"));
-pub static SERVER_CERT: Lazy<X509> = Lazy::new(|| load_cert("keys/server.crt"));
-pub static SERVER_KEY: Lazy<PKey<Private>> = Lazy::new(|| load_key("keys/key.pem"));
-pub static CURVE_521_TEST_KEY: Lazy<PKey<Private>> =
+#[cfg(feature = "openssl_derived")]
+mod key_types {
+ use super::*;
+ pub type PrivateKeyType = PKey<Private>;
+ pub type CertType = X509;
+}
+
+#[cfg(feature = "rustls")]
+mod key_types {
+ use super::*;
+ pub type PrivateKeyType = Vec<u8>;
+ pub type CertType = Vec<u8>;
+}
+
+use key_types::*;
+
+pub static INTERMEDIATE_CERT: Lazy<CertType> = Lazy::new(|| load_cert("keys/intermediate.crt"));
+pub static LEAF_CERT: Lazy<CertType> = Lazy::new(|| load_cert("keys/leaf.crt"));
+pub static LEAF2_CERT: Lazy<CertType> = Lazy::new(|| load_cert("keys/leaf2.crt"));
+pub static LEAF_KEY: Lazy<PrivateKeyType> = Lazy::new(|| load_key("keys/leaf.key"));
+pub static LEAF2_KEY: Lazy<PrivateKeyType> = Lazy::new(|| load_key("keys/leaf2.key"));
+pub static CURVE_521_TEST_KEY: Lazy<PrivateKeyType> =
Lazy::new(|| load_key("keys/curve_test.521.key.pem"));
-pub static CURVE_521_TEST_CERT: Lazy<X509> = Lazy::new(|| load_cert("keys/curve_test.521.crt"));
-pub static CURVE_384_TEST_KEY: Lazy<PKey<Private>> =
+pub static CURVE_521_TEST_CERT: Lazy<CertType> = Lazy::new(|| load_cert("keys/curve_test.521.crt"));
+pub static CURVE_384_TEST_KEY: Lazy<PrivateKeyType> =
Lazy::new(|| load_key("keys/curve_test.384.key.pem"));
-pub static CURVE_384_TEST_CERT: Lazy<X509> = Lazy::new(|| load_cert("keys/curve_test.384.crt"));
+pub static CURVE_384_TEST_CERT: Lazy<CertType> = Lazy::new(|| load_cert("keys/curve_test.384.crt"));
+#[cfg(feature = "openssl_derived")]
fn load_cert(path: &str) -> X509 {
let path = format!("{}/{path}", super::conf_dir());
let cert_bytes = fs::read(path).unwrap();
X509::from_pem(&cert_bytes).unwrap()
}
-
+#[cfg(feature = "openssl_derived")]
fn load_key(path: &str) -> PKey<Private> {
let path = format!("{}/{path}", super::conf_dir());
let key_bytes = fs::read(path).unwrap();
PKey::private_key_from_pem(&key_bytes).unwrap()
}
+
+#[cfg(feature = "rustls")]
+fn load_cert(path: &str) -> Vec<u8> {
+ let path = format!("{}/{path}", super::conf_dir());
+ load_pem_file_ca(&path)
+}
+
+#[cfg(feature = "rustls")]
+fn load_key(path: &str) -> Vec<u8> {
+ let path = format!("{}/{path}", super::conf_dir());
+ load_pem_file_private_key(&path)
+}
diff --git a/pingora-proxy/tests/utils/conf/keys/README.md b/pingora-proxy/tests/utils/conf/keys/README.md
index 13965cd..44944ab 100644
--- a/pingora-proxy/tests/utils/conf/keys/README.md
+++ b/pingora-proxy/tests/utils/conf/keys/README.md
@@ -16,3 +16,12 @@ openssl ecparam -genkey -name secp256r1 -noout -out test_key.pem
openssl req -new -key test_key.pem -out test.csr
openssl x509 -req -in test.csr -CA server.crt -CAkey key.pem -CAcreateserial -CAserial test.srl -out test.crt -days 3650 -sha256
```
+
+```
+openssl version
+# OpenSSL 3.1.1
+echo '[v3_req]' > openssl.cnf
+openssl req -config openssl.cnf -new -x509 -key key.pem -out server_rustls.crt -days 3650 -sha256 \
+ -subj '/C=US/ST=CA/L=San Francisco/O=Cloudflare, Inc/CN=openrusty.org' \
+ -addext "subjectAltName=DNS:*.openrusty.org,DNS:openrusty.org,DNS:cat.com,DNS:dog.com"
+``` \ No newline at end of file
diff --git a/pingora-proxy/tests/utils/conf/keys/server_boringssl_openssl.crt b/pingora-proxy/tests/utils/conf/keys/server_boringssl_openssl.crt
new file mode 100644
index 0000000..afb2d1e
--- /dev/null
+++ b/pingora-proxy/tests/utils/conf/keys/server_boringssl_openssl.crt
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB9zCCAZ2gAwIBAgIUMI7aLvTxyRFCHhw57hGt4U6yupcwCgYIKoZIzj0EAwIw
+ZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNp
+c2NvMRgwFgYDVQQKDA9DbG91ZGZsYXJlLCBJbmMxFjAUBgNVBAMMDW9wZW5ydXN0
+eS5vcmcwHhcNMjIwNDExMjExMzEzWhcNMzIwNDA4MjExMzEzWjBkMQswCQYDVQQG
+EwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xGDAWBgNV
+BAoMD0Nsb3VkZmxhcmUsIEluYzEWMBQGA1UEAwwNb3BlbnJ1c3R5Lm9yZzBZMBMG
+ByqGSM49AgEGCCqGSM49AwEHA0IABNn/9RZtR48knaJD6tk9BdccaJfZ0hGEPn6B
+SDXmlmJPhcTBqa4iUwW/ABpGvO3FpJcNWasrX2k+qZLq3g205MKjLTArMCkGA1Ud
+EQQiMCCCDyoub3BlbnJ1c3R5Lm9yZ4INb3BlbnJ1c3R5Lm9yZzAKBggqhkjOPQQD
+AgNIADBFAiAjISZ9aEKmobKGlT76idO740J6jPaX/hOrm41MLeg69AIhAJqKrSyz
+wD/AAF5fR6tXmBqlnpQOmtxfdy13wDr4MT3h
+-----END CERTIFICATE-----
diff --git a/pingora-proxy/tests/utils/conf/keys/server.csr b/pingora-proxy/tests/utils/conf/keys/server_boringssl_openssl.csr
index ca75dce..ca75dce 100644
--- a/pingora-proxy/tests/utils/conf/keys/server.csr
+++ b/pingora-proxy/tests/utils/conf/keys/server_boringssl_openssl.csr
diff --git a/pingora-proxy/tests/utils/conf/keys/server_rustls.crt b/pingora-proxy/tests/utils/conf/keys/server_rustls.crt
new file mode 100644
index 0000000..28cdadf
--- /dev/null
+++ b/pingora-proxy/tests/utils/conf/keys/server_rustls.crt
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICJzCCAc6gAwIBAgIUU+G0acG/uiMu1ZDSjlcoY4gH53QwCgYIKoZIzj0EAwIw
+ZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNp
+c2NvMRgwFgYDVQQKDA9DbG91ZGZsYXJlLCBJbmMxFjAUBgNVBAMMDW9wZW5ydXN0
+eS5vcmcwHhcNMjQwNzI0MTMzOTQ4WhcNMzQwNzIyMTMzOTQ4WjBkMQswCQYDVQQG
+EwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xGDAWBgNV
+BAoMD0Nsb3VkZmxhcmUsIEluYzEWMBQGA1UEAwwNb3BlbnJ1c3R5Lm9yZzBZMBMG
+ByqGSM49AgEGCCqGSM49AwEHA0IABNn/9RZtR48knaJD6tk9BdccaJfZ0hGEPn6B
+SDXmlmJPhcTBqa4iUwW/ABpGvO3FpJcNWasrX2k+qZLq3g205MKjXjBcMDsGA1Ud
+EQQ0MDKCDyoub3BlbnJ1c3R5Lm9yZ4INb3BlbnJ1c3R5Lm9yZ4IHY2F0LmNvbYIH
+ZG9nLmNvbTAdBgNVHQ4EFgQUnfYAFWyQnSN57IGokj7jcz8ChJQwCgYIKoZIzj0E
+AwIDRwAwRAIgQr+Ly2cH04CncbnbhUf4hBl5frTp1pXgGnn8dYjd+UcCICuunEtp
+H/a42/sVGBFvjS6FOFe6ZDs4oWBNEqQSw0S2
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/pingora-proxy/tests/utils/conf/origin/conf/nginx.conf b/pingora-proxy/tests/utils/conf/origin/conf/nginx.conf
index 6d5abd7..2718f88 100644
--- a/pingora-proxy/tests/utils/conf/origin/conf/nginx.conf
+++ b/pingora-proxy/tests/utils/conf/origin/conf/nginx.conf
@@ -6,7 +6,7 @@ error_log /dev/stdout;
#error_log logs/error.log notice;
#error_log logs/error.log info;
-pid /tmp/mock_origin.pid;
+pid /tmp/pingora_mock_origin.pid;
master_process off;
daemon off;
@@ -85,7 +85,7 @@ http {
listen 8001;
listen [::]:8000;
#listen 8443 ssl;
- listen unix:/tmp/nginx-test.sock;
+ listen unix:/tmp/pingora_nginx_test.sock;
listen 8443 ssl http2;
server_name localhost;
@@ -97,6 +97,9 @@ http {
# for benchmark
http2_max_requests 999999;
+ # increase max body size for /upload/ test
+ client_max_body_size 128m;
+
#charset koi8-r;
#access_log logs/host.access.log main;
diff --git a/pingora-proxy/tests/utils/mock_origin.rs b/pingora-proxy/tests/utils/mock_origin.rs
index db84f8d..ec59e51 100644
--- a/pingora-proxy/tests/utils/mock_origin.rs
+++ b/pingora-proxy/tests/utils/mock_origin.rs
@@ -13,15 +13,38 @@
// limitations under the License.
use once_cell::sync::Lazy;
+use std::path::Path;
use std::process;
use std::{thread, time};
pub static MOCK_ORIGIN: Lazy<bool> = Lazy::new(init);
fn init() -> bool {
+ #[cfg(feature = "rustls")]
+ let src_cert_path = format!(
+ "{}/tests/utils/conf/keys/server_rustls.crt",
+ env!("CARGO_MANIFEST_DIR")
+ );
+ #[cfg(feature = "openssl_derived")]
+ let src_cert_path = format!(
+ "{}/tests/utils/conf/keys/server_boringssl_openssl.crt",
+ env!("CARGO_MANIFEST_DIR")
+ );
+
+ #[cfg(feature = "any_tls")]
+ {
+ let mut dst_cert_path = format!("{}/tests/keys/server.crt", env!("CARGO_MANIFEST_DIR"));
+ std::fs::copy(Path::new(&src_cert_path), Path::new(&dst_cert_path));
+ dst_cert_path = format!(
+ "{}/tests/utils/conf/keys/server.crt",
+ env!("CARGO_MANIFEST_DIR")
+ );
+ std::fs::copy(Path::new(&src_cert_path), Path::new(&dst_cert_path));
+ }
+
// TODO: figure out a way to kill openresty when exiting
process::Command::new("pkill")
- .args(["-F", "/tmp/mock_origin.pid"])
+ .args(["-F", "/tmp/pingora_mock_origin.pid"])
.spawn()
.unwrap();
let _origin = thread::spawn(|| {
diff --git a/pingora-proxy/tests/utils/mod.rs b/pingora-proxy/tests/utils/mod.rs
index 6a5a1c9..df769e5 100644
--- a/pingora-proxy/tests/utils/mod.rs
+++ b/pingora-proxy/tests/utils/mod.rs
@@ -14,7 +14,9 @@
#![allow(unused)]
+#[cfg(feature = "any_tls")]
pub mod cert;
+
pub mod mock_origin;
pub mod server_utils;
pub mod websocket;
diff --git a/pingora-proxy/tests/utils/server_utils.rs b/pingora-proxy/tests/utils/server_utils.rs
index 885fcb1..62b5882 100644
--- a/pingora-proxy/tests/utils/server_utils.rs
+++ b/pingora-proxy/tests/utils/server_utils.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#[cfg(feature = "any_tls")]
use super::cert;
use async_trait::async_trait;
use clap::Parser;
@@ -32,7 +33,7 @@ use pingora_core::protocols::{l4::socket::SocketAddr, Digest};
use pingora_core::server::configuration::Opt;
use pingora_core::services::Service;
use pingora_core::upstreams::peer::HttpPeer;
-use pingora_core::utils::CertKey;
+use pingora_core::utils::tls::CertKey;
use pingora_error::{Error, ErrorSource, Result};
use pingora_http::{RequestHeader, ResponseHeader};
use pingora_proxy::{ProxyHttp, Session};
@@ -106,6 +107,7 @@ fn response_filter_common(
}
#[async_trait]
+#[cfg(feature = "any_tls")]
impl ProxyHttp for ExampleProxyHttps {
type CTX = CTX;
fn new_ctx(&self) -> Self::CTX {
@@ -283,7 +285,7 @@ impl ProxyHttp for ExampleProxyHttp {
#[cfg(unix)]
if req.headers.contains_key("x-uds-peer") {
return Ok(Box::new(HttpPeer::new_uds(
- "/tmp/nginx-test.sock",
+ "/tmp/pingora_nginx_test.sock",
false,
"".to_string(),
)?));
@@ -558,27 +560,36 @@ fn test_main() {
http_logic.server_options = Some(http_server_options);
proxy_service_h2c.add_tcp("0.0.0.0:6146");
- let mut proxy_service_https =
- pingora_proxy::http_proxy_service(&my_server.configuration, ExampleProxyHttps {});
- proxy_service_https.add_tcp("0.0.0.0:6149");
- let cert_path = format!("{}/tests/keys/server.crt", env!("CARGO_MANIFEST_DIR"));
- let key_path = format!("{}/tests/keys/key.pem", env!("CARGO_MANIFEST_DIR"));
- let mut tls_settings =
- pingora_core::listeners::TlsSettings::intermediate(&cert_path, &key_path).unwrap();
- tls_settings.enable_h2();
- proxy_service_https.add_tls_with_settings("0.0.0.0:6150", None, tls_settings);
+ let mut proxy_service_https_opt: Option<Box<dyn Service>> = None;
+
+ #[cfg(feature = "any_tls")]
+ {
+ let mut proxy_service_https =
+ pingora_proxy::http_proxy_service(&my_server.configuration, ExampleProxyHttps {});
+ proxy_service_https.add_tcp("0.0.0.0:6149");
+ let cert_path = format!("{}/tests/keys/server.crt", env!("CARGO_MANIFEST_DIR"));
+ let key_path = format!("{}/tests/keys/key.pem", env!("CARGO_MANIFEST_DIR"));
+ let mut tls_settings =
+ pingora_core::listeners::tls::TlsSettings::intermediate(&cert_path, &key_path).unwrap();
+ tls_settings.enable_h2();
+ proxy_service_https.add_tls_with_settings("0.0.0.0:6150", None, tls_settings);
+ proxy_service_https_opt = Some(Box::new(proxy_service_https))
+ }
let mut proxy_service_cache =
pingora_proxy::http_proxy_service(&my_server.configuration, ExampleProxyCache {});
proxy_service_cache.add_tcp("0.0.0.0:6148");
- let services: Vec<Box<dyn Service>> = vec![
+ let mut services: Vec<Box<dyn Service>> = vec![
Box::new(proxy_service_h2c),
Box::new(proxy_service_http),
- Box::new(proxy_service_https),
Box::new(proxy_service_cache),
];
+ if let Some(proxy_service_https) = proxy_service_https_opt {
+ services.push(proxy_service_https)
+ }
+
set_compression_dict_path("tests/headers.dict");
my_server.add_services(services);
my_server.run_forever();
diff --git a/pingora-rustls/Cargo.toml b/pingora-rustls/Cargo.toml
new file mode 100644
index 0000000..858ce30
--- /dev/null
+++ b/pingora-rustls/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "pingora-rustls"
+version = "0.3.0"
+authors = ["Harald Gutmann <[email protected]>"]
+license = "Apache-2.0"
+edition = "2021"
+repository = "https://github.com/cloudflare/pingora"
+categories = ["asynchronous", "network-programming"]
+keywords = ["async", "tls", "ssl", "pingora"]
+description = """
+RusTLS async APIs for Pingora.
+"""
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+name = "pingora_rustls"
+path = "src/lib.rs"
diff --git a/pingora-rustls/LICENSE b/pingora-rustls/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/pingora-rustls/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/pingora-rustls/src/lib.rs b/pingora-rustls/src/lib.rs
new file mode 100644
index 0000000..814bcb9
--- /dev/null
+++ b/pingora-rustls/src/lib.rs
@@ -0,0 +1,17 @@
+// Copyright 2024 Cloudflare, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub fn rustls() {
+ todo!()
+}
diff --git a/pingora/Cargo.toml b/pingora/Cargo.toml
index 236c2d3..8dccca1 100644
--- a/pingora/Cargo.toml
+++ b/pingora/Cargo.toml
@@ -49,21 +49,32 @@ hyperlocal = "0.8"
jemallocator = "0.5"
[features]
-default = ["openssl"]
+default = []
openssl = [
"pingora-core/openssl",
"pingora-proxy?/openssl",
"pingora-cache?/openssl",
"pingora-load-balancing?/openssl",
+ "openssl_derived",
]
boringssl = [
"pingora-core/boringssl",
"pingora-proxy?/boringssl",
"pingora-cache?/boringssl",
"pingora-load-balancing?/boringssl",
+ "openssl_derived",
]
proxy = ["pingora-proxy"]
lb = ["pingora-load-balancing", "proxy"]
cache = ["pingora-cache"]
time = []
sentry = ["pingora-core/sentry"]
+rustls = [
+ "pingora-core/rustls",
+ "pingora-proxy?/rustls",
+ "pingora-cache?/rustls",
+ "pingora-load-balancing?/rustls",
+ "any_tls",
+]
+openssl_derived = ["any_tls"]
+any_tls = []
diff --git a/pingora/examples/server.rs b/pingora/examples/server.rs
index a2a092e..ff25151 100644
--- a/pingora/examples/server.rs
+++ b/pingora/examples/server.rs
@@ -15,6 +15,7 @@
#[global_allocator]
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
+use pingora::listeners::tls::TlsSettings;
use pingora::protocols::TcpKeepalive;
use pingora::server::configuration::Opt;
use pingora::server::{Server, ShutdownWatch};
@@ -49,31 +50,35 @@ impl BackgroundService for ExampleBackgroundService {
}
}
}
+#[cfg(feature = "openssl_derived")]
+mod boringssl_openssl {
+ use super::*;
+ use pingora::tls::pkey::{PKey, Private};
+ use pingora::tls::x509::X509;
+
+ pub(super) struct DynamicCert {
+ cert: X509,
+ key: PKey<Private>,
+ }
-use pingora::tls::pkey::{PKey, Private};
-use pingora::tls::x509::X509;
-struct DynamicCert {
- cert: X509,
- key: PKey<Private>,
-}
-
-impl DynamicCert {
- fn new(cert: &str, key: &str) -> Box<Self> {
- let cert_bytes = std::fs::read(cert).unwrap();
- let cert = X509::from_pem(&cert_bytes).unwrap();
+ impl DynamicCert {
+ pub(super) fn new(cert: &str, key: &str) -> Box<Self> {
+ let cert_bytes = std::fs::read(cert).unwrap();
+ let cert = X509::from_pem(&cert_bytes).unwrap();
- let key_bytes = std::fs::read(key).unwrap();
- let key = PKey::private_key_from_pem(&key_bytes).unwrap();
- Box::new(DynamicCert { cert, key })
+ let key_bytes = std::fs::read(key).unwrap();
+ let key = PKey::private_key_from_pem(&key_bytes).unwrap();
+ Box::new(DynamicCert { cert, key })
+ }
}
-}
-#[async_trait]
-impl pingora::listeners::TlsAccept for DynamicCert {
- async fn certificate_callback(&self, ssl: &mut pingora::tls::ssl::SslRef) {
- use pingora::tls::ext;
- ext::ssl_use_certificate(ssl, &self.cert).unwrap();
- ext::ssl_use_private_key(ssl, &self.key).unwrap();
+ #[async_trait]
+ impl pingora::listeners::TlsAccept for DynamicCert {
+ async fn certificate_callback(&self, ssl: &mut pingora::tls::ssl::SslRef) {
+ use pingora::tls::ext;
+ ext::ssl_use_certificate(ssl, &self.cert).unwrap();
+ ext::ssl_use_private_key(ssl, &self.key).unwrap();
+ }
}
}
@@ -132,12 +137,32 @@ pub fn main() {
echo_service_http.add_tcp_with_settings("0.0.0.0:6145", options);
echo_service_http.add_uds("/tmp/echo.sock", None);
- let dynamic_cert = DynamicCert::new(&cert_path, &key_path);
- let mut tls_settings = pingora::listeners::TlsSettings::with_callbacks(dynamic_cert).unwrap();
- // by default intermediate supports both TLS 1.2 and 1.3. We force to tls 1.2 just for the demo
- tls_settings
- .set_max_proto_version(Some(pingora::tls::ssl::SslVersion::TLS1_2))
- .unwrap();
+ let mut tls_settings;
+
+ // NOTE: dynamic certificate callback is only supported with BoringSSL/OpenSSL
+ #[cfg(feature = "openssl_derived")]
+ {
+ use std::ops::DerefMut;
+
+ let dynamic_cert = boringssl_openssl::DynamicCert::new(&cert_path, &key_path);
+ tls_settings = TlsSettings::with_callbacks(dynamic_cert).unwrap();
+ // by default intermediate supports both TLS 1.2 and 1.3. We force to tls 1.2 just for the demo
+
+ tls_settings
+ .deref_mut()
+ .deref_mut()
+ .set_max_proto_version(Some(pingora::tls::ssl::SslVersion::TLS1_2))
+ .unwrap();
+ }
+ #[cfg(feature = "rustls")]
+ {
+ tls_settings = TlsSettings::intermediate(&cert_path, &key_path).unwrap();
+ }
+ #[cfg(not(feature = "any_tls"))]
+ {
+ tls_settings = TlsSettings;
+ }
+
tls_settings.enable_h2();
echo_service_http.add_tls_with_settings("0.0.0.0:6148", None, tls_settings);