From e188d6a9cae00e8acf1feddb6fb6aa72de099f6d Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 1 Nov 2021 22:11:44 +0000 Subject: [PATCH 01/85] wip: layout --- Cargo.lock | 15 ++ Cargo.toml | 1 + linkerd/identity/fips/Cargo.toml | 18 ++ linkerd/identity/fips/src/client.rs | 165 ++++++++++++++ linkerd/identity/fips/src/creds.rs | 82 +++++++ linkerd/identity/fips/src/creds/receiver.rs | 117 ++++++++++ linkerd/identity/fips/src/creds/store.rs | 237 ++++++++++++++++++++ linkerd/identity/fips/src/lib.rs | 12 + linkerd/identity/fips/src/server.rs | 213 ++++++++++++++++++ linkerd/identity/fips/src/tests.rs | 35 +++ 10 files changed, 895 insertions(+) create mode 100644 linkerd/identity/fips/Cargo.toml create mode 100644 linkerd/identity/fips/src/client.rs create mode 100644 linkerd/identity/fips/src/creds.rs create mode 100644 linkerd/identity/fips/src/creds/receiver.rs create mode 100644 linkerd/identity/fips/src/creds/store.rs create mode 100644 linkerd/identity/fips/src/lib.rs create mode 100644 linkerd/identity/fips/src/server.rs create mode 100644 linkerd/identity/fips/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 587f224cfb..5f7faa628d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1022,6 +1022,21 @@ dependencies = [ "webpki", ] +[[package]] +name = "linkerd-identity-fips" +version = "0.1.0" +dependencies = [ + "futures", + "linkerd-error", + "linkerd-identity", + "linkerd-io", + "linkerd-stack", + "linkerd-tls", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "linkerd-io" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5d6f81c998..16985633bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "linkerd/http-retry", "linkerd/identity", "linkerd/identity/default", + "linkerd/identity/fips", "linkerd/io", "linkerd/metrics", "linkerd/opencensus", diff --git a/linkerd/identity/fips/Cargo.toml b/linkerd/identity/fips/Cargo.toml new file mode 100644 index 0000000000..057c326ab5 --- /dev/null +++ b/linkerd/identity/fips/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "linkerd-identity-fips" +version = "0.1.0" +authors = ["Linkerd Developers "] +license = "Apache-2.0" +edition = "2018" +publish = false + +[dependencies] +futures = { version = "0.3", default-features = false } +linkerd-error = { path = "../../error" } +linkerd-io = { path = "../../io" } +linkerd-identity = { path = ".." } +linkerd-stack = { path = "../../stack" } +linkerd-tls = { path = "../../tls" } +thiserror = "1" +tokio = { version = "1", features = ["macros", "sync"] } +tracing = "0.1" diff --git a/linkerd/identity/fips/src/client.rs b/linkerd/identity/fips/src/client.rs new file mode 100644 index 0000000000..052f445020 --- /dev/null +++ b/linkerd/identity/fips/src/client.rs @@ -0,0 +1,165 @@ +use futures::prelude::*; +use linkerd_io as io; +use linkerd_stack::{NewService, Service}; +use linkerd_tls::{client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef}; +use std::{pin::Pin, sync::Arc}; +use tokio::sync::watch; +use tokio_rustls::rustls::{ClientConfig, Session}; + +/// A `NewService` that produces `Connect` services from a dynamic TLS configuration. +#[derive(Clone)] +pub struct NewClient { + config: watch::Receiver>, +} + +/// A `Service` that initiates client-side TLS connections. +#[derive(Clone)] +pub struct Connect { + server_id: webpki::DNSName, + config: Arc, +} + +pub type ConnectFuture = futures::future::MapOk< + tokio_rustls::Connect, + fn(tokio_rustls::client::TlsStream) -> ClientIo, +>; + +#[derive(Debug)] +pub struct ClientIo(tokio_rustls::client::TlsStream); + +// === impl NewClient === + +impl NewClient { + pub(crate) fn new(config: watch::Receiver>) -> Self { + Self { config } + } +} + +impl NewService for NewClient { + type Service = Connect; + + fn new_service(&self, target: ClientTls) -> Self::Service { + Connect::new(target, (*self.config.borrow()).clone()) + } +} + +impl std::fmt::Debug for NewClient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NewClient").finish() + } +} + +// === impl Connect === + +impl Connect { + pub(crate) fn new(client_tls: ClientTls, config: Arc) -> Self { + // If ALPN protocols are configured by the endpoint, we have to clone the entire + // configuration and set the protocols. If there are no ALPN options, clone the Arc'd base + // configuration without extra allocation. + // + // TODO it would be better to avoid cloning the whole TLS config per-connection, but the + // Rustls API doesn't give us a lot of options. + let config = match client_tls.alpn { + None => config, + Some(AlpnProtocols(protocols)) => { + let mut c = (*config).clone(); + c.alpn_protocols = protocols; + Arc::new(c) + } + }; + + let server_id = webpki::DNSNameRef::try_from_ascii(client_tls.server_id.as_bytes()) + .expect("identity must be a valid DNS name") + .to_owned(); + + Self { server_id, config } + } +} + +impl Service for Connect +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + type Response = ClientIo; + type Error = io::Error; + type Future = ConnectFuture; + + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, io: I) -> Self::Future { + tokio_rustls::TlsConnector::from(self.config.clone()) + .connect(self.server_id.as_ref(), io) + .map_ok(ClientIo) + } +} + +// === impl ClientIo === + +impl io::AsyncRead for ClientIo { + #[inline] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl io::AsyncWrite for ClientIo { + #[inline] + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + Pin::new(&mut self.0).poll_flush(cx) + } + + #[inline] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + Pin::new(&mut self.0).poll_shutdown(cx) + } + + #[inline] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> io::Poll { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + #[inline] + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> std::task::Poll> { + Pin::new(&mut self.0).poll_write_vectored(cx, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } +} + +impl HasNegotiatedProtocol for ClientIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.0 + .get_ref() + .1 + .get_alpn_protocol() + .map(NegotiatedProtocolRef) + } +} + +impl io::PeerAddr for ClientIo { + #[inline] + fn peer_addr(&self) -> io::Result { + self.0.get_ref().0.peer_addr() + } +} diff --git a/linkerd/identity/fips/src/creds.rs b/linkerd/identity/fips/src/creds.rs new file mode 100644 index 0000000000..0d356b4bc6 --- /dev/null +++ b/linkerd/identity/fips/src/creds.rs @@ -0,0 +1,82 @@ +mod receiver; +mod store; + +pub use self::{receiver::Receiver, store::Store}; +use linkerd_error::Result; +use linkerd_identity as id; +use ring::{error::KeyRejected, signature::EcdsaKeyPair}; +use std::sync::Arc; +use thiserror::Error; +use tokio::sync::watch; +use tokio_rustls::rustls; +use tracing::warn; + +#[derive(Debug, Error)] +#[error(transparent)] +pub struct InvalidKey(KeyRejected); + +#[derive(Debug, Error)] +#[error("invalid trust roots")] +pub struct InvalidTrustRoots(()); + +pub fn watch( + identity: id::Name, + roots_pem: &str, + key_pkcs8: &[u8], + csr: &[u8], +) -> Result<(Store, Receiver)> { + let mut roots = rustls::RootCertStore::empty(); + let (added, skipped) = roots + .add_pem_file(&mut std::io::Cursor::new(roots_pem)) + .map_err(InvalidTrustRoots)?; + if skipped != 0 { + warn!("Skipped {} invalid trust anchors", skipped); + } + if added == 0 { + return Err("no trust roots loaded".into()); + } + + let key = EcdsaKeyPair::from_pkcs8(params::SIGNATURE_ALG_RING_SIGNING, key_pkcs8) + .map_err(InvalidKey)?; + + let (client_tx, client_rx) = watch::channel(Arc::new(rustls::ClientConfig::new())); + let (server_tx, server_rx) = watch::channel(Arc::new(rustls::ServerConfig::new( + rustls::AllowAnyAnonymousOrAuthenticatedClient::new(roots.clone()), + ))); + + let rx = Receiver::new(identity.clone(), client_rx, server_rx); + let store = Store::new(roots, key, csr, identity, client_tx, server_tx); + + Ok((store, rx)) +} + +#[cfg(feature = "test-util")] +pub fn for_test(ent: &linkerd_tls_test_util::Entity) -> (Store, Receiver) { + watch( + ent.name.parse().expect("name must be valid"), + std::str::from_utf8(ent.trust_anchors).expect("roots must be PEM"), + ent.key, + b"fake CSR", + ) + .expect("credentials must be valid") +} + +#[cfg(feature = "test-util")] +pub fn default_for_test() -> (Store, Receiver) { + for_test(&linkerd_tls_test_util::FOO_NS1) +} + +mod params { + use tokio_rustls::rustls; + + // These must be kept in sync: + pub static SIGNATURE_ALG_RING_SIGNING: &ring::signature::EcdsaSigningAlgorithm = + &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING; + pub const SIGNATURE_ALG_RUSTLS_SCHEME: rustls::SignatureScheme = + rustls::SignatureScheme::ECDSA_NISTP256_SHA256; + pub const SIGNATURE_ALG_RUSTLS_ALGORITHM: rustls::internal::msgs::enums::SignatureAlgorithm = + rustls::internal::msgs::enums::SignatureAlgorithm::ECDSA; + pub const TLS_VERSIONS: &[rustls::ProtocolVersion] = &[rustls::ProtocolVersion::TLSv1_3]; + pub static TLS_SUPPORTED_CIPHERSUITES: [&rustls::SupportedCipherSuite; 1] = + [&rustls::ciphersuite::TLS13_CHACHA20_POLY1305_SHA256]; +} diff --git a/linkerd/identity/fips/src/creds/receiver.rs b/linkerd/identity/fips/src/creds/receiver.rs new file mode 100644 index 0000000000..5cdb9f2e56 --- /dev/null +++ b/linkerd/identity/fips/src/creds/receiver.rs @@ -0,0 +1,117 @@ +use crate::{NewClient, Server}; +use linkerd_identity::Name; +use std::sync::Arc; +use tokio::sync::watch; +use tokio_rustls::rustls; + +/// Receives TLS config updates to build `NewClient` and `Server` types. +#[derive(Clone)] +pub struct Receiver { + name: Name, + client_rx: watch::Receiver>, + server_rx: watch::Receiver>, +} + +// === impl Receiver === + +impl Receiver { + pub(super) fn new( + name: Name, + client_rx: watch::Receiver>, + server_rx: watch::Receiver>, + ) -> Self { + Self { + name, + client_rx, + server_rx, + } + } + + /// Returns the local identity. + pub fn name(&self) -> &Name { + &self.name + } + + /// Returns a `NewClient` that can be used to establish TLS on client connections. + pub fn new_client(&self) -> NewClient { + NewClient::new(self.client_rx.clone()) + } + + /// Returns a `Server` that can be used to terminate TLS on server connections. + pub fn server(&self) -> Server { + Server::new(self.name.clone(), self.server_rx.clone()) + } +} + +impl std::fmt::Debug for Receiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Receiver") + .field("name", &self.name) + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_server() { + let init_config = Arc::new(rustls::ServerConfig::new(rustls::NoClientAuth::new())); + let (server_tx, server_rx) = watch::channel(init_config.clone()); + let (_, client_rx) = watch::channel(Arc::new(rustls::ClientConfig::new())); + let receiver = Receiver { + name: "example".parse().unwrap(), + server_rx, + client_rx, + }; + + let server = receiver.server(); + + assert!(Arc::ptr_eq(&server.config(), &init_config)); + + let server_config = Arc::new(rustls::ServerConfig::new(rustls::NoClientAuth::new())); + server_tx + .send(server_config.clone()) + .ok() + .expect("receiver is held"); + + assert!(Arc::ptr_eq(&server.config(), &server_config)); + } + + #[tokio::test] + async fn test_spawn_server_with_alpn() { + let init_config = Arc::new(rustls::ServerConfig::new(rustls::NoClientAuth::new())); + let (server_tx, server_rx) = watch::channel(init_config.clone()); + let (_, client_rx) = watch::channel(Arc::new(rustls::ClientConfig::new())); + let receiver = Receiver { + name: "example".parse().unwrap(), + server_rx, + client_rx, + }; + + let server = receiver + .server() + .spawn_with_alpn(vec![b"my.alpn".to_vec()]) + .expect("sender must not be lost"); + + let init_sc = server.config(); + assert!(!Arc::ptr_eq(&init_config, &init_sc)); + assert_eq!(init_sc.alpn_protocols, [b"my.alpn"]); + + let update_config = Arc::new(rustls::ServerConfig::new(rustls::NoClientAuth::new())); + assert!(!Arc::ptr_eq(&update_config, &init_config)); + server_tx + .send(update_config.clone()) + .ok() + .expect("receiver is held"); + + // Give the update task a chance to run. + tokio::task::yield_now().await; + + let update_sc = server.config(); + assert!(!Arc::ptr_eq(&update_config, &update_sc)); + assert!(!Arc::ptr_eq(&init_sc, &update_sc)); + assert_eq!(update_sc.alpn_protocols, [b"my.alpn"]); + } +} diff --git a/linkerd/identity/fips/src/creds/store.rs b/linkerd/identity/fips/src/creds/store.rs new file mode 100644 index 0000000000..284b53fa47 --- /dev/null +++ b/linkerd/identity/fips/src/creds/store.rs @@ -0,0 +1,237 @@ +use super::params::*; +use linkerd_error::Result; +use linkerd_identity as id; +use ring::{rand, signature::EcdsaKeyPair}; +use std::sync::Arc; +use tokio::sync::watch; +use tokio_rustls::rustls; +use tracing::debug; + +pub struct Store { + roots: rustls::RootCertStore, + key: Arc, + csr: Arc<[u8]>, + name: id::Name, + client_tx: watch::Sender>, + server_tx: watch::Sender>, +} + +#[derive(Clone)] +struct Key(Arc); + +struct CertResolver(rustls::sign::CertifiedKey); + +// === impl Store === + +impl Store { + pub(super) fn new( + roots: rustls::RootCertStore, + key: EcdsaKeyPair, + csr: &[u8], + name: id::Name, + client_tx: watch::Sender>, + server_tx: watch::Sender>, + ) -> Self { + Self { + roots, + key: Arc::new(key), + csr: csr.into(), + name, + client_tx, + server_tx, + } + } + + /// Builds a new TLS client configuration. + fn client(&self, resolver: Arc) -> rustls::ClientConfig { + let mut client = rustls::ClientConfig::new(); + client.ciphersuites = TLS_SUPPORTED_CIPHERSUITES.to_vec(); + + // XXX: Rustls's built-in verifiers don't let us tweak things as fully + // as we'd like (e.g. controlling the set of trusted signature + // algorithms), but they provide good enough defaults for now. + // TODO: lock down the verification further. + // TODO: Change Rustls's API to avoid needing to clone `root_cert_store`. + client.root_store = self.roots.clone(); + + // Disable session resumption for the time-being until resumption is + // more tested. + client.enable_tickets = false; + + // Enable client authentication. + client.client_auth_cert_resolver = resolver; + + client + } + + /// Builds a new TLS server configuration. + fn server(&self, resolver: Arc) -> rustls::ServerConfig { + // Ask TLS clients for a certificate and accept any certificate issued by our trusted CA(s). + // + // XXX: Rustls's built-in verifiers don't let us tweak things as fully as we'd like (e.g. + // controlling the set of trusted signature algorithms), but they provide good enough + // defaults for now. + // TODO: lock down the verification further. + // + // TODO: Change Rustls's API to avoid needing to clone `root_cert_store`. + let mut server = rustls::ServerConfig::new( + rustls::AllowAnyAnonymousOrAuthenticatedClient::new(self.roots.clone()), + ); + server.versions = TLS_VERSIONS.to_vec(); + server.cert_resolver = resolver; + server.ciphersuites = TLS_SUPPORTED_CIPHERSUITES.to_vec(); + server + } + + /// Ensures the certificate is valid for the services we terminate for TLS. This assumes that + /// server cert validation does the same or more validation than client cert validation. + fn validate(&self, client: &rustls::ClientConfig, certs: &[rustls::Certificate]) -> Result<()> { + // XXX: Rustls currently only provides access to a `ServerCertVerifier` through + // `ClientConfig::get_verifier()`. + // + // XXX: Once `ServerCertVerified` is exposed in Rustls's safe API, use it to pass proof to + // CertCertResolver::new.... + // + // TODO: Restrict accepted signature algorithms. + let crt_id = webpki::DNSNameRef::try_from_ascii(self.name.as_bytes()) + .expect("identity must be a valid DNS name"); + static NO_OCSP: &[u8] = &[]; + client + .get_verifier() + .verify_server_cert(&self.roots, &*certs, crt_id, NO_OCSP)?; + debug!("Certified"); + Ok(()) + } +} + +impl id::Credentials for Store { + /// Returns the proxy's identity. + fn dns_name(&self) -> &id::Name { + &self.name + } + + /// Returns the CSR that was configured at proxy startup. + fn gen_certificate_signing_request(&mut self) -> id::DerX509 { + id::DerX509(self.csr.to_vec()) + } + + /// Publishes TLS client and server configurations using + fn set_certificate( + &mut self, + id::DerX509(leaf): id::DerX509, + intermediates: Vec, + _expiry: std::time::SystemTime, + ) -> Result<()> { + let mut chain = Vec::with_capacity(intermediates.len() + 1); + chain.push(rustls::Certificate(leaf)); + chain.extend( + intermediates + .into_iter() + .map(|id::DerX509(der)| rustls::Certificate(der)), + ); + + let resolver = Arc::new(CertResolver(rustls::sign::CertifiedKey::new( + chain.clone(), + Arc::new(Box::new(Key(self.key.clone()))), + ))); + + // Build new client and server TLS configs. + let client = self.client(resolver.clone()); + let server = self.server(resolver); + + // Use the client's verifier to validate the certificate for our local name. + self.validate(&client, &*chain)?; + + // Publish the new configs. + let _ = self.client_tx.send(client.into()); + let _ = self.server_tx.send(server.into()); + + Ok(()) + } +} + +// === impl Key === + +impl rustls::sign::SigningKey for Key { + fn choose_scheme( + &self, + offered: &[rustls::SignatureScheme], + ) -> Option> { + if !offered.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { + return None; + } + + Some(Box::new(self.clone())) + } + + fn algorithm(&self) -> rustls::internal::msgs::enums::SignatureAlgorithm { + SIGNATURE_ALG_RUSTLS_ALGORITHM + } +} + +impl rustls::sign::Signer for Key { + fn sign(&self, message: &[u8]) -> Result, rustls::TLSError> { + let rng = rand::SystemRandom::new(); + self.0 + .sign(&rng, message) + .map(|signature| signature.as_ref().to_owned()) + .map_err(|ring::error::Unspecified| { + rustls::TLSError::General("Signing Failed".to_owned()) + }) + } + + fn get_scheme(&self) -> rustls::SignatureScheme { + SIGNATURE_ALG_RUSTLS_SCHEME + } +} + +// === impl CertResolver === + +impl CertResolver { + #[inline] + fn resolve_( + &self, + sigschemes: &[rustls::SignatureScheme], + ) -> Option { + if !sigschemes.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { + debug!("Signature scheme not supported -> no certificate"); + return None; + } + + Some(self.0.clone()) + } +} + +impl rustls::ResolvesClientCert for CertResolver { + fn resolve( + &self, + _acceptable_issuers: &[&[u8]], + sigschemes: &[rustls::SignatureScheme], + ) -> Option { + self.resolve_(sigschemes) + } + + fn has_certs(&self) -> bool { + true + } +} + +impl rustls::ResolvesServerCert for CertResolver { + fn resolve(&self, hello: rustls::ClientHello<'_>) -> Option { + let server_name = hello.server_name().or_else(|| { + debug!("no SNI -> no certificate"); + None + })?; + + // Verify that our certificate is valid for the given SNI name. + let c = self.0.cert.first()?; + if let Err(error) = webpki::EndEntityCert::from(c.as_ref()) + .and_then(|c| c.verify_is_valid_for_dns_name(server_name)) + { + debug!(%error, "Local certificate is not valid for SNI"); + return None; + }; + + self.resolve_(hello.sigschemes()) + } +} diff --git a/linkerd/identity/fips/src/lib.rs b/linkerd/identity/fips/src/lib.rs new file mode 100644 index 0000000000..0f37758c91 --- /dev/null +++ b/linkerd/identity/fips/src/lib.rs @@ -0,0 +1,12 @@ +#![deny(warnings, rust_2018_idioms)] +#![forbid(unsafe_code)] + +mod client; +pub mod creds; +mod server; + +pub use self::{ + client::{ClientIo, Connect, ConnectFuture, NewClient}, + server::{Server, ServerIo, TerminateFuture}, +}; +pub use linkerd_identity::*; diff --git a/linkerd/identity/fips/src/server.rs b/linkerd/identity/fips/src/server.rs new file mode 100644 index 0000000000..6fd5806761 --- /dev/null +++ b/linkerd/identity/fips/src/server.rs @@ -0,0 +1,213 @@ +use futures::prelude::*; +use linkerd_identity::{LocalId, Name}; +use linkerd_io as io; +use linkerd_stack::{Param, Service}; +use linkerd_tls::{ + ClientId, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, ServerTls, +}; +use std::{pin::Pin, sync::Arc, task}; +use thiserror::Error; +use tokio::sync::watch; +use tokio_rustls::rustls::{Certificate, ServerConfig, Session}; +use tracing::debug; + +/// A Service that terminates TLS connections using a dynamically updated server configuration. +#[derive(Clone)] +pub struct Server { + name: Name, + rx: watch::Receiver>, +} + +pub type TerminateFuture = futures::future::MapOk< + tokio_rustls::Accept, + fn(tokio_rustls::server::TlsStream) -> (ServerTls, ServerIo), +>; + +#[derive(Debug)] +pub struct ServerIo(tokio_rustls::server::TlsStream); + +#[derive(Debug, Error)] +#[error("credential store lost")] +pub struct LostStore(()); + +impl Server { + pub(crate) fn new(name: Name, rx: watch::Receiver>) -> Self { + Self { name, rx } + } + + #[cfg(test)] + pub(crate) fn config(&self) -> Arc { + (*self.rx.borrow()).clone() + } + + /// Spawns a background task that watches for TLS configuration updates and creates an augmented + /// configuration with the provided ALPN protocols. The returned server uses this ALPN-aware + /// configuration. + pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { + if alpn_protocols.is_empty() { + return Ok(self); + } + + let mut orig_rx = self.rx; + + let mut c = (**orig_rx.borrow_and_update()).clone(); + c.alpn_protocols = alpn_protocols.clone(); + let (tx, rx) = watch::channel(c.into()); + + // Spawn a background task that watches the optional server configuration and publishes it + // as a reliable channel, including any ALPN overrides. + // + // The background task completes when the original sender is closed or when all receivers + // are dropped. + tokio::spawn(async move { + loop { + tokio::select! { + _ = tx.closed() => { + debug!("ALPN TLS config receivers dropped"); + return; + } + res = orig_rx.changed() => { + if res.is_err() { + debug!("TLS config sender closed"); + return; + } + } + } + + let mut c = (*orig_rx.borrow().clone()).clone(); + c.alpn_protocols = alpn_protocols.clone(); + let _ = tx.send(c.into()); + } + }); + + Ok(Self::new(self.name, rx)) + } +} + +impl Param for Server { + fn param(&self) -> LocalId { + LocalId(self.name.clone()) + } +} + +impl Service for Server +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + type Response = (ServerTls, ServerIo); + type Error = std::io::Error; + type Future = TerminateFuture; + + #[inline] + fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { + task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + tokio_rustls::TlsAcceptor::from((*self.rx.borrow()).clone()) + .accept(io) + .map_ok(|io| { + // Determine the peer's identity, if it exist. + let client_id = client_identity(&io); + + let negotiated_protocol = io + .get_ref() + .1 + .get_alpn_protocol() + .map(|b| NegotiatedProtocol(b.into())); + + debug!(client.id = ?client_id, alpn = ?negotiated_protocol, "Accepted TLS connection"); + let tls = ServerTls::Established { + client_id, + negotiated_protocol, + }; + (tls, ServerIo(io)) + }) + } +} + +fn client_identity(tls: &tokio_rustls::server::TlsStream) -> Option { + let (_io, session) = tls.get_ref(); + let certs = session.get_peer_certificates()?; + let c = certs.first().map(Certificate::as_ref)?; + let end_cert = webpki::EndEntityCert::from(c).ok()?; + let dns_names = end_cert.dns_names().ok()?; + + match dns_names.first()? { + webpki::GeneralDNSNameRef::DNSName(n) => { + let s: &str = (*n).into(); + s.parse().ok().map(ClientId) + } + webpki::GeneralDNSNameRef::Wildcard(_) => { + // Wildcards can perhaps be handled in a future path... + None + } + } +} + +// === impl ServerIo === + +impl io::AsyncRead for ServerIo { + #[inline] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl io::AsyncWrite for ServerIo { + #[inline] + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + Pin::new(&mut self.0).poll_flush(cx) + } + + #[inline] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + Pin::new(&mut self.0).poll_shutdown(cx) + } + + #[inline] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> io::Poll { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + #[inline] + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> std::task::Poll> { + Pin::new(&mut self.0).poll_write_vectored(cx, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } +} + +impl HasNegotiatedProtocol for ServerIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.0 + .get_ref() + .1 + .get_alpn_protocol() + .map(NegotiatedProtocolRef) + } +} + +impl io::PeerAddr for ServerIo { + #[inline] + fn peer_addr(&self) -> io::Result { + self.0.get_ref().0.peer_addr() + } +} diff --git a/linkerd/identity/fips/src/tests.rs b/linkerd/identity/fips/src/tests.rs new file mode 100644 index 0000000000..abf27117d0 --- /dev/null +++ b/linkerd/identity/fips/src/tests.rs @@ -0,0 +1,35 @@ +use super::test_util::*; + +#[test] +fn can_construct_client_and_server_config_from_valid_settings() { + FOO_NS1.validate().expect("foo.ns1 must be valid"); +} + +#[test] +fn recognize_ca_did_not_issue_cert() { + let s = Identity { + trust_anchors: include_bytes!("testdata/ca2.pem"), + ..FOO_NS1 + }; + assert!(s.validate().is_err(), "ca2 should not validate foo.ns1"); +} + +#[test] +fn recognize_cert_is_not_valid_for_identity() { + let s = Identity { + crt: BAR_NS1.crt, + key: BAR_NS1.key, + ..FOO_NS1 + }; + assert!(s.validate().is_err(), "identity should not be valid"); +} + +#[test] +#[ignore] // XXX this doesn't fail because we don't actually check the key against the cert... +fn recognize_private_key_is_not_valid_for_cert() { + let s = Identity { + key: BAR_NS1.key, + ..FOO_NS1 + }; + assert!(s.validate().is_err(), "identity should not be valid"); +} From 05a4f5343463148cd180557e43a5179f018b08b4 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 1 Nov 2021 22:14:38 +0000 Subject: [PATCH 02/85] wip --- Cargo.lock | 321 ++++++++++++++++++++++++++++++- linkerd/identity/fips/Cargo.toml | 3 + 2 files changed, 321 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f7faa628d..f0d294ae5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -79,6 +88,17 @@ dependencies = [ "syn", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -91,12 +111,68 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bindgen" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which 3.1.1", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "boring" +version = "1.1.6" +source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +dependencies = [ + "bitflags", + "boring-sys", + "foreign-types", + "lazy_static", + "libc", +] + +[[package]] +name = "boring-sys" +version = "1.1.1" +source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +dependencies = [ + "bindgen", + "cmake", +] + [[package]] name = "bumpalo" version = "3.8.0" @@ -121,12 +197,56 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +[[package]] +name = "cexpr" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +dependencies = [ + "nom 6.1.2", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term 0.11.0", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cmake" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" +dependencies = [ + "cc", +] + [[package]] name = "crc32fast" version = "1.2.1" @@ -192,6 +312,19 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fixedbitset" version = "0.4.0" @@ -216,6 +349,33 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -232,6 +392,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures" version = "0.3.17" @@ -321,6 +487,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "gzip-header" version = "0.3.0" @@ -434,6 +606,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.14" @@ -582,6 +760,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.106" @@ -599,6 +783,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libloading" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -1026,6 +1220,8 @@ dependencies = [ name = "linkerd-identity-fips" version = "0.1.0" dependencies = [ + "boring", + "boring-sys", "futures", "linkerd-error", "linkerd-identity", @@ -1034,6 +1230,7 @@ dependencies = [ "linkerd-tls", "thiserror", "tokio", + "tokio-boring", "tracing", ] @@ -1615,6 +1812,18 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "memchr", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -1685,6 +1894,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1756,7 +1971,7 @@ checksum = "6ab1427f3d2635891f842892dda177883dca0639e05fe66796a62c9d2f23b49c" dependencies = [ "byteorder", "libc", - "nom", + "nom 2.2.1", "rustc_version", ] @@ -1787,7 +2002,7 @@ dependencies = [ "prost-types", "regex", "tempfile", - "which", + "which 4.2.2", ] [[package]] @@ -1837,6 +2052,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.8.4" @@ -1946,6 +2167,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2031,6 +2258,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2079,6 +2312,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "syn" version = "1.0.80" @@ -2090,6 +2329,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.2.0" @@ -2104,6 +2349,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.30" @@ -2168,6 +2431,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "tokio-boring" +version = "2.1.3" +source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +dependencies = [ + "boring", + "boring-sys", + "tokio", +] + [[package]] name = "tokio-io-timeout" version = "1.1.1" @@ -2399,7 +2672,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80a4ddde70311d8da398062ecf6fc2c309337de6b0f77d6c27aff8d53f6fca52" dependencies = [ - "ansi_term", + "ansi_term 0.12.1", "lazy_static", "matchers", "parking_lot", @@ -2487,6 +2760,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "unicode-xid" version = "0.2.2" @@ -2511,6 +2790,18 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "want" version = "0.3.0" @@ -2600,6 +2891,15 @@ dependencies = [ "untrusted", ] +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + [[package]] name = "which" version = "4.2.2" @@ -2633,6 +2933,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2647,3 +2956,9 @@ checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" dependencies = [ "winapi", ] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/linkerd/identity/fips/Cargo.toml b/linkerd/identity/fips/Cargo.toml index 057c326ab5..d4f17668eb 100644 --- a/linkerd/identity/fips/Cargo.toml +++ b/linkerd/identity/fips/Cargo.toml @@ -7,6 +7,8 @@ edition = "2018" publish = false [dependencies] +boring = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} +boring-sys = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} futures = { version = "0.3", default-features = false } linkerd-error = { path = "../../error" } linkerd-io = { path = "../../io" } @@ -15,4 +17,5 @@ linkerd-stack = { path = "../../stack" } linkerd-tls = { path = "../../tls" } thiserror = "1" tokio = { version = "1", features = ["macros", "sync"] } +tokio-boring = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} tracing = "0.1" From 00c2dba609032ae4ceaff975e0b67a36147b85cd Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 04:04:57 +0000 Subject: [PATCH 03/85] wip --- Cargo.toml | 4 + linkerd/identity/fips/src/client.rs | 164 -------------- linkerd/identity/fips/src/creds.rs | 80 ------- linkerd/identity/fips/src/creds/receiver.rs | 116 ---------- linkerd/identity/fips/src/creds/store.rs | 236 -------------------- linkerd/identity/fips/src/server.rs | 212 ------------------ linkerd/identity/fips/src/tests.rs | 35 --- 7 files changed, 4 insertions(+), 843 deletions(-) delete mode 100644 linkerd/identity/fips/src/tests.rs diff --git a/Cargo.toml b/Cargo.toml index 16985633bb..4b9b6e5202 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ # dependencies. This will be the default behavior in Rust 2021. resolver = "2" +exclude = [ + "linkerd/identity/fips", +] + members = [ "hyper-balance", "linkerd/addr", diff --git a/linkerd/identity/fips/src/client.rs b/linkerd/identity/fips/src/client.rs index 052f445020..8b13789179 100644 --- a/linkerd/identity/fips/src/client.rs +++ b/linkerd/identity/fips/src/client.rs @@ -1,165 +1 @@ -use futures::prelude::*; -use linkerd_io as io; -use linkerd_stack::{NewService, Service}; -use linkerd_tls::{client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef}; -use std::{pin::Pin, sync::Arc}; -use tokio::sync::watch; -use tokio_rustls::rustls::{ClientConfig, Session}; -/// A `NewService` that produces `Connect` services from a dynamic TLS configuration. -#[derive(Clone)] -pub struct NewClient { - config: watch::Receiver>, -} - -/// A `Service` that initiates client-side TLS connections. -#[derive(Clone)] -pub struct Connect { - server_id: webpki::DNSName, - config: Arc, -} - -pub type ConnectFuture = futures::future::MapOk< - tokio_rustls::Connect, - fn(tokio_rustls::client::TlsStream) -> ClientIo, ->; - -#[derive(Debug)] -pub struct ClientIo(tokio_rustls::client::TlsStream); - -// === impl NewClient === - -impl NewClient { - pub(crate) fn new(config: watch::Receiver>) -> Self { - Self { config } - } -} - -impl NewService for NewClient { - type Service = Connect; - - fn new_service(&self, target: ClientTls) -> Self::Service { - Connect::new(target, (*self.config.borrow()).clone()) - } -} - -impl std::fmt::Debug for NewClient { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NewClient").finish() - } -} - -// === impl Connect === - -impl Connect { - pub(crate) fn new(client_tls: ClientTls, config: Arc) -> Self { - // If ALPN protocols are configured by the endpoint, we have to clone the entire - // configuration and set the protocols. If there are no ALPN options, clone the Arc'd base - // configuration without extra allocation. - // - // TODO it would be better to avoid cloning the whole TLS config per-connection, but the - // Rustls API doesn't give us a lot of options. - let config = match client_tls.alpn { - None => config, - Some(AlpnProtocols(protocols)) => { - let mut c = (*config).clone(); - c.alpn_protocols = protocols; - Arc::new(c) - } - }; - - let server_id = webpki::DNSNameRef::try_from_ascii(client_tls.server_id.as_bytes()) - .expect("identity must be a valid DNS name") - .to_owned(); - - Self { server_id, config } - } -} - -impl Service for Connect -where - I: io::AsyncRead + io::AsyncWrite + Send + Unpin, -{ - type Response = ClientIo; - type Error = io::Error; - type Future = ConnectFuture; - - fn poll_ready( - &mut self, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, io: I) -> Self::Future { - tokio_rustls::TlsConnector::from(self.config.clone()) - .connect(self.server_id.as_ref(), io) - .map_ok(ClientIo) - } -} - -// === impl ClientIo === - -impl io::AsyncRead for ClientIo { - #[inline] - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &mut io::ReadBuf<'_>, - ) -> io::Poll<()> { - Pin::new(&mut self.0).poll_read(cx, buf) - } -} - -impl io::AsyncWrite for ClientIo { - #[inline] - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { - Pin::new(&mut self.0).poll_flush(cx) - } - - #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { - Pin::new(&mut self.0).poll_shutdown(cx) - } - - #[inline] - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> io::Poll { - Pin::new(&mut self.0).poll_write(cx, buf) - } - - #[inline] - fn poll_write_vectored( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> std::task::Poll> { - Pin::new(&mut self.0).poll_write_vectored(cx, bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - self.0.is_write_vectored() - } -} - -impl HasNegotiatedProtocol for ClientIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.0 - .get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) - } -} - -impl io::PeerAddr for ClientIo { - #[inline] - fn peer_addr(&self) -> io::Result { - self.0.get_ref().0.peer_addr() - } -} diff --git a/linkerd/identity/fips/src/creds.rs b/linkerd/identity/fips/src/creds.rs index 0d356b4bc6..b17518694e 100644 --- a/linkerd/identity/fips/src/creds.rs +++ b/linkerd/identity/fips/src/creds.rs @@ -1,82 +1,2 @@ mod receiver; mod store; - -pub use self::{receiver::Receiver, store::Store}; -use linkerd_error::Result; -use linkerd_identity as id; -use ring::{error::KeyRejected, signature::EcdsaKeyPair}; -use std::sync::Arc; -use thiserror::Error; -use tokio::sync::watch; -use tokio_rustls::rustls; -use tracing::warn; - -#[derive(Debug, Error)] -#[error(transparent)] -pub struct InvalidKey(KeyRejected); - -#[derive(Debug, Error)] -#[error("invalid trust roots")] -pub struct InvalidTrustRoots(()); - -pub fn watch( - identity: id::Name, - roots_pem: &str, - key_pkcs8: &[u8], - csr: &[u8], -) -> Result<(Store, Receiver)> { - let mut roots = rustls::RootCertStore::empty(); - let (added, skipped) = roots - .add_pem_file(&mut std::io::Cursor::new(roots_pem)) - .map_err(InvalidTrustRoots)?; - if skipped != 0 { - warn!("Skipped {} invalid trust anchors", skipped); - } - if added == 0 { - return Err("no trust roots loaded".into()); - } - - let key = EcdsaKeyPair::from_pkcs8(params::SIGNATURE_ALG_RING_SIGNING, key_pkcs8) - .map_err(InvalidKey)?; - - let (client_tx, client_rx) = watch::channel(Arc::new(rustls::ClientConfig::new())); - let (server_tx, server_rx) = watch::channel(Arc::new(rustls::ServerConfig::new( - rustls::AllowAnyAnonymousOrAuthenticatedClient::new(roots.clone()), - ))); - - let rx = Receiver::new(identity.clone(), client_rx, server_rx); - let store = Store::new(roots, key, csr, identity, client_tx, server_tx); - - Ok((store, rx)) -} - -#[cfg(feature = "test-util")] -pub fn for_test(ent: &linkerd_tls_test_util::Entity) -> (Store, Receiver) { - watch( - ent.name.parse().expect("name must be valid"), - std::str::from_utf8(ent.trust_anchors).expect("roots must be PEM"), - ent.key, - b"fake CSR", - ) - .expect("credentials must be valid") -} - -#[cfg(feature = "test-util")] -pub fn default_for_test() -> (Store, Receiver) { - for_test(&linkerd_tls_test_util::FOO_NS1) -} - -mod params { - use tokio_rustls::rustls; - - // These must be kept in sync: - pub static SIGNATURE_ALG_RING_SIGNING: &ring::signature::EcdsaSigningAlgorithm = - &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING; - pub const SIGNATURE_ALG_RUSTLS_SCHEME: rustls::SignatureScheme = - rustls::SignatureScheme::ECDSA_NISTP256_SHA256; - pub const SIGNATURE_ALG_RUSTLS_ALGORITHM: rustls::internal::msgs::enums::SignatureAlgorithm = - rustls::internal::msgs::enums::SignatureAlgorithm::ECDSA; - pub const TLS_VERSIONS: &[rustls::ProtocolVersion] = &[rustls::ProtocolVersion::TLSv1_3]; - pub static TLS_SUPPORTED_CIPHERSUITES: [&rustls::SupportedCipherSuite; 1] = - [&rustls::ciphersuite::TLS13_CHACHA20_POLY1305_SHA256]; -} diff --git a/linkerd/identity/fips/src/creds/receiver.rs b/linkerd/identity/fips/src/creds/receiver.rs index 5cdb9f2e56..8b13789179 100644 --- a/linkerd/identity/fips/src/creds/receiver.rs +++ b/linkerd/identity/fips/src/creds/receiver.rs @@ -1,117 +1 @@ -use crate::{NewClient, Server}; -use linkerd_identity::Name; -use std::sync::Arc; -use tokio::sync::watch; -use tokio_rustls::rustls; -/// Receives TLS config updates to build `NewClient` and `Server` types. -#[derive(Clone)] -pub struct Receiver { - name: Name, - client_rx: watch::Receiver>, - server_rx: watch::Receiver>, -} - -// === impl Receiver === - -impl Receiver { - pub(super) fn new( - name: Name, - client_rx: watch::Receiver>, - server_rx: watch::Receiver>, - ) -> Self { - Self { - name, - client_rx, - server_rx, - } - } - - /// Returns the local identity. - pub fn name(&self) -> &Name { - &self.name - } - - /// Returns a `NewClient` that can be used to establish TLS on client connections. - pub fn new_client(&self) -> NewClient { - NewClient::new(self.client_rx.clone()) - } - - /// Returns a `Server` that can be used to terminate TLS on server connections. - pub fn server(&self) -> Server { - Server::new(self.name.clone(), self.server_rx.clone()) - } -} - -impl std::fmt::Debug for Receiver { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Receiver") - .field("name", &self.name) - .finish() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_server() { - let init_config = Arc::new(rustls::ServerConfig::new(rustls::NoClientAuth::new())); - let (server_tx, server_rx) = watch::channel(init_config.clone()); - let (_, client_rx) = watch::channel(Arc::new(rustls::ClientConfig::new())); - let receiver = Receiver { - name: "example".parse().unwrap(), - server_rx, - client_rx, - }; - - let server = receiver.server(); - - assert!(Arc::ptr_eq(&server.config(), &init_config)); - - let server_config = Arc::new(rustls::ServerConfig::new(rustls::NoClientAuth::new())); - server_tx - .send(server_config.clone()) - .ok() - .expect("receiver is held"); - - assert!(Arc::ptr_eq(&server.config(), &server_config)); - } - - #[tokio::test] - async fn test_spawn_server_with_alpn() { - let init_config = Arc::new(rustls::ServerConfig::new(rustls::NoClientAuth::new())); - let (server_tx, server_rx) = watch::channel(init_config.clone()); - let (_, client_rx) = watch::channel(Arc::new(rustls::ClientConfig::new())); - let receiver = Receiver { - name: "example".parse().unwrap(), - server_rx, - client_rx, - }; - - let server = receiver - .server() - .spawn_with_alpn(vec![b"my.alpn".to_vec()]) - .expect("sender must not be lost"); - - let init_sc = server.config(); - assert!(!Arc::ptr_eq(&init_config, &init_sc)); - assert_eq!(init_sc.alpn_protocols, [b"my.alpn"]); - - let update_config = Arc::new(rustls::ServerConfig::new(rustls::NoClientAuth::new())); - assert!(!Arc::ptr_eq(&update_config, &init_config)); - server_tx - .send(update_config.clone()) - .ok() - .expect("receiver is held"); - - // Give the update task a chance to run. - tokio::task::yield_now().await; - - let update_sc = server.config(); - assert!(!Arc::ptr_eq(&update_config, &update_sc)); - assert!(!Arc::ptr_eq(&init_sc, &update_sc)); - assert_eq!(update_sc.alpn_protocols, [b"my.alpn"]); - } -} diff --git a/linkerd/identity/fips/src/creds/store.rs b/linkerd/identity/fips/src/creds/store.rs index 284b53fa47..8b13789179 100644 --- a/linkerd/identity/fips/src/creds/store.rs +++ b/linkerd/identity/fips/src/creds/store.rs @@ -1,237 +1 @@ -use super::params::*; -use linkerd_error::Result; -use linkerd_identity as id; -use ring::{rand, signature::EcdsaKeyPair}; -use std::sync::Arc; -use tokio::sync::watch; -use tokio_rustls::rustls; -use tracing::debug; -pub struct Store { - roots: rustls::RootCertStore, - key: Arc, - csr: Arc<[u8]>, - name: id::Name, - client_tx: watch::Sender>, - server_tx: watch::Sender>, -} - -#[derive(Clone)] -struct Key(Arc); - -struct CertResolver(rustls::sign::CertifiedKey); - -// === impl Store === - -impl Store { - pub(super) fn new( - roots: rustls::RootCertStore, - key: EcdsaKeyPair, - csr: &[u8], - name: id::Name, - client_tx: watch::Sender>, - server_tx: watch::Sender>, - ) -> Self { - Self { - roots, - key: Arc::new(key), - csr: csr.into(), - name, - client_tx, - server_tx, - } - } - - /// Builds a new TLS client configuration. - fn client(&self, resolver: Arc) -> rustls::ClientConfig { - let mut client = rustls::ClientConfig::new(); - client.ciphersuites = TLS_SUPPORTED_CIPHERSUITES.to_vec(); - - // XXX: Rustls's built-in verifiers don't let us tweak things as fully - // as we'd like (e.g. controlling the set of trusted signature - // algorithms), but they provide good enough defaults for now. - // TODO: lock down the verification further. - // TODO: Change Rustls's API to avoid needing to clone `root_cert_store`. - client.root_store = self.roots.clone(); - - // Disable session resumption for the time-being until resumption is - // more tested. - client.enable_tickets = false; - - // Enable client authentication. - client.client_auth_cert_resolver = resolver; - - client - } - - /// Builds a new TLS server configuration. - fn server(&self, resolver: Arc) -> rustls::ServerConfig { - // Ask TLS clients for a certificate and accept any certificate issued by our trusted CA(s). - // - // XXX: Rustls's built-in verifiers don't let us tweak things as fully as we'd like (e.g. - // controlling the set of trusted signature algorithms), but they provide good enough - // defaults for now. - // TODO: lock down the verification further. - // - // TODO: Change Rustls's API to avoid needing to clone `root_cert_store`. - let mut server = rustls::ServerConfig::new( - rustls::AllowAnyAnonymousOrAuthenticatedClient::new(self.roots.clone()), - ); - server.versions = TLS_VERSIONS.to_vec(); - server.cert_resolver = resolver; - server.ciphersuites = TLS_SUPPORTED_CIPHERSUITES.to_vec(); - server - } - - /// Ensures the certificate is valid for the services we terminate for TLS. This assumes that - /// server cert validation does the same or more validation than client cert validation. - fn validate(&self, client: &rustls::ClientConfig, certs: &[rustls::Certificate]) -> Result<()> { - // XXX: Rustls currently only provides access to a `ServerCertVerifier` through - // `ClientConfig::get_verifier()`. - // - // XXX: Once `ServerCertVerified` is exposed in Rustls's safe API, use it to pass proof to - // CertCertResolver::new.... - // - // TODO: Restrict accepted signature algorithms. - let crt_id = webpki::DNSNameRef::try_from_ascii(self.name.as_bytes()) - .expect("identity must be a valid DNS name"); - static NO_OCSP: &[u8] = &[]; - client - .get_verifier() - .verify_server_cert(&self.roots, &*certs, crt_id, NO_OCSP)?; - debug!("Certified"); - Ok(()) - } -} - -impl id::Credentials for Store { - /// Returns the proxy's identity. - fn dns_name(&self) -> &id::Name { - &self.name - } - - /// Returns the CSR that was configured at proxy startup. - fn gen_certificate_signing_request(&mut self) -> id::DerX509 { - id::DerX509(self.csr.to_vec()) - } - - /// Publishes TLS client and server configurations using - fn set_certificate( - &mut self, - id::DerX509(leaf): id::DerX509, - intermediates: Vec, - _expiry: std::time::SystemTime, - ) -> Result<()> { - let mut chain = Vec::with_capacity(intermediates.len() + 1); - chain.push(rustls::Certificate(leaf)); - chain.extend( - intermediates - .into_iter() - .map(|id::DerX509(der)| rustls::Certificate(der)), - ); - - let resolver = Arc::new(CertResolver(rustls::sign::CertifiedKey::new( - chain.clone(), - Arc::new(Box::new(Key(self.key.clone()))), - ))); - - // Build new client and server TLS configs. - let client = self.client(resolver.clone()); - let server = self.server(resolver); - - // Use the client's verifier to validate the certificate for our local name. - self.validate(&client, &*chain)?; - - // Publish the new configs. - let _ = self.client_tx.send(client.into()); - let _ = self.server_tx.send(server.into()); - - Ok(()) - } -} - -// === impl Key === - -impl rustls::sign::SigningKey for Key { - fn choose_scheme( - &self, - offered: &[rustls::SignatureScheme], - ) -> Option> { - if !offered.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { - return None; - } - - Some(Box::new(self.clone())) - } - - fn algorithm(&self) -> rustls::internal::msgs::enums::SignatureAlgorithm { - SIGNATURE_ALG_RUSTLS_ALGORITHM - } -} - -impl rustls::sign::Signer for Key { - fn sign(&self, message: &[u8]) -> Result, rustls::TLSError> { - let rng = rand::SystemRandom::new(); - self.0 - .sign(&rng, message) - .map(|signature| signature.as_ref().to_owned()) - .map_err(|ring::error::Unspecified| { - rustls::TLSError::General("Signing Failed".to_owned()) - }) - } - - fn get_scheme(&self) -> rustls::SignatureScheme { - SIGNATURE_ALG_RUSTLS_SCHEME - } -} - -// === impl CertResolver === - -impl CertResolver { - #[inline] - fn resolve_( - &self, - sigschemes: &[rustls::SignatureScheme], - ) -> Option { - if !sigschemes.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { - debug!("Signature scheme not supported -> no certificate"); - return None; - } - - Some(self.0.clone()) - } -} - -impl rustls::ResolvesClientCert for CertResolver { - fn resolve( - &self, - _acceptable_issuers: &[&[u8]], - sigschemes: &[rustls::SignatureScheme], - ) -> Option { - self.resolve_(sigschemes) - } - - fn has_certs(&self) -> bool { - true - } -} - -impl rustls::ResolvesServerCert for CertResolver { - fn resolve(&self, hello: rustls::ClientHello<'_>) -> Option { - let server_name = hello.server_name().or_else(|| { - debug!("no SNI -> no certificate"); - None - })?; - - // Verify that our certificate is valid for the given SNI name. - let c = self.0.cert.first()?; - if let Err(error) = webpki::EndEntityCert::from(c.as_ref()) - .and_then(|c| c.verify_is_valid_for_dns_name(server_name)) - { - debug!(%error, "Local certificate is not valid for SNI"); - return None; - }; - - self.resolve_(hello.sigschemes()) - } -} diff --git a/linkerd/identity/fips/src/server.rs b/linkerd/identity/fips/src/server.rs index 6fd5806761..8b13789179 100644 --- a/linkerd/identity/fips/src/server.rs +++ b/linkerd/identity/fips/src/server.rs @@ -1,213 +1 @@ -use futures::prelude::*; -use linkerd_identity::{LocalId, Name}; -use linkerd_io as io; -use linkerd_stack::{Param, Service}; -use linkerd_tls::{ - ClientId, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, ServerTls, -}; -use std::{pin::Pin, sync::Arc, task}; -use thiserror::Error; -use tokio::sync::watch; -use tokio_rustls::rustls::{Certificate, ServerConfig, Session}; -use tracing::debug; -/// A Service that terminates TLS connections using a dynamically updated server configuration. -#[derive(Clone)] -pub struct Server { - name: Name, - rx: watch::Receiver>, -} - -pub type TerminateFuture = futures::future::MapOk< - tokio_rustls::Accept, - fn(tokio_rustls::server::TlsStream) -> (ServerTls, ServerIo), ->; - -#[derive(Debug)] -pub struct ServerIo(tokio_rustls::server::TlsStream); - -#[derive(Debug, Error)] -#[error("credential store lost")] -pub struct LostStore(()); - -impl Server { - pub(crate) fn new(name: Name, rx: watch::Receiver>) -> Self { - Self { name, rx } - } - - #[cfg(test)] - pub(crate) fn config(&self) -> Arc { - (*self.rx.borrow()).clone() - } - - /// Spawns a background task that watches for TLS configuration updates and creates an augmented - /// configuration with the provided ALPN protocols. The returned server uses this ALPN-aware - /// configuration. - pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { - if alpn_protocols.is_empty() { - return Ok(self); - } - - let mut orig_rx = self.rx; - - let mut c = (**orig_rx.borrow_and_update()).clone(); - c.alpn_protocols = alpn_protocols.clone(); - let (tx, rx) = watch::channel(c.into()); - - // Spawn a background task that watches the optional server configuration and publishes it - // as a reliable channel, including any ALPN overrides. - // - // The background task completes when the original sender is closed or when all receivers - // are dropped. - tokio::spawn(async move { - loop { - tokio::select! { - _ = tx.closed() => { - debug!("ALPN TLS config receivers dropped"); - return; - } - res = orig_rx.changed() => { - if res.is_err() { - debug!("TLS config sender closed"); - return; - } - } - } - - let mut c = (*orig_rx.borrow().clone()).clone(); - c.alpn_protocols = alpn_protocols.clone(); - let _ = tx.send(c.into()); - } - }); - - Ok(Self::new(self.name, rx)) - } -} - -impl Param for Server { - fn param(&self) -> LocalId { - LocalId(self.name.clone()) - } -} - -impl Service for Server -where - I: io::AsyncRead + io::AsyncWrite + Send + Unpin, -{ - type Response = (ServerTls, ServerIo); - type Error = std::io::Error; - type Future = TerminateFuture; - - #[inline] - fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { - task::Poll::Ready(Ok(())) - } - - #[inline] - fn call(&mut self, io: I) -> Self::Future { - tokio_rustls::TlsAcceptor::from((*self.rx.borrow()).clone()) - .accept(io) - .map_ok(|io| { - // Determine the peer's identity, if it exist. - let client_id = client_identity(&io); - - let negotiated_protocol = io - .get_ref() - .1 - .get_alpn_protocol() - .map(|b| NegotiatedProtocol(b.into())); - - debug!(client.id = ?client_id, alpn = ?negotiated_protocol, "Accepted TLS connection"); - let tls = ServerTls::Established { - client_id, - negotiated_protocol, - }; - (tls, ServerIo(io)) - }) - } -} - -fn client_identity(tls: &tokio_rustls::server::TlsStream) -> Option { - let (_io, session) = tls.get_ref(); - let certs = session.get_peer_certificates()?; - let c = certs.first().map(Certificate::as_ref)?; - let end_cert = webpki::EndEntityCert::from(c).ok()?; - let dns_names = end_cert.dns_names().ok()?; - - match dns_names.first()? { - webpki::GeneralDNSNameRef::DNSName(n) => { - let s: &str = (*n).into(); - s.parse().ok().map(ClientId) - } - webpki::GeneralDNSNameRef::Wildcard(_) => { - // Wildcards can perhaps be handled in a future path... - None - } - } -} - -// === impl ServerIo === - -impl io::AsyncRead for ServerIo { - #[inline] - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &mut io::ReadBuf<'_>, - ) -> io::Poll<()> { - Pin::new(&mut self.0).poll_read(cx, buf) - } -} - -impl io::AsyncWrite for ServerIo { - #[inline] - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { - Pin::new(&mut self.0).poll_flush(cx) - } - - #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { - Pin::new(&mut self.0).poll_shutdown(cx) - } - - #[inline] - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> io::Poll { - Pin::new(&mut self.0).poll_write(cx, buf) - } - - #[inline] - fn poll_write_vectored( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> std::task::Poll> { - Pin::new(&mut self.0).poll_write_vectored(cx, bufs) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - self.0.is_write_vectored() - } -} - -impl HasNegotiatedProtocol for ServerIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.0 - .get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) - } -} - -impl io::PeerAddr for ServerIo { - #[inline] - fn peer_addr(&self) -> io::Result { - self.0.get_ref().0.peer_addr() - } -} diff --git a/linkerd/identity/fips/src/tests.rs b/linkerd/identity/fips/src/tests.rs deleted file mode 100644 index abf27117d0..0000000000 --- a/linkerd/identity/fips/src/tests.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::test_util::*; - -#[test] -fn can_construct_client_and_server_config_from_valid_settings() { - FOO_NS1.validate().expect("foo.ns1 must be valid"); -} - -#[test] -fn recognize_ca_did_not_issue_cert() { - let s = Identity { - trust_anchors: include_bytes!("testdata/ca2.pem"), - ..FOO_NS1 - }; - assert!(s.validate().is_err(), "ca2 should not validate foo.ns1"); -} - -#[test] -fn recognize_cert_is_not_valid_for_identity() { - let s = Identity { - crt: BAR_NS1.crt, - key: BAR_NS1.key, - ..FOO_NS1 - }; - assert!(s.validate().is_err(), "identity should not be valid"); -} - -#[test] -#[ignore] // XXX this doesn't fail because we don't actually check the key against the cert... -fn recognize_private_key_is_not_valid_for_cert() { - let s = Identity { - key: BAR_NS1.key, - ..FOO_NS1 - }; - assert!(s.validate().is_err(), "identity should not be valid"); -} From 4fa0cc52e32eedb2ec12866a02efff671af11048 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 05:58:34 +0000 Subject: [PATCH 04/85] wip --- Cargo.lock | 85 +++++---------------- linkerd/identity/fips/Cargo.toml | 6 +- linkerd/identity/fips/src/client.rs | 6 ++ linkerd/identity/fips/src/creds.rs | 31 ++++++++ linkerd/identity/fips/src/creds/receiver.rs | 3 +- linkerd/identity/fips/src/creds/store.rs | 67 ++++++++++++++++ linkerd/identity/fips/src/lib.rs | 1 + linkerd/identity/fips/src/server.rs | 4 + 8 files changed, 133 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0d294ae5d..4170672c89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,9 +113,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bindgen" -version = "0.59.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" dependencies = [ "bitflags", "cexpr", @@ -140,22 +140,11 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitvec" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "boring" version = "1.1.6" -source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de45976d3e185902843f8ac67fcdc3f10f47cb96e275a545218273bf09a592f6" dependencies = [ "bitflags", "boring-sys", @@ -167,7 +156,8 @@ dependencies = [ [[package]] name = "boring-sys" version = "1.1.1" -source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2416bce1bcabf0d7995ce0338ec2425b8766a4d5a39d758a3638008911642fc" dependencies = [ "bindgen", "cmake", @@ -199,11 +189,11 @@ checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cexpr" -version = "0.5.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" dependencies = [ - "nom 6.1.2", + "nom 5.1.2", ] [[package]] @@ -351,30 +341,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" -version = "0.5.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-macros", "foreign-types-shared", ] -[[package]] -name = "foreign-types-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "foreign-types-shared" -version = "0.3.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" @@ -392,12 +370,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - [[package]] name = "futures" version = "0.3.17" @@ -1814,12 +1786,10 @@ checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" [[package]] name = "nom" -version = "6.1.2" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ - "bitvec", - "funty", "memchr", "version_check", ] @@ -2052,12 +2022,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - [[package]] name = "rand" version = "0.8.4" @@ -2260,9 +2224,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "signal-hook-registry" @@ -2329,12 +2293,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tempfile" version = "3.2.0" @@ -2434,7 +2392,8 @@ dependencies = [ [[package]] name = "tokio-boring" version = "2.1.3" -source = "git+https://github.com/netapp/boring?branch=feat/fips-support#0f431cbc3be689c477b3a8e8bc8d1ae177cd1e4e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a691f1783bcff212705a7be3ce90428511f7012d085b948741b40a81acb8dd" dependencies = [ "boring", "boring-sys", @@ -2956,9 +2915,3 @@ checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" dependencies = [ "winapi", ] - -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/linkerd/identity/fips/Cargo.toml b/linkerd/identity/fips/Cargo.toml index d4f17668eb..b4bec99319 100644 --- a/linkerd/identity/fips/Cargo.toml +++ b/linkerd/identity/fips/Cargo.toml @@ -7,8 +7,8 @@ edition = "2018" publish = false [dependencies] -boring = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} -boring-sys = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} +boring = { version = "1" } +boring-sys = { version = "1"} futures = { version = "0.3", default-features = false } linkerd-error = { path = "../../error" } linkerd-io = { path = "../../io" } @@ -17,5 +17,5 @@ linkerd-stack = { path = "../../stack" } linkerd-tls = { path = "../../tls" } thiserror = "1" tokio = { version = "1", features = ["macros", "sync"] } -tokio-boring = { git = 'https://github.com/netapp/boring', branch = "feat/fips-support"} +tokio-boring = { version = "2" } tracing = "0.1" diff --git a/linkerd/identity/fips/src/client.rs b/linkerd/identity/fips/src/client.rs index 8b13789179..0c41bc4789 100644 --- a/linkerd/identity/fips/src/client.rs +++ b/linkerd/identity/fips/src/client.rs @@ -1 +1,7 @@ +pub struct NewClient {} +pub struct Connect {} + +pub struct ConnectFuture {} + +pub struct ClientIo {} diff --git a/linkerd/identity/fips/src/creds.rs b/linkerd/identity/fips/src/creds.rs index b17518694e..250295ed15 100644 --- a/linkerd/identity/fips/src/creds.rs +++ b/linkerd/identity/fips/src/creds.rs @@ -1,2 +1,33 @@ mod receiver; mod store; + +pub use self::{receiver::Receiver, store::Store}; +use boring::{ + pkey::PKey, + x509::{store::X509StoreBuilder, X509}, +}; +use linkerd_error::Result; +use linkerd_identity as id; + +pub fn watch( + identity: id::Name, + roots_pem: &str, + key_pkcs8: &[u8], + csr: &[u8], +) -> Result<(Store, Receiver)> { + let roots = { + let mut store = X509StoreBuilder::new()?; + // FIXME(ver) This should handle a list of PEM-encoded certificates. + let cert = X509::from_pem(roots_pem.as_bytes())?; + store.add_cert(cert)?; + store.build() + }; + + let key = PKey::private_key_from_pkcs8(key_pkcs8)?; + + let store = Store::new(roots, key, csr, identity); + + let rx = Receiver {}; + + Ok((store, rx)) +} diff --git a/linkerd/identity/fips/src/creds/receiver.rs b/linkerd/identity/fips/src/creds/receiver.rs index 8b13789179..60be720d33 100644 --- a/linkerd/identity/fips/src/creds/receiver.rs +++ b/linkerd/identity/fips/src/creds/receiver.rs @@ -1 +1,2 @@ - +#[derive(Clone, Debug)] +pub struct Receiver {} diff --git a/linkerd/identity/fips/src/creds/store.rs b/linkerd/identity/fips/src/creds/store.rs index 8b13789179..2507ea74c7 100644 --- a/linkerd/identity/fips/src/creds/store.rs +++ b/linkerd/identity/fips/src/creds/store.rs @@ -1 +1,68 @@ +use boring::{ + error::ErrorStack, + pkey::{PKey, Private}, + x509::{store::X509Store, X509}, +}; +use linkerd_error::Result; +use linkerd_identity as id; +pub struct Store { + roots: X509Store, + key: PKey, + csr: Vec, + name: id::Name, +} + +// === impl Store === + +impl Store { + pub(super) fn new( + roots: X509Store, + key: PKey, + csr: &[u8], + name: id::Name, + // client_tx: watch::Sender>, + // server_tx: watch::Sender>, + ) -> Self { + Self { + roots, + key, + csr: csr.into(), + name, + // client_tx, + // server_tx, + } + } +} + +impl id::Credentials for Store { + /// Returns the proxy's identity. + fn dns_name(&self) -> &id::Name { + &self.name + } + + /// Returns the CSR that was configured at proxy startup. + fn gen_certificate_signing_request(&mut self) -> id::DerX509 { + id::DerX509(self.csr.to_vec()) + } + + /// Publishes TLS client and server configurations using + fn set_certificate( + &mut self, + id::DerX509(leaf): id::DerX509, + intermediates: Vec, + _expiry: std::time::SystemTime, + ) -> Result<()> { + let mut chain = Vec::with_capacity(intermediates.len() + 1); + let cert = X509::from_der(&leaf)?; + chain.push(cert); + chain.extend( + intermediates + .into_iter() + .map(|crt| X509::from_der(&crt)) + .collect::, ErrorStack>>()?, + ); + + Ok(()) + } +} diff --git a/linkerd/identity/fips/src/lib.rs b/linkerd/identity/fips/src/lib.rs index 0f37758c91..6e5f629d46 100644 --- a/linkerd/identity/fips/src/lib.rs +++ b/linkerd/identity/fips/src/lib.rs @@ -1,5 +1,6 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] +#![allow(warnings)] mod client; pub mod creds; diff --git a/linkerd/identity/fips/src/server.rs b/linkerd/identity/fips/src/server.rs index 8b13789179..b5fadda727 100644 --- a/linkerd/identity/fips/src/server.rs +++ b/linkerd/identity/fips/src/server.rs @@ -1 +1,5 @@ +pub struct Server {} +pub struct TerminateFuture {} + +pub struct ServerIo {} From 8e240348326181e3dbde4e0f9a239ed3bf3f34e8 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 15:35:10 +0000 Subject: [PATCH 05/85] sketch out boring initialization --- Cargo.lock | 1 + linkerd/identity/fips/Cargo.toml | 1 + linkerd/identity/fips/src/creds.rs | 12 ++- linkerd/identity/fips/src/creds/receiver.rs | 22 ++++- linkerd/identity/fips/src/creds/store.rs | 94 ++++++++++++++++++--- 5 files changed, 112 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4170672c89..429e223a91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1195,6 +1195,7 @@ dependencies = [ "boring", "boring-sys", "futures", + "linkerd-dns-name", "linkerd-error", "linkerd-identity", "linkerd-io", diff --git a/linkerd/identity/fips/Cargo.toml b/linkerd/identity/fips/Cargo.toml index b4bec99319..30e0f21ade 100644 --- a/linkerd/identity/fips/Cargo.toml +++ b/linkerd/identity/fips/Cargo.toml @@ -11,6 +11,7 @@ boring = { version = "1" } boring-sys = { version = "1"} futures = { version = "0.3", default-features = false } linkerd-error = { path = "../../error" } +linkerd-dns-name = { path = "../../dns/name" } linkerd-io = { path = "../../io" } linkerd-identity = { path = ".." } linkerd-stack = { path = "../../stack" } diff --git a/linkerd/identity/fips/src/creds.rs b/linkerd/identity/fips/src/creds.rs index 250295ed15..ebb8761639 100644 --- a/linkerd/identity/fips/src/creds.rs +++ b/linkerd/identity/fips/src/creds.rs @@ -4,10 +4,12 @@ mod store; pub use self::{receiver::Receiver, store::Store}; use boring::{ pkey::PKey, + ssl, x509::{store::X509StoreBuilder, X509}, }; use linkerd_error::Result; use linkerd_identity as id; +use tokio::sync::watch; pub fn watch( identity: id::Name, @@ -25,9 +27,13 @@ pub fn watch( let key = PKey::private_key_from_pkcs8(key_pkcs8)?; - let store = Store::new(roots, key, csr, identity); - - let rx = Receiver {}; + let (client_tx, client_rx) = + watch::channel(ssl::SslConnector::builder(ssl::SslMethod::tls_client())?.build()); + let (server_tx, server_rx) = watch::channel( + ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?.build(), + ); + let store = Store::new(roots, key, csr, identity, client_tx, server_tx); + let rx = Receiver::new(client_rx, server_rx); Ok((store, rx)) } diff --git a/linkerd/identity/fips/src/creds/receiver.rs b/linkerd/identity/fips/src/creds/receiver.rs index 60be720d33..52c7434325 100644 --- a/linkerd/identity/fips/src/creds/receiver.rs +++ b/linkerd/identity/fips/src/creds/receiver.rs @@ -1,2 +1,20 @@ -#[derive(Clone, Debug)] -pub struct Receiver {} +use boring::ssl; +use tokio::sync::watch; + +#[derive(Clone)] +pub struct Receiver { + client_rx: watch::Receiver, + server_rx: watch::Receiver, +} + +impl Receiver { + pub(crate) fn new( + client_rx: watch::Receiver, + server_rx: watch::Receiver, + ) -> Self { + Self { + client_rx, + server_rx, + } + } +} diff --git a/linkerd/identity/fips/src/creds/store.rs b/linkerd/identity/fips/src/creds/store.rs index 2507ea74c7..1380974889 100644 --- a/linkerd/identity/fips/src/creds/store.rs +++ b/linkerd/identity/fips/src/creds/store.rs @@ -1,16 +1,20 @@ use boring::{ error::ErrorStack, pkey::{PKey, Private}, - x509::{store::X509Store, X509}, + ssl, + x509::{store::X509Store, X509StoreContext, X509}, }; use linkerd_error::Result; use linkerd_identity as id; +use tokio::sync::watch; pub struct Store { roots: X509Store, key: PKey, csr: Vec, name: id::Name, + client_tx: watch::Sender, + server_tx: watch::Sender, } // === impl Store === @@ -21,18 +25,43 @@ impl Store { key: PKey, csr: &[u8], name: id::Name, - // client_tx: watch::Sender>, - // server_tx: watch::Sender>, + client_tx: watch::Sender, + server_tx: watch::Sender, ) -> Self { Self { roots, key, csr: csr.into(), name, - // client_tx, - // server_tx, + client_tx, + server_tx, } } + + fn cert_matches_name(&self, cert: &X509) -> bool { + for san in cert.subject_alt_names().into_iter().flatten() { + if let Some(n) = san.dnsname() { + if let Ok(name) = n.parse::() { + if &name == &*self.name { + return true; + } + } + } + } + + false + } + + fn clone_roots(&self) -> Result { + // X509Store does not implement clone, so we need to manually copy it. + let mut roots = boring::x509::store::X509StoreBuilder::new()?; + for obj in self.roots.objects() { + if let Some(c) = obj.x509() { + roots.add_cert(c.to_owned())?; + } + } + Ok(roots.build()) + } } impl id::Credentials for Store { @@ -53,15 +82,54 @@ impl id::Credentials for Store { intermediates: Vec, _expiry: std::time::SystemTime, ) -> Result<()> { - let mut chain = Vec::with_capacity(intermediates.len() + 1); let cert = X509::from_der(&leaf)?; - chain.push(cert); - chain.extend( - intermediates - .into_iter() - .map(|crt| X509::from_der(&crt)) - .collect::, ErrorStack>>()?, - ); + if !self.cert_matches_name(&cert) { + return Err("certificate does not have a DNS name SAN for the local identity".into()); + } + + let mut chain = boring::stack::Stack::new()?; + chain.push(cert.clone()); + for id::DerX509(der) in intermediates.iter() { + let cert = X509::from_der(der)?; + chain.push(cert); + } + + let mut context = X509StoreContext::new()?; + if !context.init(&self.roots, &cert, &chain, |c| c.verify_cert())? { + return Err("certificate could not be validated against the trust chain".into()); + } + + let conn = { + // FIXME(ver) Restrict TLS version, algorithms, etc. + let mut b = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; + b.set_private_key(self.key.as_ref())?; + b.set_cert_store(self.clone_roots()?); + b.set_certificate(cert.as_ref())?; + for id::DerX509(der) in intermediates.iter() { + let cert = X509::from_der(der)?; + b.add_extra_chain_cert(cert)?; + } + b.build() + }; + + let acc = { + // mozilla_intermediate_v5 is the only variant that enables TLSv1.3, so we use that. + // TODO(ver) Ensure that this configuration includes FIPS-approved algorithms. + let mut b = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; + b.set_private_key(self.key.as_ref())?; + b.set_cert_store(self.clone_roots()?); + b.set_certificate(cert.as_ref())?; + for id::DerX509(der) in intermediates.iter() { + let cert = X509::from_der(der)?; + b.add_extra_chain_cert(cert)?; + } + b.set_verify(ssl::SslVerifyMode::PEER); + b.check_private_key()?; + b.build() + }; + + let _ = self.server_tx.send(acc); + let _ = self.client_tx.send(conn); Ok(()) } From f2d9ab84fc54070269f01dc9f1af225be4a12cc1 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 15:58:03 +0000 Subject: [PATCH 06/85] rename crates: identity-default => identity-rustls-meshtls --- Cargo.lock | 36 +++++++++---------- Cargo.toml | 6 ++-- linkerd/app/core/Cargo.toml | 2 +- linkerd/app/core/src/lib.rs | 2 +- linkerd/app/inbound/Cargo.toml | 2 +- linkerd/app/inbound/fuzz/Cargo.toml | 2 +- linkerd/app/outbound/Cargo.toml | 2 +- .../Cargo.toml | 2 +- .../src/client.rs | 0 .../src/creds.rs | 0 .../src/creds/receiver.rs | 0 .../src/creds/store.rs | 0 .../src/lib.rs | 0 .../src/server.rs | 0 .../{default => rustls-meshtls}/Cargo.toml | 2 +- .../{default => rustls-meshtls}/src/client.rs | 0 .../{default => rustls-meshtls}/src/creds.rs | 0 .../src/creds/receiver.rs | 0 .../src/creds/store.rs | 0 .../{default => rustls-meshtls}/src/lib.rs | 0 .../{default => rustls-meshtls}/src/server.rs | 0 .../{default => rustls-meshtls}/src/tests.rs | 0 .../tests/tls_accept.rs | 2 +- linkerd/proxy/tap/Cargo.toml | 2 +- linkerd/proxy/tap/src/accept.rs | 2 +- 25 files changed, 31 insertions(+), 31 deletions(-) rename linkerd/identity/{fips => boring-mozilla-intermediate-v5}/Cargo.toml (92%) rename linkerd/identity/{fips => boring-mozilla-intermediate-v5}/src/client.rs (100%) rename linkerd/identity/{fips => boring-mozilla-intermediate-v5}/src/creds.rs (100%) rename linkerd/identity/{fips => boring-mozilla-intermediate-v5}/src/creds/receiver.rs (100%) rename linkerd/identity/{fips => boring-mozilla-intermediate-v5}/src/creds/store.rs (100%) rename linkerd/identity/{fips => boring-mozilla-intermediate-v5}/src/lib.rs (100%) rename linkerd/identity/{fips => boring-mozilla-intermediate-v5}/src/server.rs (100%) rename linkerd/identity/{default => rustls-meshtls}/Cargo.toml (96%) rename linkerd/identity/{default => rustls-meshtls}/src/client.rs (100%) rename linkerd/identity/{default => rustls-meshtls}/src/creds.rs (100%) rename linkerd/identity/{default => rustls-meshtls}/src/creds/receiver.rs (100%) rename linkerd/identity/{default => rustls-meshtls}/src/creds/store.rs (100%) rename linkerd/identity/{default => rustls-meshtls}/src/lib.rs (100%) rename linkerd/identity/{default => rustls-meshtls}/src/server.rs (100%) rename linkerd/identity/{default => rustls-meshtls}/src/tests.rs (100%) rename linkerd/identity/{default => rustls-meshtls}/tests/tls_accept.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 429e223a91..905680f071 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -840,7 +840,7 @@ dependencies = [ "linkerd-http-classify", "linkerd-http-metrics", "linkerd-http-retry", - "linkerd-identity-default", + "linkerd-identity-rustls-meshtls", "linkerd-io", "linkerd-metrics", "linkerd-opencensus", @@ -909,7 +909,7 @@ dependencies = [ "libfuzzer-sys", "linkerd-app-core", "linkerd-app-test", - "linkerd-identity-default", + "linkerd-identity-rustls-meshtls", "linkerd-io", "linkerd-server-policy", "linkerd-tonic-watch", @@ -965,7 +965,7 @@ dependencies = [ "linkerd-app-test", "linkerd-http-retry", "linkerd-identity", - "linkerd-identity-default", + "linkerd-identity-rustls-meshtls", "linkerd-io", "linkerd-tracing", "parking_lot", @@ -1166,45 +1166,45 @@ dependencies = [ ] [[package]] -name = "linkerd-identity-default" +name = "linkerd-identity-mozilla-intermediate-v5" version = "0.1.0" dependencies = [ + "boring", + "boring-sys", "futures", - "linkerd-conditional", + "linkerd-dns-name", "linkerd-error", "linkerd-identity", "linkerd-io", - "linkerd-proxy-transport", "linkerd-stack", "linkerd-tls", - "linkerd-tls-test-util", - "linkerd-tracing", - "ring", "thiserror", "tokio", - "tokio-rustls", - "tower", + "tokio-boring", "tracing", - "webpki", ] [[package]] -name = "linkerd-identity-fips" +name = "linkerd-identity-rustls-meshtls" version = "0.1.0" dependencies = [ - "boring", - "boring-sys", "futures", - "linkerd-dns-name", + "linkerd-conditional", "linkerd-error", "linkerd-identity", "linkerd-io", + "linkerd-proxy-transport", "linkerd-stack", "linkerd-tls", + "linkerd-tls-test-util", + "linkerd-tracing", + "ring", "thiserror", "tokio", - "tokio-boring", + "tokio-rustls", + "tower", "tracing", + "webpki", ] [[package]] @@ -1387,7 +1387,7 @@ dependencies = [ "ipnet", "linkerd-conditional", "linkerd-error", - "linkerd-identity-default", + "linkerd-identity-rustls-meshtls", "linkerd-io", "linkerd-proxy-http", "linkerd-stack", diff --git a/Cargo.toml b/Cargo.toml index 4b9b6e5202..c3b83f0ea9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" exclude = [ - "linkerd/identity/fips", + "linkerd/identity/boring-mozilla-intermediate-v5", ] members = [ @@ -33,8 +33,8 @@ members = [ "linkerd/http-metrics", "linkerd/http-retry", "linkerd/identity", - "linkerd/identity/default", - "linkerd/identity/fips", + "linkerd/identity/boring-mozilla-intermediate-v5", + "linkerd/identity/rustls-meshtls", "linkerd/io", "linkerd/metrics", "linkerd/opencensus", diff --git a/linkerd/app/core/Cargo.toml b/linkerd/app/core/Cargo.toml index 3d5c1327e1..4e07552d1c 100644 --- a/linkerd/app/core/Cargo.toml +++ b/linkerd/app/core/Cargo.toml @@ -33,7 +33,7 @@ linkerd-exp-backoff = { path = "../../exp-backoff" } linkerd-http-classify = { path = "../../http-classify" } linkerd-http-metrics = { path = "../../http-metrics" } linkerd-http-retry = { path = "../../http-retry" } -linkerd-identity-default = { path = "../../identity/default" } +linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls" } linkerd-io = { path = "../../io" } linkerd-metrics = { path = "../../metrics", features = ["linkerd-stack"] } linkerd-opencensus = { path = "../../opencensus" } diff --git a/linkerd/app/core/src/lib.rs b/linkerd/app/core/src/lib.rs index faa9d425e8..b56486a8c9 100644 --- a/linkerd/app/core/src/lib.rs +++ b/linkerd/app/core/src/lib.rs @@ -20,7 +20,7 @@ pub use linkerd_dns; pub use linkerd_error::{is_error, Error, Infallible, Recover, Result}; pub use linkerd_exp_backoff as exp_backoff; pub use linkerd_http_metrics as http_metrics; -pub use linkerd_identity_default as identity; +pub use linkerd_identity_rustls_meshtls as identity; pub use linkerd_io as io; pub use linkerd_opencensus as opencensus; pub use linkerd_proxy_identity_client as identity_client; diff --git a/linkerd/app/inbound/Cargo.toml b/linkerd/app/inbound/Cargo.toml index d9f3fdb6ea..8c7186d2f2 100644 --- a/linkerd/app/inbound/Cargo.toml +++ b/linkerd/app/inbound/Cargo.toml @@ -34,7 +34,7 @@ libfuzzer-sys = { version = "0.4.2", features = ["arbitrary-derive"] } hyper = { version = "0.14.14", features = ["http1", "http2"] } linkerd-app-test = { path = "../test" } linkerd-io = { path = "../../io", features = ["tokio-test"] } -linkerd-identity-default = { path = "../../identity/default", features = ["test-util"] } +linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls", features = ["test-util"] } linkerd-tracing = { path = "../../tracing", features = ["ansi"] } tokio = { version = "1", features = ["full", "macros"] } tokio-test = "0.4" diff --git a/linkerd/app/inbound/fuzz/Cargo.toml b/linkerd/app/inbound/fuzz/Cargo.toml index 9ae622f451..17815e6851 100644 --- a/linkerd/app/inbound/fuzz/Cargo.toml +++ b/linkerd/app/inbound/fuzz/Cargo.toml @@ -17,7 +17,7 @@ libfuzzer-sys = { version = "0.4.2", features = ["arbitrary-derive"] } linkerd-app-core = { path = "../../core" } linkerd-app-inbound = { path = ".." } linkerd-app-test = { path = "../../test" } -linkerd-identity-default = { path = "../../../identity/default", features = ["test-util"] } +linkerd-identity-rustls-meshtls = { path = "../../../identity/rustls-meshtls", features = ["test-util"] } linkerd-tracing = { path = "../../../tracing", features = ["ansi"] } tokio = { version = "1", features = ["full"] } tracing = "0.1" diff --git a/linkerd/app/outbound/Cargo.toml b/linkerd/app/outbound/Cargo.toml index 796c25003c..5ddf137df9 100644 --- a/linkerd/app/outbound/Cargo.toml +++ b/linkerd/app/outbound/Cargo.toml @@ -32,7 +32,7 @@ pin-project = "1" hyper = { version = "0.14.14", features = ["http1", "http2"] } linkerd-app-test = { path = "../test" } linkerd-io = { path = "../../io", features = ["tokio-test"] } -linkerd-identity-default = { path = "../../identity/default", features = ["test-util"] } +linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls", features = ["test-util"] } linkerd-tracing = { path = "../../tracing", features = ["ansi"] } parking_lot = "0.11" tokio = { version = "1", features = ["time", "macros"] } diff --git a/linkerd/identity/fips/Cargo.toml b/linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml similarity index 92% rename from linkerd/identity/fips/Cargo.toml rename to linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml index 30e0f21ade..5ea9f4ec3c 100644 --- a/linkerd/identity/fips/Cargo.toml +++ b/linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "linkerd-identity-fips" +name = "linkerd-identity-mozilla-intermediate-v5" version = "0.1.0" authors = ["Linkerd Developers "] license = "Apache-2.0" diff --git a/linkerd/identity/fips/src/client.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs similarity index 100% rename from linkerd/identity/fips/src/client.rs rename to linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs diff --git a/linkerd/identity/fips/src/creds.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds.rs similarity index 100% rename from linkerd/identity/fips/src/creds.rs rename to linkerd/identity/boring-mozilla-intermediate-v5/src/creds.rs diff --git a/linkerd/identity/fips/src/creds/receiver.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs similarity index 100% rename from linkerd/identity/fips/src/creds/receiver.rs rename to linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs diff --git a/linkerd/identity/fips/src/creds/store.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/store.rs similarity index 100% rename from linkerd/identity/fips/src/creds/store.rs rename to linkerd/identity/boring-mozilla-intermediate-v5/src/creds/store.rs diff --git a/linkerd/identity/fips/src/lib.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs similarity index 100% rename from linkerd/identity/fips/src/lib.rs rename to linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs diff --git a/linkerd/identity/fips/src/server.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs similarity index 100% rename from linkerd/identity/fips/src/server.rs rename to linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs diff --git a/linkerd/identity/default/Cargo.toml b/linkerd/identity/rustls-meshtls/Cargo.toml similarity index 96% rename from linkerd/identity/default/Cargo.toml rename to linkerd/identity/rustls-meshtls/Cargo.toml index eedb64872e..d9d3b3d472 100644 --- a/linkerd/identity/default/Cargo.toml +++ b/linkerd/identity/rustls-meshtls/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "linkerd-identity-default" +name = "linkerd-identity-rustls-meshtls" version = "0.1.0" authors = ["Linkerd Developers "] license = "Apache-2.0" diff --git a/linkerd/identity/default/src/client.rs b/linkerd/identity/rustls-meshtls/src/client.rs similarity index 100% rename from linkerd/identity/default/src/client.rs rename to linkerd/identity/rustls-meshtls/src/client.rs diff --git a/linkerd/identity/default/src/creds.rs b/linkerd/identity/rustls-meshtls/src/creds.rs similarity index 100% rename from linkerd/identity/default/src/creds.rs rename to linkerd/identity/rustls-meshtls/src/creds.rs diff --git a/linkerd/identity/default/src/creds/receiver.rs b/linkerd/identity/rustls-meshtls/src/creds/receiver.rs similarity index 100% rename from linkerd/identity/default/src/creds/receiver.rs rename to linkerd/identity/rustls-meshtls/src/creds/receiver.rs diff --git a/linkerd/identity/default/src/creds/store.rs b/linkerd/identity/rustls-meshtls/src/creds/store.rs similarity index 100% rename from linkerd/identity/default/src/creds/store.rs rename to linkerd/identity/rustls-meshtls/src/creds/store.rs diff --git a/linkerd/identity/default/src/lib.rs b/linkerd/identity/rustls-meshtls/src/lib.rs similarity index 100% rename from linkerd/identity/default/src/lib.rs rename to linkerd/identity/rustls-meshtls/src/lib.rs diff --git a/linkerd/identity/default/src/server.rs b/linkerd/identity/rustls-meshtls/src/server.rs similarity index 100% rename from linkerd/identity/default/src/server.rs rename to linkerd/identity/rustls-meshtls/src/server.rs diff --git a/linkerd/identity/default/src/tests.rs b/linkerd/identity/rustls-meshtls/src/tests.rs similarity index 100% rename from linkerd/identity/default/src/tests.rs rename to linkerd/identity/rustls-meshtls/src/tests.rs diff --git a/linkerd/identity/default/tests/tls_accept.rs b/linkerd/identity/rustls-meshtls/tests/tls_accept.rs similarity index 99% rename from linkerd/identity/default/tests/tls_accept.rs rename to linkerd/identity/rustls-meshtls/tests/tls_accept.rs index 5f014cb228..9ac9412d63 100644 --- a/linkerd/identity/default/tests/tls_accept.rs +++ b/linkerd/identity/rustls-meshtls/tests/tls_accept.rs @@ -8,7 +8,7 @@ use futures::prelude::*; use linkerd_conditional::Conditional; use linkerd_error::Infallible; -use linkerd_identity_default::{self as identity, Credentials, DerX509, Name}; +use linkerd_identity_rustls_meshtls::{self as identity, Credentials, DerX509, Name}; use linkerd_io::{self as io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use linkerd_proxy_transport::{ addrs::*, diff --git a/linkerd/proxy/tap/Cargo.toml b/linkerd/proxy/tap/Cargo.toml index 1aa9732a40..19c4c2fd59 100644 --- a/linkerd/proxy/tap/Cargo.toml +++ b/linkerd/proxy/tap/Cargo.toml @@ -17,7 +17,7 @@ ipnet = "2.3" linkerd2-proxy-api = { version = "0.3", features = ["tap", "server"] } linkerd-conditional = { path = "../../conditional" } linkerd-error = { path = "../../error" } -linkerd-identity-default = { path = "../../identity/default" } +linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls" } linkerd-io = { path = "../../io" } linkerd-proxy-http = { path = "../http" } linkerd-stack = { path = "../../stack" } diff --git a/linkerd/proxy/tap/src/accept.rs b/linkerd/proxy/tap/src/accept.rs index 4d414b6ae9..db0863bf4f 100644 --- a/linkerd/proxy/tap/src/accept.rs +++ b/linkerd/proxy/tap/src/accept.rs @@ -3,7 +3,7 @@ use futures::future; use linkerd2_proxy_api::tap::tap_server::{Tap, TapServer}; use linkerd_conditional::Conditional; use linkerd_error::Error; -use linkerd_identity_default as identity; +use linkerd_identity_rustls_meshtls as identity; use linkerd_io as io; use linkerd_proxy_http::{trace, HyperServerSvc}; use linkerd_tls as tls; From b9b41c8fee583972e4d32f2de10a891a348ee331 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 17:15:09 +0000 Subject: [PATCH 07/85] add cmake to actions --- .github/workflows/rust.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0abdd2abf2..5805d7b3af 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,6 +34,7 @@ jobs: permissions: contents: read steps: + - run: apt update && apt install -y cmake - uses: actions/checkout@1e204e9a9253d643386038d443f96446fa156a97 - run: rustup component add clippy - run: make lint @@ -47,6 +48,7 @@ jobs: permissions: contents: read steps: + - run: apt update && apt install -y cmake - uses: actions/checkout@1e204e9a9253d643386038d443f96446fa156a97 - run: for d in $(for toml in $(find . -mindepth 2 -name Cargo.toml -not -path '*/fuzz/*') ; do echo ${toml%/*} ; done | sort -r ) ; do echo "# $d" ; (cd $d ; cargo check --all-targets) ; done From c31ca52398f570e85d6774f03d6c7128ffe2ccfe Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 17:26:45 +0000 Subject: [PATCH 08/85] more deps. yay. --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5805d7b3af..daae563da7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,7 +34,7 @@ jobs: permissions: contents: read steps: - - run: apt update && apt install -y cmake + - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@1e204e9a9253d643386038d443f96446fa156a97 - run: rustup component add clippy - run: make lint @@ -48,7 +48,7 @@ jobs: permissions: contents: read steps: - - run: apt update && apt install -y cmake + - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@1e204e9a9253d643386038d443f96446fa156a97 - run: for d in $(for toml in $(find . -mindepth 2 -name Cargo.toml -not -path '*/fuzz/*') ; do echo ${toml%/*} ; done | sort -r ) ; do echo "# $d" ; (cd $d ; cargo check --all-targets) ; done From ead49f8d1b54be9d9851f590ec47105cc1d0ccb7 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 17:51:59 +0000 Subject: [PATCH 09/85] +deny --- deny.toml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/deny.toml b/deny.toml index 516c9b5a18..4e92ca2400 100644 --- a/deny.toml +++ b/deny.toml @@ -47,8 +47,15 @@ highlight = "all" deny = [ { name = "rustls", wrappers = ["tokio-rustls"] } ] -skip = [] -skip-tree = [] +skip = [ + # boring-sys pulls in an old version via bindgen. See + # https://github.com/cloudflare/boring/pull/55. + { name = "ansi_term" }, +] +skip-tree = [ + # Hasn't seen a new release since 2017. Pulls in an older version of nom. + { name = "procinfo" } +] [sources] unknown-registry = "deny" From fe0ed294318aaf4d5b23cd47c7ddd1a7b7b34b36 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 20:11:28 +0000 Subject: [PATCH 10/85] wip: client compiles --- .../src/client.rs | 91 ++++++++++++++++++- .../src/creds.rs | 15 +-- .../src/creds/receiver.rs | 20 ++++ .../src/server.rs | 16 +++- 4 files changed, 131 insertions(+), 11 deletions(-) diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs index 0c41bc4789..f6d0459707 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs @@ -1,7 +1,90 @@ -pub struct NewClient {} +use boring::ssl; +use linkerd_error::{Error, Result}; +use linkerd_identity::Name; +use linkerd_io as io; +use linkerd_stack::{NewService, Service}; +use linkerd_tls::client::{AlpnProtocols, ClientTls, ServerId}; +use std::{future::Future, pin::Pin}; +use tokio::sync::watch; -pub struct Connect {} +#[derive(Clone)] +pub struct NewClient(watch::Receiver); -pub struct ConnectFuture {} +#[derive(Clone)] +pub struct Connect { + server_id: Name, + connector: ssl::SslConnector, +} -pub struct ClientIo {} +pub type ConnectFuture = Pin>> + Send>>; + +#[derive(Debug)] +pub struct ClientIo(tokio_boring::SslStream); + +// === impl NewClient === + +impl NewClient { + pub(crate) fn new(rx: watch::Receiver) -> Self { + Self(rx) + } +} + +impl NewService for NewClient { + type Service = Connect; + + fn new_service(&self, target: ClientTls) -> Self::Service { + Connect::new(target, (*self.0.borrow()).clone()) + } +} + +// === impl Connect === + +impl Connect { + pub(crate) fn new(client_tls: ClientTls, connector: ssl::SslConnector) -> Self { + let ServerId(server_id) = client_tls.server_id; + + if let Some(AlpnProtocols(protocols)) = client_tls.alpn { + if !protocols.is_empty() { + todo!("support ALPN") + } + } + + Self { + server_id, + connector, + } + } +} + +impl Service for Connect +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + std::fmt::Debug + 'static, +{ + type Response = ClientIo; + type Error = io::Error; + type Future = ConnectFuture; + + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, io: I) -> Self::Future { + let conn = self.connector.clone(); + let id = self.server_id.clone(); + Box::pin(async move { + let config = conn + .configure() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let io = tokio_boring::connect(config, id.as_str(), io) + .await + .map_err(|e| match e.as_io_error() { + Some(ioe) => io::Error::new(ioe.kind(), ioe.to_string()), + None => io::Error::new(io::ErrorKind::Other, e), + })?; + Ok(ClientIo(io)) + }) + } +} diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds.rs index ebb8761639..fe9f3c694a 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds.rs @@ -27,13 +27,16 @@ pub fn watch( let key = PKey::private_key_from_pkcs8(key_pkcs8)?; - let (client_tx, client_rx) = - watch::channel(ssl::SslConnector::builder(ssl::SslMethod::tls_client())?.build()); - let (server_tx, server_rx) = watch::channel( - ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?.build(), - ); + let (client_tx, client_rx) = { + let conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; + watch::channel(conn.build()) + }; + let (server_tx, server_rx) = { + let acc = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; + watch::channel(acc.build()) + }; + let rx = Receiver::new(identity.clone(), client_rx, server_rx); let store = Store::new(roots, key, csr, identity, client_tx, server_tx); - let rx = Receiver::new(client_rx, server_rx); Ok((store, rx)) } diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs index 52c7434325..619e4fd04c 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs @@ -1,20 +1,40 @@ +use crate::{NewClient, Server}; use boring::ssl; +use linkerd_identity::Name; use tokio::sync::watch; #[derive(Clone)] pub struct Receiver { + name: Name, client_rx: watch::Receiver, server_rx: watch::Receiver, } impl Receiver { pub(crate) fn new( + name: Name, client_rx: watch::Receiver, server_rx: watch::Receiver, ) -> Self { Self { + name, client_rx, server_rx, } } + + /// Returns the local identity. + pub fn name(&self) -> &Name { + &self.name + } + + /// Returns a `NewClient` that can be used to establish TLS on client connections. + pub fn new_client(&self) -> NewClient { + NewClient::new(self.client_rx.clone()) + } + + /// Returns a `Server` that can be used to terminate TLS on server connections. + pub fn server(&self) -> Server { + Server::new(self.name.clone(), self.server_rx.clone()) + } } diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs index b5fadda727..5fef0130b1 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs @@ -1,5 +1,19 @@ -pub struct Server {} +use boring::ssl; +use linkerd_identity::Name; +use tokio::sync::watch; + +#[derive(Clone)] +pub struct Server { + name: Name, + rx: watch::Receiver, +} pub struct TerminateFuture {} pub struct ServerIo {} + +impl Server { + pub(crate) fn new(name: Name, rx: watch::Receiver) -> Self { + Self { name, rx } + } +} From 6742b529502c8a52f23b92de7d53b575ecd92772 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 20:46:57 +0000 Subject: [PATCH 11/85] +ServerIo --- .../src/client.rs | 70 +++++++++- .../src/server.rs | 131 +++++++++++++++++- 2 files changed, 198 insertions(+), 3 deletions(-) diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs index f6d0459707..d600cd50eb 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs @@ -1,9 +1,12 @@ use boring::ssl; +use futures::prelude::*; use linkerd_error::{Error, Result}; use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{NewService, Service}; -use linkerd_tls::client::{AlpnProtocols, ClientTls, ServerId}; +use linkerd_tls::{ + client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerId, +}; use std::{future::Future, pin::Pin}; use tokio::sync::watch; @@ -88,3 +91,68 @@ where }) } } + +// === impl ClientIo === + +impl io::AsyncRead for ClientIo { + #[inline] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl io::AsyncWrite for ClientIo { + #[inline] + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + Pin::new(&mut self.0).poll_flush(cx) + } + + #[inline] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + Pin::new(&mut self.0).poll_shutdown(cx) + } + + #[inline] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> io::Poll { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + #[inline] + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> std::task::Poll> { + Pin::new(&mut self.0).poll_write_vectored(cx, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } +} + +impl HasNegotiatedProtocol for ClientIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.0 + .ssl() + .selected_alpn_protocol() + .map(NegotiatedProtocolRef) + } +} + +impl io::PeerAddr for ClientIo { + #[inline] + fn peer_addr(&self) -> io::Result { + self.0.get_ref().peer_addr() + } +} diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs index 5fef0130b1..401af8c1d2 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs @@ -1,6 +1,12 @@ use boring::ssl; +use futures::prelude::*; use linkerd_identity::Name; +use linkerd_io as io; +use linkerd_stack::{Param, Service}; +use linkerd_tls::{ClientId, LocalId, NegotiatedProtocol, NegotiatedProtocolRef, ServerTls}; +use std::{future::Future, pin::Pin}; use tokio::sync::watch; +use tracing::debug; #[derive(Clone)] pub struct Server { @@ -8,12 +14,133 @@ pub struct Server { rx: watch::Receiver, } -pub struct TerminateFuture {} +pub type TerminateFuture = + Pin)>> + Send>>; -pub struct ServerIo {} +#[derive(Debug)] +pub struct ServerIo(tokio_boring::SslStream); + +// === impl Server === impl Server { pub(crate) fn new(name: Name, rx: watch::Receiver) -> Self { Self { name, rx } } } + +impl Param for Server { + fn param(&self) -> LocalId { + LocalId(self.name.clone()) + } +} + +impl Service for Server +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + std::fmt::Debug + 'static, +{ + type Response = (ServerTls, ServerIo); + type Error = std::io::Error; + type Future = TerminateFuture; + + #[inline] + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + let acc = (*self.rx.borrow()).clone(); + Box::pin(async move { + let io = tokio_boring::accept(&acc, io) + .await + .map(ServerIo) + .map_err(|e| match e.as_io_error() { + Some(ioe) => io::Error::new(ioe.kind(), ioe.to_string()), + None => io::Error::new(io::ErrorKind::Other, e), + })?; + + let client_id = io.client_identity(); + let negotiated_protocol = io.negotiated_protocol_ref().map(|p| p.to_owned()); + + debug!(client.id = ?client_id, alpn = ?negotiated_protocol, "Accepted TLS connection"); + let tls = ServerTls::Established { + client_id, + negotiated_protocol, + }; + Ok((tls, io)) + }) + } +} + +// === impl ServerIo === + +impl ServerIo { + fn negotiated_protocol_ref(&self) -> Option> { + self.0 + .ssl() + .selected_alpn_protocol() + .map(NegotiatedProtocolRef) + } + + fn client_identity(&self) -> Option { + let cert = self.0.ssl().peer_certificate()?; + let peer = cert.subject_alt_names()?.pop()?; + peer.dnsname()?.parse().ok() + } +} + +impl io::AsyncRead for ServerIo { + #[inline] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl io::AsyncWrite for ServerIo { + #[inline] + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + Pin::new(&mut self.0).poll_flush(cx) + } + + #[inline] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + Pin::new(&mut self.0).poll_shutdown(cx) + } + + #[inline] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> io::Poll { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + #[inline] + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> std::task::Poll> { + Pin::new(&mut self.0).poll_write_vectored(cx, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } +} + +impl io::PeerAddr for ServerIo { + #[inline] + fn peer_addr(&self) -> io::Result { + self.0.get_ref().peer_addr() + } +} From 4bbbeb7329136cf65c3c7baf9d6789a2847dce3b Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 21:09:25 +0000 Subject: [PATCH 12/85] Address warnings --- .../identity/boring-mozilla-intermediate-v5/src/client.rs | 3 +-- .../boring-mozilla-intermediate-v5/src/creds/store.rs | 7 +++---- linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs | 1 - .../identity/boring-mozilla-intermediate-v5/src/server.rs | 3 +-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs index d600cd50eb..2a83a56f02 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs @@ -1,6 +1,5 @@ use boring::ssl; -use futures::prelude::*; -use linkerd_error::{Error, Result}; +use linkerd_error::Result; use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{NewService, Service}; diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/store.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/store.rs index 1380974889..b0c9f74d28 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/store.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/store.rs @@ -1,5 +1,4 @@ use boring::{ - error::ErrorStack, pkey::{PKey, Private}, ssl, x509::{store::X509Store, X509StoreContext, X509}, @@ -42,7 +41,7 @@ impl Store { for san in cert.subject_alt_names().into_iter().flatten() { if let Some(n) = san.dnsname() { if let Ok(name) = n.parse::() { - if &name == &*self.name { + if name == *self.name { return true; } } @@ -88,10 +87,10 @@ impl id::Credentials for Store { } let mut chain = boring::stack::Stack::new()?; - chain.push(cert.clone()); + chain.push(cert.clone())?; for id::DerX509(der) in intermediates.iter() { let cert = X509::from_der(der)?; - chain.push(cert); + chain.push(cert)?; } let mut context = X509StoreContext::new()?; diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs index 6e5f629d46..0f37758c91 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs @@ -1,6 +1,5 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -#![allow(warnings)] mod client; pub mod creds; diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs index 401af8c1d2..eb5dcb6bda 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs @@ -1,9 +1,8 @@ use boring::ssl; -use futures::prelude::*; use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{Param, Service}; -use linkerd_tls::{ClientId, LocalId, NegotiatedProtocol, NegotiatedProtocolRef, ServerTls}; +use linkerd_tls::{ClientId, LocalId, NegotiatedProtocolRef, ServerTls}; use std::{future::Future, pin::Pin}; use tokio::sync::watch; use tracing::debug; From fc452e4e772c8f09f60d09fb1cd00b8bab3d256b Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 21:19:09 +0000 Subject: [PATCH 13/85] Add ALPN todo --- .../boring-mozilla-intermediate-v5/src/server.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs index eb5dcb6bda..3a9f623ac5 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs @@ -1,4 +1,5 @@ use boring::ssl; +use linkerd_error::Result; use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{Param, Service}; @@ -25,6 +26,14 @@ impl Server { pub(crate) fn new(name: Name, rx: watch::Receiver) -> Self { Self { name, rx } } + + pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { + if alpn_protocols.is_empty() { + return Ok(self); + } + + todo!("support ALPN") + } } impl Param for Server { From c6721ea89064075240ca9139b67da3943058eecf Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 2 Nov 2021 23:44:36 +0000 Subject: [PATCH 14/85] Get the facade compiling --- Cargo.lock | 4 +- linkerd/app/core/Cargo.toml | 9 +- linkerd/app/core/src/identity.rs | 493 ++++++++++++++++++ linkerd/app/core/src/lib.rs | 5 +- .../boring-mozilla-intermediate-v5/Cargo.toml | 2 +- .../src/client.rs | 6 + .../src/creds/receiver.rs | 8 + 7 files changed, 521 insertions(+), 6 deletions(-) create mode 100644 linkerd/app/core/src/identity.rs diff --git a/Cargo.lock b/Cargo.lock index 905680f071..82461b72e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -840,6 +840,8 @@ dependencies = [ "linkerd-http-classify", "linkerd-http-metrics", "linkerd-http-retry", + "linkerd-identity", + "linkerd-identity-boring-mozilla-intermediate-v5", "linkerd-identity-rustls-meshtls", "linkerd-io", "linkerd-metrics", @@ -1166,7 +1168,7 @@ dependencies = [ ] [[package]] -name = "linkerd-identity-mozilla-intermediate-v5" +name = "linkerd-identity-boring-mozilla-intermediate-v5" version = "0.1.0" dependencies = [ "boring", diff --git a/linkerd/app/core/Cargo.toml b/linkerd/app/core/Cargo.toml index 4e07552d1c..bc0185e752 100644 --- a/linkerd/app/core/Cargo.toml +++ b/linkerd/app/core/Cargo.toml @@ -12,6 +12,11 @@ This crate conglomerates proxy configuration, runtime administration, etc, independently of the inbound and outbound proxy logic. """ +[features] +default = ["identity-rustls-meshtls"] +identity-rustls-meshtls = ["linkerd-identity-rustls-meshtls"] +identity-boring-mozilla-intermediate-v5 = ["linkerd-identity-boring-mozilla-intermediate-v5"] + [dependencies] bytes = "1" drain = { version = "0.1.0", features = ["retain"] } @@ -33,7 +38,9 @@ linkerd-exp-backoff = { path = "../../exp-backoff" } linkerd-http-classify = { path = "../../http-classify" } linkerd-http-metrics = { path = "../../http-metrics" } linkerd-http-retry = { path = "../../http-retry" } -linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls" } +linkerd-identity = { path = "../../identity" } +linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls", optional = true } +linkerd-identity-boring-mozilla-intermediate-v5 = { path = "../../identity/boring-mozilla-intermediate-v5", optional = true } linkerd-io = { path = "../../io" } linkerd-metrics = { path = "../../metrics", features = ["linkerd-stack"] } linkerd-opencensus = { path = "../../opencensus" } diff --git a/linkerd/app/core/src/identity.rs b/linkerd/app/core/src/identity.rs new file mode 100644 index 0000000000..effabba4d0 --- /dev/null +++ b/linkerd/app/core/src/identity.rs @@ -0,0 +1,493 @@ +#![allow(irrefutable_let_patterns)] + +use futures::Future; +use linkerd_error::Result; +pub use linkerd_identity::*; +use linkerd_io as io; +pub use linkerd_proxy_identity_client as client; +use linkerd_stack::{NewService, Service}; +use linkerd_tls::{ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerTls}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +#[derive(Copy, Clone, Debug)] +pub enum Mode { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls, + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5, +} + +pub enum Store { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(linkerd_identity_rustls_meshtls::creds::Store), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::creds::Store), +} + +#[derive(Clone, Debug)] +pub enum Receiver { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(linkerd_identity_rustls_meshtls::creds::Receiver), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::creds::Receiver), +} + +#[derive(Clone, Debug)] +pub enum NewClient { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(linkerd_identity_rustls_meshtls::NewClient), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::NewClient), +} + +#[derive(Clone)] +pub enum Connect { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(linkerd_identity_rustls_meshtls::Connect), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::Connect), +} + +#[pin_project::pin_project(project = ConnectFutureProj)] +pub enum ConnectFuture { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(#[pin] linkerd_identity_rustls_meshtls::ConnectFuture), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5( + #[pin] linkerd_identity_boring_mozilla_intermediate_v5::ConnectFuture, + ), +} + +#[pin_project::pin_project(project = ClientIoProj)] +#[derive(Debug)] +pub enum ClientIo { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(#[pin] linkerd_identity_rustls_meshtls::ClientIo), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5( + #[pin] linkerd_identity_boring_mozilla_intermediate_v5::ClientIo, + ), +} + +#[derive(Clone)] +pub enum Server { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(linkerd_identity_rustls_meshtls::Server), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::Server), +} + +#[pin_project::pin_project(project = TerminateFutureProj)] +pub enum TerminateFuture { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(#[pin] linkerd_identity_rustls_meshtls::TerminateFuture), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5( + #[pin] linkerd_identity_boring_mozilla_intermediate_v5::TerminateFuture, + ), +} + +#[pin_project::pin_project(project = ServerIoProj)] +#[derive(Debug)] +pub enum ServerIo { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(#[pin] linkerd_identity_rustls_meshtls::ServerIo), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5( + #[pin] linkerd_identity_boring_mozilla_intermediate_v5::ServerIo, + ), +} + +// === impl Mode === + +impl Mode { + pub fn watch( + self, + identity: Name, + roots_pem: &str, + key_pkcs8: &[u8], + csr: &[u8], + ) -> Result<(Store, Receiver)> { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls = self { + let (store, receiver) = + linkerd_identity_rustls_meshtls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; + return Ok(( + Store::RustlsMeshtls(store), + Receiver::RustlsMeshtls(receiver), + )); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5 = self { + let (store, receiver) = linkerd_identity_boring_mozilla_intermediate_v5::creds::watch( + identity, roots_pem, key_pkcs8, csr, + )?; + return Ok(( + Store::BoringMozillaIntermediateV5(store), + Receiver::BoringMozillaIntermediateV5(receiver), + )); + } + + unreachable!() + } +} + +// === impl Store === + +impl Credentials for Store { + fn dns_name(&self) -> &Name { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(store) = self { + return store.dns_name(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(store) = self { + return store.dns_name(); + } + + unreachable!() + } + + fn gen_certificate_signing_request(&mut self) -> DerX509 { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(store) = self { + return store.gen_certificate_signing_request(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(store) = self { + return store.gen_certificate_signing_request(); + } + + unreachable!() + } + + fn set_certificate( + &mut self, + leaf: DerX509, + chain: Vec, + expiry: std::time::SystemTime, + ) -> Result<()> { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + unreachable!() + } +} + +// === impl Receiver === + +impl Receiver { + pub fn name(&self) -> &Name { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(receiver) = self { + return receiver.name(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(receiver) = self { + return receiver.name(); + } + + unreachable!() + } + + pub fn new_client(&self) -> NewClient { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(receiver) = self { + return NewClient::RustlsMeshtls(receiver.new_client()); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(receiver) = self { + return NewClient::BoringMozillaIntermediateV5(receiver.new_client()); + } + + unreachable!() + } + + pub fn server(&self) -> Server { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(receiver) = self { + return Server::RustlsMeshtls(receiver.server()); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(receiver) = self { + return Server::BoringMozillaIntermediateV5(receiver.server()); + } + + unreachable!() + } +} + +// === impl NewClient === + +impl NewService for NewClient { + type Service = Connect; + + fn new_service(&self, target: ClientTls) -> Self::Service { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(new_client) = self { + return Connect::RustlsMeshtls(new_client.new_service(target)); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(new_client) = self { + return Connect::BoringMozillaIntermediateV5(new_client.new_service(target)); + } + + unreachable!() + } +} + +// === impl Connect === + +impl Service for Connect +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + std::fmt::Debug + 'static, +{ + type Response = ClientIo; + type Error = io::Error; + type Future = ConnectFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(connect) = self { + return >::poll_ready( + connect, cx, + ); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(connect) = self { + return >::poll_ready( + connect, + cx, + ); + } + + unreachable!() + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(connect) = self { + return ConnectFuture::RustlsMeshtls(connect.call(io)); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(connect) = self { + return ConnectFuture::BoringMozillaIntermediateV5(connect.call(io)); + } + + unreachable!() + } +} + +// === impl ConnectFuture === + +impl Future for ConnectFuture +where + I: io::AsyncRead + io::AsyncWrite + Unpin, +{ + type Output = io::Result>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ConnectFutureProj::RustlsMeshtls(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(ClientIo::RustlsMeshtls)); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ConnectFutureProj::BoringMozillaIntermediateV5(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(ClientIo::BoringMozillaIntermediateV5)); + } + + unreachable!() + } +} + +// === impl ClientIo === + +impl io::AsyncRead for ClientIo { + #[inline] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ClientIoProj::RustlsMeshtls(io) = this { + return io.poll_read(cx, buf); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_read(cx, buf); + } + + unreachable!() + } +} + +impl io::AsyncWrite for ClientIo { + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ClientIoProj::RustlsMeshtls(io) = this { + return io.poll_flush(cx); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_flush(cx); + } + + unreachable!() + } + + #[inline] + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ClientIoProj::RustlsMeshtls(io) = this { + return io.poll_shutdown(cx); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_shutdown(cx); + } + + unreachable!() + } + + #[inline] + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ClientIoProj::RustlsMeshtls(io) = this { + return io.poll_write(cx, buf); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_write(cx, buf); + } + + unreachable!() + } + + #[inline] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ClientIoProj::RustlsMeshtls(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + unreachable!() + } + + #[inline] + fn is_write_vectored(&self) -> bool { + unimplemented!() + } +} + +impl HasNegotiatedProtocol for ClientIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + unimplemented!() + } +} + +impl io::PeerAddr for ClientIo { + #[inline] + fn peer_addr(&self) -> io::Result { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(io) = self { + return io.peer_addr(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(io) = self { + return io.peer_addr(); + } + + unreachable!() + } +} + +// === impl TerminateFuture === + +impl Future for TerminateFuture +where + I: io::AsyncRead + io::AsyncWrite + Unpin, +{ + type Output = io::Result<(ServerTls, ServerIo)>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let TerminateFutureProj::RustlsMeshtls(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::RustlsMeshtls(io)))); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let TerminateFutureProj::BoringMozillaIntermediateV5(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready( + res.map(|(tls, io)| (tls, ServerIo::BoringMozillaIntermediateV5(io))), + ); + } + + unreachable!() + } +} diff --git a/linkerd/app/core/src/lib.rs b/linkerd/app/core/src/lib.rs index b56486a8c9..c7112e729c 100644 --- a/linkerd/app/core/src/lib.rs +++ b/linkerd/app/core/src/lib.rs @@ -20,10 +20,8 @@ pub use linkerd_dns; pub use linkerd_error::{is_error, Error, Infallible, Recover, Result}; pub use linkerd_exp_backoff as exp_backoff; pub use linkerd_http_metrics as http_metrics; -pub use linkerd_identity_rustls_meshtls as identity; pub use linkerd_io as io; pub use linkerd_opencensus as opencensus; -pub use linkerd_proxy_identity_client as identity_client; pub use linkerd_service_profiles as profiles; pub use linkerd_stack_metrics as stack_metrics; pub use linkerd_stack_tracing as stack_tracing; @@ -41,6 +39,7 @@ pub mod dns; pub mod dst; pub mod errors; pub mod http_tracing; +pub mod identity; pub mod metrics; pub mod proxy; pub mod retry; @@ -57,7 +56,7 @@ const DEFAULT_PORT: u16 = 80; #[derive(Clone, Debug)] pub struct ProxyRuntime { - pub identity: identity::creds::Receiver, + pub identity: identity::Receiver, pub metrics: metrics::Proxy, pub tap: proxy::tap::Registry, pub span_sink: http_tracing::OpenCensusSink, diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml b/linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml index 5ea9f4ec3c..498ce2bf84 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml +++ b/linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "linkerd-identity-mozilla-intermediate-v5" +name = "linkerd-identity-boring-mozilla-intermediate-v5" version = "0.1.0" authors = ["Linkerd Developers "] license = "Apache-2.0" diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs index 2a83a56f02..82fb9300d2 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs @@ -39,6 +39,12 @@ impl NewService for NewClient { } } +impl std::fmt::Debug for NewClient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NewClient").finish() + } +} + // === impl Connect === impl Connect { diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs index 619e4fd04c..82a3d8c68d 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs @@ -38,3 +38,11 @@ impl Receiver { Server::new(self.name.clone(), self.server_rx.clone()) } } + +impl std::fmt::Debug for Receiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Receiver") + .field("name", &self.name) + .finish() + } +} From 08b44ae7284f3e3fe74bdad7df16c677298246ff Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Wed, 3 Nov 2021 00:31:46 +0000 Subject: [PATCH 15/85] +ServerIo --- linkerd/app/core/src/identity.rs | 468 +++++++++++++----- linkerd/app/core/src/lib.rs | 2 +- linkerd/app/gateway/src/lib.rs | 2 +- linkerd/app/outbound/src/endpoint.rs | 2 +- linkerd/app/outbound/src/logical.rs | 2 +- linkerd/app/outbound/src/tcp/connect.rs | 2 +- linkerd/app/src/env.rs | 10 +- linkerd/app/src/identity.rs | 16 +- .../src/client.rs | 2 +- 9 files changed, 363 insertions(+), 143 deletions(-) diff --git a/linkerd/app/core/src/identity.rs b/linkerd/app/core/src/identity.rs index effabba4d0..8df634dcfb 100644 --- a/linkerd/app/core/src/identity.rs +++ b/linkerd/app/core/src/identity.rs @@ -5,7 +5,7 @@ use linkerd_error::Result; pub use linkerd_identity::*; use linkerd_io as io; pub use linkerd_proxy_identity_client as client; -use linkerd_stack::{NewService, Service}; +use linkerd_stack::{NewService, Param, Service}; use linkerd_tls::{ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerTls}; use std::{ pin::Pin, @@ -21,23 +21,6 @@ pub enum Mode { BoringMozillaIntermediateV5, } -pub enum Store { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(linkerd_identity_rustls_meshtls::creds::Store), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::creds::Store), -} - -#[derive(Clone, Debug)] -pub enum Receiver { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(linkerd_identity_rustls_meshtls::creds::Receiver), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::creds::Receiver), -} - #[derive(Clone, Debug)] pub enum NewClient { #[cfg(feature = "identity-rustls-meshtls")] @@ -113,6 +96,23 @@ pub enum ServerIo { // === impl Mode === +#[cfg(feature = "identity-rustls-meshtls")] +impl Default for Mode { + fn default() -> Self { + Self::RustlsMeshtls + } +} + +#[cfg(all( + not(feature = "identity-rustls-meshtls"), + feature = "identity-boring-mozilla-intermediate-v5" +))] +impl Default for Mode { + fn default() -> Self { + Self::BoringMozillaIntermediateV5 + } +} + impl Mode { pub fn watch( self, @@ -120,14 +120,14 @@ impl Mode { roots_pem: &str, key_pkcs8: &[u8], csr: &[u8], - ) -> Result<(Store, Receiver)> { + ) -> Result<(creds::Store, creds::Receiver)> { #[cfg(feature = "identity-rustls-meshtls")] if let Self::RustlsMeshtls = self { let (store, receiver) = linkerd_identity_rustls_meshtls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; return Ok(( - Store::RustlsMeshtls(store), - Receiver::RustlsMeshtls(receiver), + creds::Store::RustlsMeshtls(store), + creds::Receiver::RustlsMeshtls(receiver), )); } @@ -137,8 +137,8 @@ impl Mode { identity, roots_pem, key_pkcs8, csr, )?; return Ok(( - Store::BoringMozillaIntermediateV5(store), - Receiver::BoringMozillaIntermediateV5(receiver), + creds::Store::BoringMozillaIntermediateV5(store), + creds::Receiver::BoringMozillaIntermediateV5(receiver), )); } @@ -146,103 +146,6 @@ impl Mode { } } -// === impl Store === - -impl Credentials for Store { - fn dns_name(&self) -> &Name { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(store) = self { - return store.dns_name(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(store) = self { - return store.dns_name(); - } - - unreachable!() - } - - fn gen_certificate_signing_request(&mut self) -> DerX509 { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(store) = self { - return store.gen_certificate_signing_request(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(store) = self { - return store.gen_certificate_signing_request(); - } - - unreachable!() - } - - fn set_certificate( - &mut self, - leaf: DerX509, - chain: Vec, - expiry: std::time::SystemTime, - ) -> Result<()> { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(store) = self { - return store.set_certificate(leaf, chain, expiry); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(store) = self { - return store.set_certificate(leaf, chain, expiry); - } - - unreachable!() - } -} - -// === impl Receiver === - -impl Receiver { - pub fn name(&self) -> &Name { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(receiver) = self { - return receiver.name(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(receiver) = self { - return receiver.name(); - } - - unreachable!() - } - - pub fn new_client(&self) -> NewClient { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(receiver) = self { - return NewClient::RustlsMeshtls(receiver.new_client()); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(receiver) = self { - return NewClient::BoringMozillaIntermediateV5(receiver.new_client()); - } - - unreachable!() - } - - pub fn server(&self) -> Server { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(receiver) = self { - return Server::RustlsMeshtls(receiver.server()); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(receiver) = self { - return Server::BoringMozillaIntermediateV5(receiver.server()); - } - - unreachable!() - } -} - // === impl NewClient === impl NewService for NewClient { @@ -267,7 +170,7 @@ impl NewService for NewClient { impl Service for Connect where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + std::fmt::Debug + 'static, + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, { type Response = ClientIo; type Error = io::Error; @@ -463,6 +366,81 @@ impl io::PeerAddr for ClientIo { } } +// === impl Server === + +impl Param for Server { + fn param(&self) -> LocalId { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(srv) = self { + return srv.param(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(srv) = self { + return srv.param(); + } + + unreachable!() + } +} + +impl Server { + pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(srv) = self { + return srv.spawn_with_alpn(alpn_protocols).map(Self::RustlsMeshtls).map_err(Into::into); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(srv) = self { + return srv.spawn_with_alpn(alpn_protocols).map(Self::BoringMozillaIntermediateV5); + } + + unreachable!() + } +} + +impl Service for Server +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, +{ + type Response = (ServerTls, ServerIo); + type Error = io::Error; + type Future = TerminateFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(svc) = self { + return >::poll_ready(svc, cx); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(svc) = self { + return >::poll_ready( + svc, + cx, + ); + } + + unreachable!() + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(svc) = self { + return TerminateFuture::RustlsMeshtls(svc.call(io)); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(svc) = self { + return TerminateFuture::BoringMozillaIntermediateV5(svc.call(io)); + } + + unreachable!() + } +} + // === impl TerminateFuture === impl Future for TerminateFuture @@ -491,3 +469,251 @@ where unreachable!() } } + +// === impl ServerIo === + +impl io::AsyncRead for ServerIo { + #[inline] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ServerIoProj::RustlsMeshtls(io) = this { + return io.poll_read(cx, buf); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_read(cx, buf); + } + + unreachable!() + } +} + +impl io::AsyncWrite for ServerIo { + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ServerIoProj::RustlsMeshtls(io) = this { + return io.poll_flush(cx); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_flush(cx); + } + + unreachable!() + } + + #[inline] + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ServerIoProj::RustlsMeshtls(io) = this { + return io.poll_shutdown(cx); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_shutdown(cx); + } + + unreachable!() + } + + #[inline] + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ServerIoProj::RustlsMeshtls(io) = this { + return io.poll_write(cx, buf); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_write(cx, buf); + } + + unreachable!() + } + + #[inline] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + + #[cfg(feature = "identity-rustls-meshtls")] + if let ServerIoProj::RustlsMeshtls(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + unreachable!() + } + + #[inline] + fn is_write_vectored(&self) -> bool { + unimplemented!() + } +} + +impl HasNegotiatedProtocol for ServerIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + unimplemented!() + } +} + +impl io::PeerAddr for ServerIo { + #[inline] + fn peer_addr(&self) -> io::Result { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(io) = self { + return io.peer_addr(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(io) = self { + return io.peer_addr(); + } + + unreachable!() + } +} + +pub mod creds { + use super::*; + + pub enum Store { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(linkerd_identity_rustls_meshtls::creds::Store), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::creds::Store), + } + + #[derive(Clone, Debug)] + pub enum Receiver { + #[cfg(feature = "identity-rustls-meshtls")] + RustlsMeshtls(linkerd_identity_rustls_meshtls::creds::Receiver), + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + BoringMozillaIntermediateV5( + linkerd_identity_boring_mozilla_intermediate_v5::creds::Receiver, + ), + } + + // === impl Store === + + impl Credentials for Store { + fn dns_name(&self) -> &Name { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(store) = self { + return store.dns_name(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(store) = self { + return store.dns_name(); + } + + unreachable!() + } + + fn gen_certificate_signing_request(&mut self) -> DerX509 { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(store) = self { + return store.gen_certificate_signing_request(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(store) = self { + return store.gen_certificate_signing_request(); + } + + unreachable!() + } + + fn set_certificate( + &mut self, + leaf: DerX509, + chain: Vec, + expiry: std::time::SystemTime, + ) -> Result<()> { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + unreachable!() + } + } + + // === impl Receiver === + + impl Receiver { + pub fn name(&self) -> &Name { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(receiver) = self { + return receiver.name(); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(receiver) = self { + return receiver.name(); + } + + unreachable!() + } + + pub fn new_client(&self) -> NewClient { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(receiver) = self { + return NewClient::RustlsMeshtls(receiver.new_client()); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(receiver) = self { + return NewClient::BoringMozillaIntermediateV5(receiver.new_client()); + } + + unreachable!() + } + + pub fn server(&self) -> Server { + #[cfg(feature = "identity-rustls-meshtls")] + if let Self::RustlsMeshtls(receiver) = self { + return Server::RustlsMeshtls(receiver.server()); + } + + #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] + if let Self::BoringMozillaIntermediateV5(receiver) = self { + return Server::BoringMozillaIntermediateV5(receiver.server()); + } + + unreachable!() + } + } +} diff --git a/linkerd/app/core/src/lib.rs b/linkerd/app/core/src/lib.rs index c7112e729c..1c8faa86ed 100644 --- a/linkerd/app/core/src/lib.rs +++ b/linkerd/app/core/src/lib.rs @@ -56,7 +56,7 @@ const DEFAULT_PORT: u16 = 80; #[derive(Clone, Debug)] pub struct ProxyRuntime { - pub identity: identity::Receiver, + pub identity: identity::creds::Receiver, pub metrics: metrics::Proxy, pub tap: proxy::tap::Registry, pub span_sink: http_tracing::OpenCensusSink, diff --git a/linkerd/app/gateway/src/lib.rs b/linkerd/app/gateway/src/lib.rs index dfb9be0484..558c2cbb7f 100644 --- a/linkerd/app/gateway/src/lib.rs +++ b/linkerd/app/gateway/src/lib.rs @@ -72,7 +72,7 @@ where O: Clone + Send + Sync + Unpin + 'static, O: svc::Service, O::Response: - io::AsyncRead + io::AsyncWrite + tls::HasNegotiatedProtocol + Send + Unpin + 'static, + io::AsyncRead + io::AsyncWrite + tls::HasNegotiatedProtocol + Send + Sync + Unpin + 'static, O::Future: Send + Unpin + 'static, P: profiles::GetProfile + Clone + Send + Sync + Unpin + 'static, P::Future: Send + 'static, diff --git a/linkerd/app/outbound/src/endpoint.rs b/linkerd/app/outbound/src/endpoint.rs index e46e3ce62c..c80e7f3707 100644 --- a/linkerd/app/outbound/src/endpoint.rs +++ b/linkerd/app/outbound/src/endpoint.rs @@ -211,7 +211,7 @@ impl Outbound { Self: Clone + 'static, S: svc::Service + Clone + Send + Sync + Unpin + 'static, S::Response: - tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, + tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, S::Future: Send + Unpin, I: io::AsyncRead + io::AsyncWrite + io::PeerAddr, I: fmt::Debug + Send + Sync + Unpin + 'static, diff --git a/linkerd/app/outbound/src/logical.rs b/linkerd/app/outbound/src/logical.rs index 8daa362a2c..cbfd2f9fa0 100644 --- a/linkerd/app/outbound/src/logical.rs +++ b/linkerd/app/outbound/src/logical.rs @@ -120,7 +120,7 @@ impl Outbound { C: Clone + Send + Sync + Unpin + 'static, C: svc::Service, C::Response: - tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, + tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, C::Future: Send + Unpin, R: Clone + Send + 'static, R: Resolve + Sync, diff --git a/linkerd/app/outbound/src/tcp/connect.rs b/linkerd/app/outbound/src/tcp/connect.rs index 11d88ab4d4..11e1b55b05 100644 --- a/linkerd/app/outbound/src/tcp/connect.rs +++ b/linkerd/app/outbound/src/tcp/connect.rs @@ -52,7 +52,7 @@ impl Outbound { + svc::Param, C: svc::Service + Clone + Send + 'static, C::Response: tls::HasNegotiatedProtocol, - C::Response: io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, + C::Response: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, C::Future: Send + 'static, { self.map_stack(|config, rt, connect| { diff --git a/linkerd/app/src/env.rs b/linkerd/app/src/env.rs index ec1e086197..00069a6038 100644 --- a/linkerd/app/src/env.rs +++ b/linkerd/app/src/env.rs @@ -2,7 +2,6 @@ use crate::core::{ addr, config::*, control::{Config as ControlConfig, ControlAddr}, - identity_client, proxy::http::{h1, h2}, tls, transport::{Keepalive, ListenAddr}, @@ -1102,14 +1101,7 @@ pub fn parse_control_addr( pub fn parse_identity_config( strings: &S, -) -> Result< - ( - ControlAddr, - identity_client::certify::Config, - identity::Documents, - ), - EnvError, -> { +) -> Result<(ControlAddr, identity::certify::Config, identity::Documents), EnvError> { let control = parse_control_addr(strings, ENV_IDENTITY_SVC_BASE); let ta = parse(strings, ENV_IDENTITY_TRUST_ANCHORS, |s| { if s.is_empty() { diff --git a/linkerd/app/src/identity.rs b/linkerd/app/src/identity.rs index b6c635c289..040bd06516 100644 --- a/linkerd/app/src/identity.rs +++ b/linkerd/app/src/identity.rs @@ -1,15 +1,17 @@ +pub use linkerd_app_core::identity::{ + client::{certify, TokenSource}, + InvalidName, LocalId, Name, +}; use linkerd_app_core::{ control, dns, exp_backoff::{ExponentialBackoff, ExponentialBackoffStream}, - identity::{creds, Credentials, DerX509}, - identity_client::{Certify, Metrics as IdentityMetrics}, + identity::{ + client::{Certify, Metrics as IdentityMetrics}, + creds, Credentials, DerX509, Mode, + }, metrics::ControlHttp as ClientMetrics, Error, Result, }; -pub use linkerd_app_core::{ - identity::{InvalidName, LocalId, Name}, - identity_client::{certify, TokenSource}, -}; use std::{future::Future, pin::Pin}; use tokio::sync::watch; use tracing::Instrument; @@ -53,7 +55,7 @@ struct NotifyReady { impl Config { pub fn build(self, dns: dns::Resolver, client_metrics: ClientMetrics) -> Result { - let (store, receiver) = creds::watch( + let (store, receiver) = Mode::default().watch( (*self.documents.id).clone(), &self.documents.trust_anchors_pem, &self.documents.key_pkcs8, diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs index 82fb9300d2..16c0db6f4b 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs @@ -66,7 +66,7 @@ impl Connect { impl Service for Connect where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + std::fmt::Debug + 'static, + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, { type Response = ClientIo; type Error = io::Error; From e4d2f2a3b5dbdf77330c2913aa2ea92be1e1c9ad Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Wed, 3 Nov 2021 00:59:23 +0000 Subject: [PATCH 16/85] Move union types into the meshtls crate --- Cargo.lock | 20 +- Cargo.toml | 1 + linkerd/app/core/Cargo.toml | 9 +- linkerd/app/core/src/identity.rs | 719 ------------------ linkerd/app/core/src/lib.rs | 7 +- .../src/client.rs | 2 +- linkerd/meshtls/Cargo.toml | 24 + linkerd/meshtls/src/lib.rs | 706 +++++++++++++++++ linkerd/proxy/tap/Cargo.toml | 2 +- linkerd/proxy/tap/src/accept.rs | 4 +- 10 files changed, 762 insertions(+), 732 deletions(-) delete mode 100644 linkerd/app/core/src/identity.rs create mode 100644 linkerd/meshtls/Cargo.toml create mode 100644 linkerd/meshtls/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 82461b72e1..a1b61ce3a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,9 +841,8 @@ dependencies = [ "linkerd-http-metrics", "linkerd-http-retry", "linkerd-identity", - "linkerd-identity-boring-mozilla-intermediate-v5", - "linkerd-identity-rustls-meshtls", "linkerd-io", + "linkerd-meshtls", "linkerd-metrics", "linkerd-opencensus", "linkerd-proxy-api-resolve", @@ -1223,6 +1222,21 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "linkerd-meshtls" +version = "0.1.0" +dependencies = [ + "futures", + "linkerd-error", + "linkerd-identity", + "linkerd-identity-boring-mozilla-intermediate-v5", + "linkerd-identity-rustls-meshtls", + "linkerd-io", + "linkerd-stack", + "linkerd-tls", + "pin-project", +] + [[package]] name = "linkerd-metrics" version = "0.1.0" @@ -1389,8 +1403,8 @@ dependencies = [ "ipnet", "linkerd-conditional", "linkerd-error", - "linkerd-identity-rustls-meshtls", "linkerd-io", + "linkerd-meshtls", "linkerd-proxy-http", "linkerd-stack", "linkerd-tls", diff --git a/Cargo.toml b/Cargo.toml index c3b83f0ea9..35e0cd99e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "linkerd/identity/boring-mozilla-intermediate-v5", "linkerd/identity/rustls-meshtls", "linkerd/io", + "linkerd/meshtls", "linkerd/metrics", "linkerd/opencensus", "linkerd/proxy/api-resolve", diff --git a/linkerd/app/core/Cargo.toml b/linkerd/app/core/Cargo.toml index bc0185e752..42e1bd77d9 100644 --- a/linkerd/app/core/Cargo.toml +++ b/linkerd/app/core/Cargo.toml @@ -13,9 +13,9 @@ independently of the inbound and outbound proxy logic. """ [features] -default = ["identity-rustls-meshtls"] -identity-rustls-meshtls = ["linkerd-identity-rustls-meshtls"] -identity-boring-mozilla-intermediate-v5 = ["linkerd-identity-boring-mozilla-intermediate-v5"] +default = ["meshtls-rustls"] +meshtls-rustls = ["linkerd-meshtls/rustls"] +meshtls-boring = ["linkerd-meshtls/boring"] [dependencies] bytes = "1" @@ -39,9 +39,8 @@ linkerd-http-classify = { path = "../../http-classify" } linkerd-http-metrics = { path = "../../http-metrics" } linkerd-http-retry = { path = "../../http-retry" } linkerd-identity = { path = "../../identity" } -linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls", optional = true } -linkerd-identity-boring-mozilla-intermediate-v5 = { path = "../../identity/boring-mozilla-intermediate-v5", optional = true } linkerd-io = { path = "../../io" } +linkerd-meshtls = { path = "../../meshtls", default-features = false } linkerd-metrics = { path = "../../metrics", features = ["linkerd-stack"] } linkerd-opencensus = { path = "../../opencensus" } linkerd-proxy-core = { path = "../../proxy/core" } diff --git a/linkerd/app/core/src/identity.rs b/linkerd/app/core/src/identity.rs deleted file mode 100644 index 8df634dcfb..0000000000 --- a/linkerd/app/core/src/identity.rs +++ /dev/null @@ -1,719 +0,0 @@ -#![allow(irrefutable_let_patterns)] - -use futures::Future; -use linkerd_error::Result; -pub use linkerd_identity::*; -use linkerd_io as io; -pub use linkerd_proxy_identity_client as client; -use linkerd_stack::{NewService, Param, Service}; -use linkerd_tls::{ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerTls}; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - -#[derive(Copy, Clone, Debug)] -pub enum Mode { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls, - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5, -} - -#[derive(Clone, Debug)] -pub enum NewClient { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(linkerd_identity_rustls_meshtls::NewClient), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::NewClient), -} - -#[derive(Clone)] -pub enum Connect { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(linkerd_identity_rustls_meshtls::Connect), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::Connect), -} - -#[pin_project::pin_project(project = ConnectFutureProj)] -pub enum ConnectFuture { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(#[pin] linkerd_identity_rustls_meshtls::ConnectFuture), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5( - #[pin] linkerd_identity_boring_mozilla_intermediate_v5::ConnectFuture, - ), -} - -#[pin_project::pin_project(project = ClientIoProj)] -#[derive(Debug)] -pub enum ClientIo { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(#[pin] linkerd_identity_rustls_meshtls::ClientIo), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5( - #[pin] linkerd_identity_boring_mozilla_intermediate_v5::ClientIo, - ), -} - -#[derive(Clone)] -pub enum Server { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(linkerd_identity_rustls_meshtls::Server), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::Server), -} - -#[pin_project::pin_project(project = TerminateFutureProj)] -pub enum TerminateFuture { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(#[pin] linkerd_identity_rustls_meshtls::TerminateFuture), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5( - #[pin] linkerd_identity_boring_mozilla_intermediate_v5::TerminateFuture, - ), -} - -#[pin_project::pin_project(project = ServerIoProj)] -#[derive(Debug)] -pub enum ServerIo { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(#[pin] linkerd_identity_rustls_meshtls::ServerIo), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5( - #[pin] linkerd_identity_boring_mozilla_intermediate_v5::ServerIo, - ), -} - -// === impl Mode === - -#[cfg(feature = "identity-rustls-meshtls")] -impl Default for Mode { - fn default() -> Self { - Self::RustlsMeshtls - } -} - -#[cfg(all( - not(feature = "identity-rustls-meshtls"), - feature = "identity-boring-mozilla-intermediate-v5" -))] -impl Default for Mode { - fn default() -> Self { - Self::BoringMozillaIntermediateV5 - } -} - -impl Mode { - pub fn watch( - self, - identity: Name, - roots_pem: &str, - key_pkcs8: &[u8], - csr: &[u8], - ) -> Result<(creds::Store, creds::Receiver)> { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls = self { - let (store, receiver) = - linkerd_identity_rustls_meshtls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; - return Ok(( - creds::Store::RustlsMeshtls(store), - creds::Receiver::RustlsMeshtls(receiver), - )); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5 = self { - let (store, receiver) = linkerd_identity_boring_mozilla_intermediate_v5::creds::watch( - identity, roots_pem, key_pkcs8, csr, - )?; - return Ok(( - creds::Store::BoringMozillaIntermediateV5(store), - creds::Receiver::BoringMozillaIntermediateV5(receiver), - )); - } - - unreachable!() - } -} - -// === impl NewClient === - -impl NewService for NewClient { - type Service = Connect; - - fn new_service(&self, target: ClientTls) -> Self::Service { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(new_client) = self { - return Connect::RustlsMeshtls(new_client.new_service(target)); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(new_client) = self { - return Connect::BoringMozillaIntermediateV5(new_client.new_service(target)); - } - - unreachable!() - } -} - -// === impl Connect === - -impl Service for Connect -where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, -{ - type Response = ClientIo; - type Error = io::Error; - type Future = ConnectFuture; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(connect) = self { - return >::poll_ready( - connect, cx, - ); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(connect) = self { - return >::poll_ready( - connect, - cx, - ); - } - - unreachable!() - } - - #[inline] - fn call(&mut self, io: I) -> Self::Future { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(connect) = self { - return ConnectFuture::RustlsMeshtls(connect.call(io)); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(connect) = self { - return ConnectFuture::BoringMozillaIntermediateV5(connect.call(io)); - } - - unreachable!() - } -} - -// === impl ConnectFuture === - -impl Future for ConnectFuture -where - I: io::AsyncRead + io::AsyncWrite + Unpin, -{ - type Output = io::Result>; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ConnectFutureProj::RustlsMeshtls(f) = this { - let res = futures::ready!(f.poll(cx)); - return Poll::Ready(res.map(ClientIo::RustlsMeshtls)); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ConnectFutureProj::BoringMozillaIntermediateV5(f) = this { - let res = futures::ready!(f.poll(cx)); - return Poll::Ready(res.map(ClientIo::BoringMozillaIntermediateV5)); - } - - unreachable!() - } -} - -// === impl ClientIo === - -impl io::AsyncRead for ClientIo { - #[inline] - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut io::ReadBuf<'_>, - ) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ClientIoProj::RustlsMeshtls(io) = this { - return io.poll_read(cx, buf); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_read(cx, buf); - } - - unreachable!() - } -} - -impl io::AsyncWrite for ClientIo { - #[inline] - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ClientIoProj::RustlsMeshtls(io) = this { - return io.poll_flush(cx); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_flush(cx); - } - - unreachable!() - } - - #[inline] - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ClientIoProj::RustlsMeshtls(io) = this { - return io.poll_shutdown(cx); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_shutdown(cx); - } - - unreachable!() - } - - #[inline] - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ClientIoProj::RustlsMeshtls(io) = this { - return io.poll_write(cx, buf); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_write(cx, buf); - } - - unreachable!() - } - - #[inline] - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> Poll> { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ClientIoProj::RustlsMeshtls(io) = this { - return io.poll_write_vectored(cx, bufs); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ClientIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_write_vectored(cx, bufs); - } - - unreachable!() - } - - #[inline] - fn is_write_vectored(&self) -> bool { - unimplemented!() - } -} - -impl HasNegotiatedProtocol for ClientIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - unimplemented!() - } -} - -impl io::PeerAddr for ClientIo { - #[inline] - fn peer_addr(&self) -> io::Result { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(io) = self { - return io.peer_addr(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(io) = self { - return io.peer_addr(); - } - - unreachable!() - } -} - -// === impl Server === - -impl Param for Server { - fn param(&self) -> LocalId { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(srv) = self { - return srv.param(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(srv) = self { - return srv.param(); - } - - unreachable!() - } -} - -impl Server { - pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(srv) = self { - return srv.spawn_with_alpn(alpn_protocols).map(Self::RustlsMeshtls).map_err(Into::into); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(srv) = self { - return srv.spawn_with_alpn(alpn_protocols).map(Self::BoringMozillaIntermediateV5); - } - - unreachable!() - } -} - -impl Service for Server -where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, -{ - type Response = (ServerTls, ServerIo); - type Error = io::Error; - type Future = TerminateFuture; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(svc) = self { - return >::poll_ready(svc, cx); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(svc) = self { - return >::poll_ready( - svc, - cx, - ); - } - - unreachable!() - } - - #[inline] - fn call(&mut self, io: I) -> Self::Future { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(svc) = self { - return TerminateFuture::RustlsMeshtls(svc.call(io)); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(svc) = self { - return TerminateFuture::BoringMozillaIntermediateV5(svc.call(io)); - } - - unreachable!() - } -} - -// === impl TerminateFuture === - -impl Future for TerminateFuture -where - I: io::AsyncRead + io::AsyncWrite + Unpin, -{ - type Output = io::Result<(ServerTls, ServerIo)>; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let TerminateFutureProj::RustlsMeshtls(f) = this { - let res = futures::ready!(f.poll(cx)); - return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::RustlsMeshtls(io)))); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let TerminateFutureProj::BoringMozillaIntermediateV5(f) = this { - let res = futures::ready!(f.poll(cx)); - return Poll::Ready( - res.map(|(tls, io)| (tls, ServerIo::BoringMozillaIntermediateV5(io))), - ); - } - - unreachable!() - } -} - -// === impl ServerIo === - -impl io::AsyncRead for ServerIo { - #[inline] - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut io::ReadBuf<'_>, - ) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ServerIoProj::RustlsMeshtls(io) = this { - return io.poll_read(cx, buf); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_read(cx, buf); - } - - unreachable!() - } -} - -impl io::AsyncWrite for ServerIo { - #[inline] - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ServerIoProj::RustlsMeshtls(io) = this { - return io.poll_flush(cx); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_flush(cx); - } - - unreachable!() - } - - #[inline] - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ServerIoProj::RustlsMeshtls(io) = this { - return io.poll_shutdown(cx); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_shutdown(cx); - } - - unreachable!() - } - - #[inline] - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ServerIoProj::RustlsMeshtls(io) = this { - return io.poll_write(cx, buf); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_write(cx, buf); - } - - unreachable!() - } - - #[inline] - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> Poll> { - let this = self.project(); - - #[cfg(feature = "identity-rustls-meshtls")] - if let ServerIoProj::RustlsMeshtls(io) = this { - return io.poll_write_vectored(cx, bufs); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let ServerIoProj::BoringMozillaIntermediateV5(io) = this { - return io.poll_write_vectored(cx, bufs); - } - - unreachable!() - } - - #[inline] - fn is_write_vectored(&self) -> bool { - unimplemented!() - } -} - -impl HasNegotiatedProtocol for ServerIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - unimplemented!() - } -} - -impl io::PeerAddr for ServerIo { - #[inline] - fn peer_addr(&self) -> io::Result { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(io) = self { - return io.peer_addr(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(io) = self { - return io.peer_addr(); - } - - unreachable!() - } -} - -pub mod creds { - use super::*; - - pub enum Store { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(linkerd_identity_rustls_meshtls::creds::Store), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5(linkerd_identity_boring_mozilla_intermediate_v5::creds::Store), - } - - #[derive(Clone, Debug)] - pub enum Receiver { - #[cfg(feature = "identity-rustls-meshtls")] - RustlsMeshtls(linkerd_identity_rustls_meshtls::creds::Receiver), - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - BoringMozillaIntermediateV5( - linkerd_identity_boring_mozilla_intermediate_v5::creds::Receiver, - ), - } - - // === impl Store === - - impl Credentials for Store { - fn dns_name(&self) -> &Name { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(store) = self { - return store.dns_name(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(store) = self { - return store.dns_name(); - } - - unreachable!() - } - - fn gen_certificate_signing_request(&mut self) -> DerX509 { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(store) = self { - return store.gen_certificate_signing_request(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(store) = self { - return store.gen_certificate_signing_request(); - } - - unreachable!() - } - - fn set_certificate( - &mut self, - leaf: DerX509, - chain: Vec, - expiry: std::time::SystemTime, - ) -> Result<()> { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(store) = self { - return store.set_certificate(leaf, chain, expiry); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(store) = self { - return store.set_certificate(leaf, chain, expiry); - } - - unreachable!() - } - } - - // === impl Receiver === - - impl Receiver { - pub fn name(&self) -> &Name { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(receiver) = self { - return receiver.name(); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(receiver) = self { - return receiver.name(); - } - - unreachable!() - } - - pub fn new_client(&self) -> NewClient { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(receiver) = self { - return NewClient::RustlsMeshtls(receiver.new_client()); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(receiver) = self { - return NewClient::BoringMozillaIntermediateV5(receiver.new_client()); - } - - unreachable!() - } - - pub fn server(&self) -> Server { - #[cfg(feature = "identity-rustls-meshtls")] - if let Self::RustlsMeshtls(receiver) = self { - return Server::RustlsMeshtls(receiver.server()); - } - - #[cfg(feature = "identity-boring-mozilla-intermediate-v5")] - if let Self::BoringMozillaIntermediateV5(receiver) = self { - return Server::BoringMozillaIntermediateV5(receiver.server()); - } - - unreachable!() - } - } -} diff --git a/linkerd/app/core/src/lib.rs b/linkerd/app/core/src/lib.rs index 1c8faa86ed..5c418cdd5c 100644 --- a/linkerd/app/core/src/lib.rs +++ b/linkerd/app/core/src/lib.rs @@ -39,7 +39,6 @@ pub mod dns; pub mod dst; pub mod errors; pub mod http_tracing; -pub mod identity; pub mod metrics; pub mod proxy; pub mod retry; @@ -50,6 +49,12 @@ pub mod transport; pub use self::addr_match::{AddrMatch, IpMatch, NameMatch}; +pub mod identity { + pub use linkerd_identity::*; + pub use linkerd_meshtls::*; + pub use linkerd_proxy_identity_client as client; +} + pub const CANONICAL_DST_HEADER: &str = "l5d-dst-canonical"; const DEFAULT_PORT: u16 = 80; diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs index 16c0db6f4b..bf18d8db31 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs +++ b/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs @@ -90,7 +90,7 @@ where .await .map_err(|e| match e.as_io_error() { Some(ioe) => io::Error::new(ioe.kind(), ioe.to_string()), - None => io::Error::new(io::ErrorKind::Other, e), + None => io::Error::new(io::ErrorKind::Other, "unexpected TLS error"), })?; Ok(ClientIo(io)) }) diff --git a/linkerd/meshtls/Cargo.toml b/linkerd/meshtls/Cargo.toml new file mode 100644 index 0000000000..4504d90038 --- /dev/null +++ b/linkerd/meshtls/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "linkerd-meshtls" +version = "0.1.0" +authors = ["Linkerd Developers "] +license = "Apache-2.0" +edition = "2018" +publish = false + +[features] +default = ["rustls"] +rustls = ["linkerd-identity-rustls-meshtls"] +boring = ["linkerd-identity-boring-mozilla-intermediate-v5"] + + +[dependencies] +futures = { version = "0.3", default-features = false } +linkerd-error = { path = "../error" } +linkerd-identity = { path = "../identity" } +linkerd-io = { path = "../io" } +linkerd-identity-rustls-meshtls = { path = "../identity/rustls-meshtls", optional = true } +linkerd-identity-boring-mozilla-intermediate-v5 = { path = "../identity/boring-mozilla-intermediate-v5", optional = true } +linkerd-stack = { path = "../stack" } +linkerd-tls = { path = "../tls" } +pin-project = "1" diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs new file mode 100644 index 0000000000..098ef6b451 --- /dev/null +++ b/linkerd/meshtls/src/lib.rs @@ -0,0 +1,706 @@ +#![allow(irrefutable_let_patterns)] + +use futures::Future; +use linkerd_error::Result; +use linkerd_identity::{Credentials, DerX509, LocalId, Name}; +use linkerd_io as io; +use linkerd_stack::{NewService, Param, Service}; +use linkerd_tls::{ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerTls}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +#[derive(Copy, Clone, Debug)] +pub enum Mode { + #[cfg(feature = "rustls")] + Rustls, + + #[cfg(feature = "boring")] + Boring, +} + +#[derive(Clone, Debug)] +pub enum NewClient { + #[cfg(feature = "rustls")] + Rustls(linkerd_identity_rustls_meshtls::NewClient), + + #[cfg(feature = "boring")] + Boring(linkerd_identity_boring_mozilla_intermediate_v5::NewClient), +} + +#[derive(Clone)] +pub enum Connect { + #[cfg(feature = "rustls")] + Rustls(linkerd_identity_rustls_meshtls::Connect), + + #[cfg(feature = "boring")] + Boring(linkerd_identity_boring_mozilla_intermediate_v5::Connect), +} + +#[pin_project::pin_project(project = ConnectFutureProj)] +pub enum ConnectFuture { + #[cfg(feature = "rustls")] + Rustls(#[pin] linkerd_identity_rustls_meshtls::ConnectFuture), + + #[cfg(feature = "boring")] + Boring(#[pin] linkerd_identity_boring_mozilla_intermediate_v5::ConnectFuture), +} + +#[pin_project::pin_project(project = ClientIoProj)] +#[derive(Debug)] +pub enum ClientIo { + #[cfg(feature = "rustls")] + Rustls(#[pin] linkerd_identity_rustls_meshtls::ClientIo), + + #[cfg(feature = "boring")] + Boring(#[pin] linkerd_identity_boring_mozilla_intermediate_v5::ClientIo), +} + +#[derive(Clone)] +pub enum Server { + #[cfg(feature = "rustls")] + Rustls(linkerd_identity_rustls_meshtls::Server), + + #[cfg(feature = "boring")] + Boring(linkerd_identity_boring_mozilla_intermediate_v5::Server), +} + +#[pin_project::pin_project(project = TerminateFutureProj)] +pub enum TerminateFuture { + #[cfg(feature = "rustls")] + Rustls(#[pin] linkerd_identity_rustls_meshtls::TerminateFuture), + + #[cfg(feature = "boring")] + Boring(#[pin] linkerd_identity_boring_mozilla_intermediate_v5::TerminateFuture), +} + +#[pin_project::pin_project(project = ServerIoProj)] +#[derive(Debug)] +pub enum ServerIo { + #[cfg(feature = "rustls")] + Rustls(#[pin] linkerd_identity_rustls_meshtls::ServerIo), + + #[cfg(feature = "boring")] + Boring(#[pin] linkerd_identity_boring_mozilla_intermediate_v5::ServerIo), +} + +// === impl Mode === + +#[cfg(feature = "rustls")] +impl Default for Mode { + fn default() -> Self { + Self::Rustls + } +} + +#[cfg(all(not(feature = "rustls"), feature = "boring"))] +impl Default for Mode { + fn default() -> Self { + Self::Boring + } +} + +impl Mode { + pub fn watch( + self, + identity: Name, + roots_pem: &str, + key_pkcs8: &[u8], + csr: &[u8], + ) -> Result<(creds::Store, creds::Receiver)> { + #[cfg(feature = "rustls")] + if let Self::Rustls = self { + let (store, receiver) = + linkerd_identity_rustls_meshtls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; + return Ok(( + creds::Store::Rustls(store), + creds::Receiver::Rustls(receiver), + )); + } + + #[cfg(feature = "boring")] + if let Self::Boring = self { + let (store, receiver) = linkerd_identity_boring_mozilla_intermediate_v5::creds::watch( + identity, roots_pem, key_pkcs8, csr, + )?; + return Ok(( + creds::Store::Boring(store), + creds::Receiver::Boring(receiver), + )); + } + + unreachable!() + } +} + +// === impl NewClient === + +impl NewService for NewClient { + type Service = Connect; + + fn new_service(&self, target: ClientTls) -> Self::Service { + #[cfg(feature = "rustls")] + if let Self::Rustls(new_client) = self { + return Connect::Rustls(new_client.new_service(target)); + } + + #[cfg(feature = "boring")] + if let Self::Boring(new_client) = self { + return Connect::Boring(new_client.new_service(target)); + } + + unreachable!() + } +} + +// === impl Connect === + +impl Service for Connect +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, +{ + type Response = ClientIo; + type Error = io::Error; + type Future = ConnectFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + #[cfg(feature = "rustls")] + if let Self::Rustls(connect) = self { + return >::poll_ready( + connect, cx, + ); + } + + #[cfg(feature = "boring")] + if let Self::Boring(connect) = self { + return >::poll_ready( + connect, + cx, + ); + } + + unreachable!() + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + #[cfg(feature = "rustls")] + if let Self::Rustls(connect) = self { + return ConnectFuture::Rustls(connect.call(io)); + } + + #[cfg(feature = "boring")] + if let Self::Boring(connect) = self { + return ConnectFuture::Boring(connect.call(io)); + } + + unreachable!() + } +} + +// === impl ConnectFuture === + +impl Future for ConnectFuture +where + I: io::AsyncRead + io::AsyncWrite + Unpin, +{ + type Output = io::Result>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ConnectFutureProj::Rustls(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(ClientIo::Rustls)); + } + + #[cfg(feature = "boring")] + if let ConnectFutureProj::Boring(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(ClientIo::Boring)); + } + + unreachable!() + } +} + +// === impl ClientIo === + +impl io::AsyncRead for ClientIo { + #[inline] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_read(cx, buf); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_read(cx, buf); + } + + unreachable!() + } +} + +impl io::AsyncWrite for ClientIo { + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_flush(cx); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_flush(cx); + } + + unreachable!() + } + + #[inline] + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_shutdown(cx); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_shutdown(cx); + } + + unreachable!() + } + + #[inline] + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_write(cx, buf); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_write(cx, buf); + } + + unreachable!() + } + + #[inline] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + unreachable!() + } + + #[inline] + fn is_write_vectored(&self) -> bool { + unimplemented!() + } +} + +impl HasNegotiatedProtocol for ClientIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + unimplemented!() + } +} + +impl io::PeerAddr for ClientIo { + #[inline] + fn peer_addr(&self) -> io::Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(io) = self { + return io.peer_addr(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(io) = self { + return io.peer_addr(); + } + + unreachable!() + } +} + +// === impl Server === + +impl Param for Server { + fn param(&self) -> LocalId { + #[cfg(feature = "rustls")] + if let Self::Rustls(srv) = self { + return srv.param(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(srv) = self { + return srv.param(); + } + + unreachable!() + } +} + +impl Server { + pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(srv) = self { + return srv + .spawn_with_alpn(alpn_protocols) + .map(Self::Rustls) + .map_err(Into::into); + } + + #[cfg(feature = "boring")] + if let Self::Boring(srv) = self { + return srv.spawn_with_alpn(alpn_protocols).map(Self::Boring); + } + + unreachable!() + } +} + +impl Service for Server +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, +{ + type Response = (ServerTls, ServerIo); + type Error = io::Error; + type Future = TerminateFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + #[cfg(feature = "rustls")] + if let Self::Rustls(svc) = self { + return >::poll_ready(svc, cx); + } + + #[cfg(feature = "boring")] + if let Self::Boring(svc) = self { + return >::poll_ready( + svc, + cx, + ); + } + + unreachable!() + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + #[cfg(feature = "rustls")] + if let Self::Rustls(svc) = self { + return TerminateFuture::Rustls(svc.call(io)); + } + + #[cfg(feature = "boring")] + if let Self::Boring(svc) = self { + return TerminateFuture::Boring(svc.call(io)); + } + + unreachable!() + } +} + +// === impl TerminateFuture === + +impl Future for TerminateFuture +where + I: io::AsyncRead + io::AsyncWrite + Unpin, +{ + type Output = io::Result<(ServerTls, ServerIo)>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let TerminateFutureProj::Rustls(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Rustls(io)))); + } + + #[cfg(feature = "boring")] + if let TerminateFutureProj::Boring(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Boring(io)))); + } + + unreachable!() + } +} + +// === impl ServerIo === + +impl io::AsyncRead for ServerIo { + #[inline] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_read(cx, buf); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_read(cx, buf); + } + + unreachable!() + } +} + +impl io::AsyncWrite for ServerIo { + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_flush(cx); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_flush(cx); + } + + unreachable!() + } + + #[inline] + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_shutdown(cx); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_shutdown(cx); + } + + unreachable!() + } + + #[inline] + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_write(cx, buf); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_write(cx, buf); + } + + unreachable!() + } + + #[inline] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + unreachable!() + } + + #[inline] + fn is_write_vectored(&self) -> bool { + unimplemented!() + } +} + +impl HasNegotiatedProtocol for ServerIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + unimplemented!() + } +} + +impl io::PeerAddr for ServerIo { + #[inline] + fn peer_addr(&self) -> io::Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(io) = self { + return io.peer_addr(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(io) = self { + return io.peer_addr(); + } + + unreachable!() + } +} + +pub mod creds { + use super::*; + + pub enum Store { + #[cfg(feature = "rustls")] + Rustls(linkerd_identity_rustls_meshtls::creds::Store), + + #[cfg(feature = "boring")] + Boring(linkerd_identity_boring_mozilla_intermediate_v5::creds::Store), + } + + #[derive(Clone, Debug)] + pub enum Receiver { + #[cfg(feature = "rustls")] + Rustls(linkerd_identity_rustls_meshtls::creds::Receiver), + + #[cfg(feature = "boring")] + Boring(linkerd_identity_boring_mozilla_intermediate_v5::creds::Receiver), + } + + // === impl Store === + + impl Credentials for Store { + fn dns_name(&self) -> &Name { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.dns_name(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(store) = self { + return store.dns_name(); + } + + unreachable!() + } + + fn gen_certificate_signing_request(&mut self) -> DerX509 { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.gen_certificate_signing_request(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(store) = self { + return store.gen_certificate_signing_request(); + } + + unreachable!() + } + + fn set_certificate( + &mut self, + leaf: DerX509, + chain: Vec, + expiry: std::time::SystemTime, + ) -> Result<()> { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + #[cfg(feature = "boring")] + if let Self::Boring(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + unreachable!() + } + } + + // === impl Receiver === + + impl Receiver { + pub fn name(&self) -> &Name { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return receiver.name(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(receiver) = self { + return receiver.name(); + } + + unreachable!() + } + + pub fn new_client(&self) -> NewClient { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return NewClient::Rustls(receiver.new_client()); + } + + #[cfg(feature = "boring")] + if let Self::Boring(receiver) = self { + return NewClient::Boring(receiver.new_client()); + } + + unreachable!() + } + + pub fn server(&self) -> Server { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return Server::Rustls(receiver.server()); + } + + #[cfg(feature = "boring")] + if let Self::Boring(receiver) = self { + return Server::Boring(receiver.server()); + } + + unreachable!() + } + } +} diff --git a/linkerd/proxy/tap/Cargo.toml b/linkerd/proxy/tap/Cargo.toml index 19c4c2fd59..8d59bc0511 100644 --- a/linkerd/proxy/tap/Cargo.toml +++ b/linkerd/proxy/tap/Cargo.toml @@ -17,7 +17,7 @@ ipnet = "2.3" linkerd2-proxy-api = { version = "0.3", features = ["tap", "server"] } linkerd-conditional = { path = "../../conditional" } linkerd-error = { path = "../../error" } -linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls" } +linkerd-meshtls = { path = "../../meshtls" } linkerd-io = { path = "../../io" } linkerd-proxy-http = { path = "../http" } linkerd-stack = { path = "../../stack" } diff --git a/linkerd/proxy/tap/src/accept.rs b/linkerd/proxy/tap/src/accept.rs index db0863bf4f..5bcb50c862 100644 --- a/linkerd/proxy/tap/src/accept.rs +++ b/linkerd/proxy/tap/src/accept.rs @@ -3,8 +3,8 @@ use futures::future; use linkerd2_proxy_api::tap::tap_server::{Tap, TapServer}; use linkerd_conditional::Conditional; use linkerd_error::Error; -use linkerd_identity_rustls_meshtls as identity; use linkerd_io as io; +use linkerd_meshtls as meshtls; use linkerd_proxy_http::{trace, HyperServerSvc}; use linkerd_tls as tls; use std::{ @@ -24,7 +24,7 @@ pub struct AcceptPermittedClients { type Connection = ( (tls::ConditionalServerTls, T), - io::EitherIo>, tls::server::DetectIo>, + io::EitherIo>, tls::server::DetectIo>, ); pub type ServeFuture = Pin> + Send + 'static>>; From 8bb531fac227f4a5cb30b15982b6b3f905478e40 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Wed, 3 Nov 2021 01:16:57 +0000 Subject: [PATCH 17/85] Move rustls and boring modules under mtls --- Cargo.lock | 66 ++++++++-------- Cargo.toml | 6 +- linkerd/app/inbound/Cargo.toml | 2 +- linkerd/app/inbound/fuzz/Cargo.toml | 2 +- linkerd/app/inbound/src/test_util.rs | 4 +- linkerd/app/outbound/Cargo.toml | 2 +- linkerd/app/outbound/src/endpoint.rs | 9 ++- linkerd/app/outbound/src/logical.rs | 9 ++- linkerd/app/outbound/src/test_util.rs | 4 +- linkerd/io/src/boxed.rs | 6 +- linkerd/meshtls/Cargo.toml | 8 +- .../boring}/Cargo.toml | 4 +- .../boring}/src/client.rs | 0 .../boring}/src/creds.rs | 0 .../boring}/src/creds/receiver.rs | 0 .../boring}/src/creds/store.rs | 0 .../boring}/src/lib.rs | 0 .../boring}/src/server.rs | 0 .../rustls}/Cargo.toml | 2 +- .../rustls}/src/client.rs | 0 .../rustls}/src/creds.rs | 0 .../rustls}/src/creds/receiver.rs | 0 .../rustls}/src/creds/store.rs | 0 .../rustls}/src/lib.rs | 0 .../rustls}/src/server.rs | 0 .../rustls}/src/tests.rs | 0 .../rustls}/tests/tls_accept.rs | 2 +- linkerd/meshtls/src/lib.rs | 79 +++++++++++-------- 28 files changed, 113 insertions(+), 92 deletions(-) rename linkerd/{identity/boring-mozilla-intermediate-v5 => meshtls/boring}/Cargo.toml (86%) rename linkerd/{identity/boring-mozilla-intermediate-v5 => meshtls/boring}/src/client.rs (100%) rename linkerd/{identity/boring-mozilla-intermediate-v5 => meshtls/boring}/src/creds.rs (100%) rename linkerd/{identity/boring-mozilla-intermediate-v5 => meshtls/boring}/src/creds/receiver.rs (100%) rename linkerd/{identity/boring-mozilla-intermediate-v5 => meshtls/boring}/src/creds/store.rs (100%) rename linkerd/{identity/boring-mozilla-intermediate-v5 => meshtls/boring}/src/lib.rs (100%) rename linkerd/{identity/boring-mozilla-intermediate-v5 => meshtls/boring}/src/server.rs (100%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/Cargo.toml (96%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/src/client.rs (100%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/src/creds.rs (100%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/src/creds/receiver.rs (100%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/src/creds/store.rs (100%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/src/lib.rs (100%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/src/server.rs (100%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/src/tests.rs (100%) rename linkerd/{identity/rustls-meshtls => meshtls/rustls}/tests/tls_accept.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index a1b61ce3a6..7058303d11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,8 +910,8 @@ dependencies = [ "libfuzzer-sys", "linkerd-app-core", "linkerd-app-test", - "linkerd-identity-rustls-meshtls", "linkerd-io", + "linkerd-meshtls-rustls", "linkerd-server-policy", "linkerd-tonic-watch", "linkerd-tracing", @@ -966,8 +966,8 @@ dependencies = [ "linkerd-app-test", "linkerd-http-retry", "linkerd-identity", - "linkerd-identity-rustls-meshtls", "linkerd-io", + "linkerd-meshtls-rustls", "linkerd-tracing", "parking_lot", "pin-project", @@ -1167,7 +1167,36 @@ dependencies = [ ] [[package]] -name = "linkerd-identity-boring-mozilla-intermediate-v5" +name = "linkerd-io" +version = "0.1.0" +dependencies = [ + "async-trait", + "bytes", + "futures", + "linkerd-errno", + "pin-project", + "tokio", + "tokio-test", + "tokio-util", +] + +[[package]] +name = "linkerd-meshtls" +version = "0.1.0" +dependencies = [ + "futures", + "linkerd-error", + "linkerd-identity", + "linkerd-io", + "linkerd-meshtls-boring", + "linkerd-meshtls-rustls", + "linkerd-stack", + "linkerd-tls", + "pin-project", +] + +[[package]] +name = "linkerd-meshtls-boring" version = "0.1.0" dependencies = [ "boring", @@ -1186,7 +1215,7 @@ dependencies = [ ] [[package]] -name = "linkerd-identity-rustls-meshtls" +name = "linkerd-meshtls-rustls" version = "0.1.0" dependencies = [ "futures", @@ -1208,35 +1237,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "linkerd-io" -version = "0.1.0" -dependencies = [ - "async-trait", - "bytes", - "futures", - "linkerd-errno", - "pin-project", - "tokio", - "tokio-test", - "tokio-util", -] - -[[package]] -name = "linkerd-meshtls" -version = "0.1.0" -dependencies = [ - "futures", - "linkerd-error", - "linkerd-identity", - "linkerd-identity-boring-mozilla-intermediate-v5", - "linkerd-identity-rustls-meshtls", - "linkerd-io", - "linkerd-stack", - "linkerd-tls", - "pin-project", -] - [[package]] name = "linkerd-metrics" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 35e0cd99e1..ba9b808020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" exclude = [ - "linkerd/identity/boring-mozilla-intermediate-v5", + "linkerd/meshtls/boring", ] members = [ @@ -33,10 +33,10 @@ members = [ "linkerd/http-metrics", "linkerd/http-retry", "linkerd/identity", - "linkerd/identity/boring-mozilla-intermediate-v5", - "linkerd/identity/rustls-meshtls", "linkerd/io", "linkerd/meshtls", + "linkerd/meshtls/boring", + "linkerd/meshtls/rustls", "linkerd/metrics", "linkerd/opencensus", "linkerd/proxy/api-resolve", diff --git a/linkerd/app/inbound/Cargo.toml b/linkerd/app/inbound/Cargo.toml index 8c7186d2f2..e378a777f2 100644 --- a/linkerd/app/inbound/Cargo.toml +++ b/linkerd/app/inbound/Cargo.toml @@ -34,7 +34,7 @@ libfuzzer-sys = { version = "0.4.2", features = ["arbitrary-derive"] } hyper = { version = "0.14.14", features = ["http1", "http2"] } linkerd-app-test = { path = "../test" } linkerd-io = { path = "../../io", features = ["tokio-test"] } -linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls", features = ["test-util"] } +linkerd-meshtls-rustls = { path = "../../meshtls/rustls", features = ["test-util"] } linkerd-tracing = { path = "../../tracing", features = ["ansi"] } tokio = { version = "1", features = ["full", "macros"] } tokio-test = "0.4" diff --git a/linkerd/app/inbound/fuzz/Cargo.toml b/linkerd/app/inbound/fuzz/Cargo.toml index 17815e6851..eb83bcf975 100644 --- a/linkerd/app/inbound/fuzz/Cargo.toml +++ b/linkerd/app/inbound/fuzz/Cargo.toml @@ -17,7 +17,7 @@ libfuzzer-sys = { version = "0.4.2", features = ["arbitrary-derive"] } linkerd-app-core = { path = "../../core" } linkerd-app-inbound = { path = ".." } linkerd-app-test = { path = "../../test" } -linkerd-identity-rustls-meshtls = { path = "../../../identity/rustls-meshtls", features = ["test-util"] } +linkerd-meshtls-rustls = { path = "../../../meshtls/rustls", features = ["test-util"] } linkerd-tracing = { path = "../../../tracing", features = ["ansi"] } tokio = { version = "1", features = ["full"] } tracing = "0.1" diff --git a/linkerd/app/inbound/src/test_util.rs b/linkerd/app/inbound/src/test_util.rs index d77fd87568..f0c5ac482f 100644 --- a/linkerd/app/inbound/src/test_util.rs +++ b/linkerd/app/inbound/src/test_util.rs @@ -3,7 +3,7 @@ pub use futures::prelude::*; use linkerd_app_core::{ config, dns::Suffix, - drain, exp_backoff, identity, metrics, + drain, exp_backoff, metrics, proxy::{ http::{h1, h2}, tap, @@ -73,7 +73,7 @@ pub fn runtime() -> (ProxyRuntime, drain::Signal) { let (tap, _) = tap::new(); let (metrics, _) = metrics::Metrics::new(std::time::Duration::from_secs(10)); let runtime = ProxyRuntime { - identity: identity::creds::default_for_test().1, + identity: linkerd_meshtls_rustls::creds::default_for_test().1.into(), metrics: metrics.proxy, tap, span_sink: None, diff --git a/linkerd/app/outbound/Cargo.toml b/linkerd/app/outbound/Cargo.toml index 5ddf137df9..fc2a51e2a1 100644 --- a/linkerd/app/outbound/Cargo.toml +++ b/linkerd/app/outbound/Cargo.toml @@ -32,7 +32,7 @@ pin-project = "1" hyper = { version = "0.14.14", features = ["http1", "http2"] } linkerd-app-test = { path = "../test" } linkerd-io = { path = "../../io", features = ["tokio-test"] } -linkerd-identity-rustls-meshtls = { path = "../../identity/rustls-meshtls", features = ["test-util"] } +linkerd-meshtls-rustls = { path = "../../meshtls/rustls", features = ["test-util"] } linkerd-tracing = { path = "../../tracing", features = ["ansi"] } parking_lot = "0.11" tokio = { version = "1", features = ["time", "macros"] } diff --git a/linkerd/app/outbound/src/endpoint.rs b/linkerd/app/outbound/src/endpoint.rs index c80e7f3707..69cfe7e442 100644 --- a/linkerd/app/outbound/src/endpoint.rs +++ b/linkerd/app/outbound/src/endpoint.rs @@ -210,8 +210,13 @@ impl Outbound { where Self: Clone + 'static, S: svc::Service + Clone + Send + Sync + Unpin + 'static, - S::Response: - tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, + S::Response: tls::HasNegotiatedProtocol + + io::AsyncRead + + io::AsyncWrite + + Send + + Sync + + Unpin + + 'static, S::Future: Send + Unpin, I: io::AsyncRead + io::AsyncWrite + io::PeerAddr, I: fmt::Debug + Send + Sync + Unpin + 'static, diff --git a/linkerd/app/outbound/src/logical.rs b/linkerd/app/outbound/src/logical.rs index cbfd2f9fa0..732e4bfaef 100644 --- a/linkerd/app/outbound/src/logical.rs +++ b/linkerd/app/outbound/src/logical.rs @@ -119,8 +119,13 @@ impl Outbound { Self: Clone + 'static, C: Clone + Send + Sync + Unpin + 'static, C: svc::Service, - C::Response: - tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, + C::Response: tls::HasNegotiatedProtocol + + io::AsyncRead + + io::AsyncWrite + + Send + + Sync + + Unpin + + 'static, C::Future: Send + Unpin, R: Clone + Send + 'static, R: Resolve + Sync, diff --git a/linkerd/app/outbound/src/test_util.rs b/linkerd/app/outbound/src/test_util.rs index 5da08b586c..4afd5c5a88 100644 --- a/linkerd/app/outbound/src/test_util.rs +++ b/linkerd/app/outbound/src/test_util.rs @@ -1,7 +1,7 @@ use crate::Config; pub use futures::prelude::*; use linkerd_app_core::{ - config, drain, exp_backoff, identity, metrics, + config, drain, exp_backoff, metrics, proxy::{ http::{h1, h2}, tap, @@ -53,7 +53,7 @@ pub(crate) fn runtime() -> (ProxyRuntime, drain::Signal) { let (tap, _) = tap::new(); let (metrics, _) = metrics::Metrics::new(std::time::Duration::from_secs(10)); let runtime = ProxyRuntime { - identity: identity::creds::default_for_test().1, + identity: linkerd_meshtls_rustls::creds::default_for_test().1.into(), metrics: metrics.proxy, tap, span_sink: None, diff --git a/linkerd/io/src/boxed.rs b/linkerd/io/src/boxed.rs index f28f12c225..0840ea4f18 100644 --- a/linkerd/io/src/boxed.rs +++ b/linkerd/io/src/boxed.rs @@ -10,14 +10,14 @@ pub struct BoxedIo(Pin>); /// This is necessary for `BoxedIo`, as `dyn AsyncRead + AsyncWrite + PeerAddr` /// is not a valid trait object. However, it needn't be public --- it's just /// used internally. -trait Io: AsyncRead + AsyncWrite + PeerAddr + Send {} +trait Io: AsyncRead + AsyncWrite + PeerAddr + Send + Sync {} -impl Io for I where I: AsyncRead + AsyncWrite + PeerAddr + Send {} +impl Io for I where I: AsyncRead + AsyncWrite + PeerAddr + Send + Sync {} impl BoxedIo { pub fn new(io: T) -> Self where - T: AsyncRead + AsyncWrite + PeerAddr + Send + Unpin + 'static, + T: AsyncRead + AsyncWrite + PeerAddr + Send + Sync + Unpin + 'static, { BoxedIo(Box::pin(io)) } diff --git a/linkerd/meshtls/Cargo.toml b/linkerd/meshtls/Cargo.toml index 4504d90038..c676d868bf 100644 --- a/linkerd/meshtls/Cargo.toml +++ b/linkerd/meshtls/Cargo.toml @@ -8,8 +8,8 @@ publish = false [features] default = ["rustls"] -rustls = ["linkerd-identity-rustls-meshtls"] -boring = ["linkerd-identity-boring-mozilla-intermediate-v5"] +rustls = ["linkerd-meshtls-rustls"] +boring = ["linkerd-meshtls-boring"] [dependencies] @@ -17,8 +17,8 @@ futures = { version = "0.3", default-features = false } linkerd-error = { path = "../error" } linkerd-identity = { path = "../identity" } linkerd-io = { path = "../io" } -linkerd-identity-rustls-meshtls = { path = "../identity/rustls-meshtls", optional = true } -linkerd-identity-boring-mozilla-intermediate-v5 = { path = "../identity/boring-mozilla-intermediate-v5", optional = true } +linkerd-meshtls-boring = { path = "boring", optional = true } +linkerd-meshtls-rustls = { path = "rustls", optional = true } linkerd-stack = { path = "../stack" } linkerd-tls = { path = "../tls" } pin-project = "1" diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml b/linkerd/meshtls/boring/Cargo.toml similarity index 86% rename from linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml rename to linkerd/meshtls/boring/Cargo.toml index 498ce2bf84..5707d80f85 100644 --- a/linkerd/identity/boring-mozilla-intermediate-v5/Cargo.toml +++ b/linkerd/meshtls/boring/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "linkerd-identity-boring-mozilla-intermediate-v5" +name = "linkerd-meshtls-boring" version = "0.1.0" authors = ["Linkerd Developers "] license = "Apache-2.0" @@ -12,8 +12,8 @@ boring-sys = { version = "1"} futures = { version = "0.3", default-features = false } linkerd-error = { path = "../../error" } linkerd-dns-name = { path = "../../dns/name" } +linkerd-identity = { path = "../../identity" } linkerd-io = { path = "../../io" } -linkerd-identity = { path = ".." } linkerd-stack = { path = "../../stack" } linkerd-tls = { path = "../../tls" } thiserror = "1" diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs b/linkerd/meshtls/boring/src/client.rs similarity index 100% rename from linkerd/identity/boring-mozilla-intermediate-v5/src/client.rs rename to linkerd/meshtls/boring/src/client.rs diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs similarity index 100% rename from linkerd/identity/boring-mozilla-intermediate-v5/src/creds.rs rename to linkerd/meshtls/boring/src/creds.rs diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs b/linkerd/meshtls/boring/src/creds/receiver.rs similarity index 100% rename from linkerd/identity/boring-mozilla-intermediate-v5/src/creds/receiver.rs rename to linkerd/meshtls/boring/src/creds/receiver.rs diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/creds/store.rs b/linkerd/meshtls/boring/src/creds/store.rs similarity index 100% rename from linkerd/identity/boring-mozilla-intermediate-v5/src/creds/store.rs rename to linkerd/meshtls/boring/src/creds/store.rs diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs similarity index 100% rename from linkerd/identity/boring-mozilla-intermediate-v5/src/lib.rs rename to linkerd/meshtls/boring/src/lib.rs diff --git a/linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs b/linkerd/meshtls/boring/src/server.rs similarity index 100% rename from linkerd/identity/boring-mozilla-intermediate-v5/src/server.rs rename to linkerd/meshtls/boring/src/server.rs diff --git a/linkerd/identity/rustls-meshtls/Cargo.toml b/linkerd/meshtls/rustls/Cargo.toml similarity index 96% rename from linkerd/identity/rustls-meshtls/Cargo.toml rename to linkerd/meshtls/rustls/Cargo.toml index d9d3b3d472..8411eaef8f 100644 --- a/linkerd/identity/rustls-meshtls/Cargo.toml +++ b/linkerd/meshtls/rustls/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "linkerd-identity-rustls-meshtls" +name = "linkerd-meshtls-rustls" version = "0.1.0" authors = ["Linkerd Developers "] license = "Apache-2.0" diff --git a/linkerd/identity/rustls-meshtls/src/client.rs b/linkerd/meshtls/rustls/src/client.rs similarity index 100% rename from linkerd/identity/rustls-meshtls/src/client.rs rename to linkerd/meshtls/rustls/src/client.rs diff --git a/linkerd/identity/rustls-meshtls/src/creds.rs b/linkerd/meshtls/rustls/src/creds.rs similarity index 100% rename from linkerd/identity/rustls-meshtls/src/creds.rs rename to linkerd/meshtls/rustls/src/creds.rs diff --git a/linkerd/identity/rustls-meshtls/src/creds/receiver.rs b/linkerd/meshtls/rustls/src/creds/receiver.rs similarity index 100% rename from linkerd/identity/rustls-meshtls/src/creds/receiver.rs rename to linkerd/meshtls/rustls/src/creds/receiver.rs diff --git a/linkerd/identity/rustls-meshtls/src/creds/store.rs b/linkerd/meshtls/rustls/src/creds/store.rs similarity index 100% rename from linkerd/identity/rustls-meshtls/src/creds/store.rs rename to linkerd/meshtls/rustls/src/creds/store.rs diff --git a/linkerd/identity/rustls-meshtls/src/lib.rs b/linkerd/meshtls/rustls/src/lib.rs similarity index 100% rename from linkerd/identity/rustls-meshtls/src/lib.rs rename to linkerd/meshtls/rustls/src/lib.rs diff --git a/linkerd/identity/rustls-meshtls/src/server.rs b/linkerd/meshtls/rustls/src/server.rs similarity index 100% rename from linkerd/identity/rustls-meshtls/src/server.rs rename to linkerd/meshtls/rustls/src/server.rs diff --git a/linkerd/identity/rustls-meshtls/src/tests.rs b/linkerd/meshtls/rustls/src/tests.rs similarity index 100% rename from linkerd/identity/rustls-meshtls/src/tests.rs rename to linkerd/meshtls/rustls/src/tests.rs diff --git a/linkerd/identity/rustls-meshtls/tests/tls_accept.rs b/linkerd/meshtls/rustls/tests/tls_accept.rs similarity index 99% rename from linkerd/identity/rustls-meshtls/tests/tls_accept.rs rename to linkerd/meshtls/rustls/tests/tls_accept.rs index 9ac9412d63..6ba59346ba 100644 --- a/linkerd/identity/rustls-meshtls/tests/tls_accept.rs +++ b/linkerd/meshtls/rustls/tests/tls_accept.rs @@ -8,8 +8,8 @@ use futures::prelude::*; use linkerd_conditional::Conditional; use linkerd_error::Infallible; -use linkerd_identity_rustls_meshtls::{self as identity, Credentials, DerX509, Name}; use linkerd_io::{self as io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use linkerd_meshtls_rustls::{self as identity, Credentials, DerX509, Name}; use linkerd_proxy_transport::{ addrs::*, listen::{Addrs, Bind, BindTcp}, diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs index 098ef6b451..055b7c732f 100644 --- a/linkerd/meshtls/src/lib.rs +++ b/linkerd/meshtls/src/lib.rs @@ -11,6 +11,12 @@ use std::{ task::{Context, Poll}, }; +#[cfg(feature = "boring")] +pub use linkerd_meshtls_boring as boring; + +#[cfg(feature = "rustls")] +pub use linkerd_meshtls_rustls as rustls; + #[derive(Copy, Clone, Debug)] pub enum Mode { #[cfg(feature = "rustls")] @@ -23,66 +29,66 @@ pub enum Mode { #[derive(Clone, Debug)] pub enum NewClient { #[cfg(feature = "rustls")] - Rustls(linkerd_identity_rustls_meshtls::NewClient), + Rustls(linkerd_meshtls_rustls::NewClient), #[cfg(feature = "boring")] - Boring(linkerd_identity_boring_mozilla_intermediate_v5::NewClient), + Boring(linkerd_meshtls_boring::NewClient), } #[derive(Clone)] pub enum Connect { #[cfg(feature = "rustls")] - Rustls(linkerd_identity_rustls_meshtls::Connect), + Rustls(linkerd_meshtls_rustls::Connect), #[cfg(feature = "boring")] - Boring(linkerd_identity_boring_mozilla_intermediate_v5::Connect), + Boring(linkerd_meshtls_boring::Connect), } #[pin_project::pin_project(project = ConnectFutureProj)] pub enum ConnectFuture { #[cfg(feature = "rustls")] - Rustls(#[pin] linkerd_identity_rustls_meshtls::ConnectFuture), + Rustls(#[pin] linkerd_meshtls_rustls::ConnectFuture), #[cfg(feature = "boring")] - Boring(#[pin] linkerd_identity_boring_mozilla_intermediate_v5::ConnectFuture), + Boring(#[pin] linkerd_meshtls_boring::ConnectFuture), } #[pin_project::pin_project(project = ClientIoProj)] #[derive(Debug)] pub enum ClientIo { #[cfg(feature = "rustls")] - Rustls(#[pin] linkerd_identity_rustls_meshtls::ClientIo), + Rustls(#[pin] linkerd_meshtls_rustls::ClientIo), #[cfg(feature = "boring")] - Boring(#[pin] linkerd_identity_boring_mozilla_intermediate_v5::ClientIo), + Boring(#[pin] linkerd_meshtls_boring::ClientIo), } #[derive(Clone)] pub enum Server { #[cfg(feature = "rustls")] - Rustls(linkerd_identity_rustls_meshtls::Server), + Rustls(linkerd_meshtls_rustls::Server), #[cfg(feature = "boring")] - Boring(linkerd_identity_boring_mozilla_intermediate_v5::Server), + Boring(linkerd_meshtls_boring::Server), } #[pin_project::pin_project(project = TerminateFutureProj)] pub enum TerminateFuture { #[cfg(feature = "rustls")] - Rustls(#[pin] linkerd_identity_rustls_meshtls::TerminateFuture), + Rustls(#[pin] linkerd_meshtls_rustls::TerminateFuture), #[cfg(feature = "boring")] - Boring(#[pin] linkerd_identity_boring_mozilla_intermediate_v5::TerminateFuture), + Boring(#[pin] linkerd_meshtls_boring::TerminateFuture), } #[pin_project::pin_project(project = ServerIoProj)] #[derive(Debug)] pub enum ServerIo { #[cfg(feature = "rustls")] - Rustls(#[pin] linkerd_identity_rustls_meshtls::ServerIo), + Rustls(#[pin] linkerd_meshtls_rustls::ServerIo), #[cfg(feature = "boring")] - Boring(#[pin] linkerd_identity_boring_mozilla_intermediate_v5::ServerIo), + Boring(#[pin] linkerd_meshtls_boring::ServerIo), } // === impl Mode === @@ -112,7 +118,7 @@ impl Mode { #[cfg(feature = "rustls")] if let Self::Rustls = self { let (store, receiver) = - linkerd_identity_rustls_meshtls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; + linkerd_meshtls_rustls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; return Ok(( creds::Store::Rustls(store), creds::Receiver::Rustls(receiver), @@ -121,9 +127,8 @@ impl Mode { #[cfg(feature = "boring")] if let Self::Boring = self { - let (store, receiver) = linkerd_identity_boring_mozilla_intermediate_v5::creds::watch( - identity, roots_pem, key_pkcs8, csr, - )?; + let (store, receiver) = + linkerd_meshtls_boring::creds::watch(identity, roots_pem, key_pkcs8, csr)?; return Ok(( creds::Store::Boring(store), creds::Receiver::Boring(receiver), @@ -167,17 +172,12 @@ where fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { #[cfg(feature = "rustls")] if let Self::Rustls(connect) = self { - return >::poll_ready( - connect, cx, - ); + return >::poll_ready(connect, cx); } #[cfg(feature = "boring")] if let Self::Boring(connect) = self { - return >::poll_ready( - connect, - cx, - ); + return >::poll_ready(connect, cx); } unreachable!() @@ -402,15 +402,12 @@ where fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { #[cfg(feature = "rustls")] if let Self::Rustls(svc) = self { - return >::poll_ready(svc, cx); + return >::poll_ready(svc, cx); } #[cfg(feature = "boring")] if let Self::Boring(svc) = self { - return >::poll_ready( - svc, - cx, - ); + return >::poll_ready(svc, cx); } unreachable!() @@ -592,19 +589,19 @@ pub mod creds { pub enum Store { #[cfg(feature = "rustls")] - Rustls(linkerd_identity_rustls_meshtls::creds::Store), + Rustls(linkerd_meshtls_rustls::creds::Store), #[cfg(feature = "boring")] - Boring(linkerd_identity_boring_mozilla_intermediate_v5::creds::Store), + Boring(linkerd_meshtls_boring::creds::Store), } #[derive(Clone, Debug)] pub enum Receiver { #[cfg(feature = "rustls")] - Rustls(linkerd_identity_rustls_meshtls::creds::Receiver), + Rustls(linkerd_meshtls_rustls::creds::Receiver), #[cfg(feature = "boring")] - Boring(linkerd_identity_boring_mozilla_intermediate_v5::creds::Receiver), + Boring(linkerd_meshtls_boring::creds::Receiver), } // === impl Store === @@ -660,6 +657,20 @@ pub mod creds { // === impl Receiver === + #[cfg(feature = "boring")] + impl From for Receiver { + fn from(rx: boring::creds::Receiver) -> Self { + Self::Boring(rx) + } + } + + #[cfg(feature = "rustls")] + impl From for Receiver { + fn from(rx: rustls::creds::Receiver) -> Self { + Self::Rustls(rx) + } + } + impl Receiver { pub fn name(&self) -> &Name { #[cfg(feature = "rustls")] From bc38ee34118c28c12cdb438ef2e1de5821735171 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Wed, 3 Nov 2021 01:43:23 +0000 Subject: [PATCH 18/85] Split meshtls crate into multiple modules --- linkerd/meshtls/boring/src/lib.rs | 1 - linkerd/meshtls/rustls/src/lib.rs | 1 - linkerd/meshtls/rustls/tests/tls_accept.rs | 29 +- linkerd/meshtls/src/client.rs | 266 ++++++++ linkerd/meshtls/src/creds.rs | 137 +++++ linkerd/meshtls/src/lib.rs | 666 +-------------------- linkerd/meshtls/src/server.rs | 277 +++++++++ 7 files changed, 703 insertions(+), 674 deletions(-) create mode 100644 linkerd/meshtls/src/client.rs create mode 100644 linkerd/meshtls/src/creds.rs create mode 100644 linkerd/meshtls/src/server.rs diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index 0f37758c91..bcc6cc0461 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -9,4 +9,3 @@ pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, server::{Server, ServerIo, TerminateFuture}, }; -pub use linkerd_identity::*; diff --git a/linkerd/meshtls/rustls/src/lib.rs b/linkerd/meshtls/rustls/src/lib.rs index 0f37758c91..bcc6cc0461 100644 --- a/linkerd/meshtls/rustls/src/lib.rs +++ b/linkerd/meshtls/rustls/src/lib.rs @@ -9,4 +9,3 @@ pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, server::{Server, ServerIo, TerminateFuture}, }; -pub use linkerd_identity::*; diff --git a/linkerd/meshtls/rustls/tests/tls_accept.rs b/linkerd/meshtls/rustls/tests/tls_accept.rs index 6ba59346ba..9357688ee1 100644 --- a/linkerd/meshtls/rustls/tests/tls_accept.rs +++ b/linkerd/meshtls/rustls/tests/tls_accept.rs @@ -8,8 +8,9 @@ use futures::prelude::*; use linkerd_conditional::Conditional; use linkerd_error::Infallible; +use linkerd_identity::{Credentials, DerX509, Name}; use linkerd_io::{self as io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use linkerd_meshtls_rustls::{self as identity, Credentials, DerX509, Name}; +use linkerd_meshtls_rustls as meshtls; use linkerd_proxy_transport::{ addrs::*, listen::{Addrs, Bind, BindTcp}, @@ -28,18 +29,12 @@ use tracing::instrument::Instrument; type ServerConn = ( (tls::ConditionalServerTls, T), - io::EitherIo>, tls::server::DetectIo>, + io::EitherIo>, tls::server::DetectIo>, ); -fn load( - ent: &test_util::Entity, -) -> ( - identity::creds::Store, - identity::NewClient, - identity::Server, -) { +fn load(ent: &test_util::Entity) -> (meshtls::creds::Store, meshtls::NewClient, meshtls::Server) { let roots_pem = std::str::from_utf8(ent.trust_anchors).expect("valid PEM"); - let (mut store, rx) = identity::creds::watch( + let (mut store, rx) = meshtls::creds::watch( ent.name.parse().unwrap(), roots_pem, ent.key, @@ -152,19 +147,19 @@ struct Transported { #[derive(Clone)] struct ServerParams { - identity: identity::Server, + identity: meshtls::Server, } -type ClientIo = io::EitherIo, identity::ClientIo>>; +type ClientIo = io::EitherIo, meshtls::ClientIo>>; /// Runs a test for a single TCP connection. `client` processes the connection /// on the client side and `server` processes the connection on the server /// side. async fn run_test( - client_tls: identity::NewClient, + client_tls: meshtls::NewClient, client_server_id: Conditional, client: C, - server_id: identity::Server, + server_id: meshtls::Server, server: S, ) -> ( Transported, @@ -187,7 +182,7 @@ where // Saves the result of every connection. let (sender, receiver) = mpsc::channel::>(); - let detect = tls::NewDetectTls::::new( + let detect = tls::NewDetectTls::::new( ServerParams { identity: server_id, }, @@ -375,8 +370,8 @@ impl ExtractParam for ServerParams { } } -impl ExtractParam for ServerParams { - fn extract_param(&self, _: &T) -> identity::Server { +impl ExtractParam for ServerParams { + fn extract_param(&self, _: &T) -> meshtls::Server { self.identity.clone() } } diff --git a/linkerd/meshtls/src/client.rs b/linkerd/meshtls/src/client.rs new file mode 100644 index 0000000000..1a07d6ea43 --- /dev/null +++ b/linkerd/meshtls/src/client.rs @@ -0,0 +1,266 @@ +use linkerd_io as io; +use linkerd_stack::{NewService, Service}; +use linkerd_tls::{ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +#[cfg(feature = "boring")] +use crate::boring; + +#[cfg(feature = "rustls")] +use crate::rustls; + +#[derive(Clone, Debug)] +pub enum NewClient { + #[cfg(feature = "rustls")] + Rustls(rustls::NewClient), + + #[cfg(feature = "boring")] + Boring(boring::NewClient), +} + +#[derive(Clone)] +pub enum Connect { + #[cfg(feature = "rustls")] + Rustls(rustls::Connect), + + #[cfg(feature = "boring")] + Boring(boring::Connect), +} + +#[pin_project::pin_project(project = ConnectFutureProj)] +pub enum ConnectFuture { + #[cfg(feature = "rustls")] + Rustls(#[pin] rustls::ConnectFuture), + + #[cfg(feature = "boring")] + Boring(#[pin] boring::ConnectFuture), +} + +#[pin_project::pin_project(project = ClientIoProj)] +#[derive(Debug)] +pub enum ClientIo { + #[cfg(feature = "rustls")] + Rustls(#[pin] rustls::ClientIo), + + #[cfg(feature = "boring")] + Boring(#[pin] boring::ClientIo), +} + +// === impl NewClient === + +impl NewService for NewClient { + type Service = Connect; + + fn new_service(&self, target: ClientTls) -> Self::Service { + #[cfg(feature = "rustls")] + if let Self::Rustls(new_client) = self { + return Connect::Rustls(new_client.new_service(target)); + } + + #[cfg(feature = "boring")] + if let Self::Boring(new_client) = self { + return Connect::Boring(new_client.new_service(target)); + } + + unreachable!() + } +} + +// === impl Connect === + +impl Service for Connect +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, +{ + type Response = ClientIo; + type Error = io::Error; + type Future = ConnectFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + #[cfg(feature = "rustls")] + if let Self::Rustls(connect) = self { + return >::poll_ready(connect, cx); + } + + #[cfg(feature = "boring")] + if let Self::Boring(connect) = self { + return >::poll_ready(connect, cx); + } + + unreachable!() + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + #[cfg(feature = "rustls")] + if let Self::Rustls(connect) = self { + return ConnectFuture::Rustls(connect.call(io)); + } + + #[cfg(feature = "boring")] + if let Self::Boring(connect) = self { + return ConnectFuture::Boring(connect.call(io)); + } + + unreachable!() + } +} + +// === impl ConnectFuture === + +impl Future for ConnectFuture +where + I: io::AsyncRead + io::AsyncWrite + Unpin, +{ + type Output = io::Result>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ConnectFutureProj::Rustls(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(ClientIo::Rustls)); + } + + #[cfg(feature = "boring")] + if let ConnectFutureProj::Boring(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(ClientIo::Boring)); + } + + unreachable!() + } +} + +// === impl ClientIo === + +impl io::AsyncRead for ClientIo { + #[inline] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_read(cx, buf); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_read(cx, buf); + } + + unreachable!() + } +} + +impl io::AsyncWrite for ClientIo { + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_flush(cx); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_flush(cx); + } + + unreachable!() + } + + #[inline] + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_shutdown(cx); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_shutdown(cx); + } + + unreachable!() + } + + #[inline] + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_write(cx, buf); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_write(cx, buf); + } + + unreachable!() + } + + #[inline] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + #[cfg(feature = "boring")] + if let ClientIoProj::Boring(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + unreachable!() + } + + #[inline] + fn is_write_vectored(&self) -> bool { + unimplemented!() + } +} + +impl HasNegotiatedProtocol for ClientIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + unimplemented!() + } +} + +impl io::PeerAddr for ClientIo { + #[inline] + fn peer_addr(&self) -> io::Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(io) = self { + return io.peer_addr(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(io) = self { + return io.peer_addr(); + } + + unreachable!() + } +} diff --git a/linkerd/meshtls/src/creds.rs b/linkerd/meshtls/src/creds.rs new file mode 100644 index 0000000000..16f416571a --- /dev/null +++ b/linkerd/meshtls/src/creds.rs @@ -0,0 +1,137 @@ +use crate::{NewClient, Server}; +use linkerd_error::Result; +use linkerd_identity::{Credentials, DerX509, Name}; + +#[cfg(feature = "boring")] +pub use crate::boring; + +#[cfg(feature = "rustls")] +pub use crate::rustls; + +pub enum Store { + #[cfg(feature = "rustls")] + Rustls(rustls::creds::Store), + + #[cfg(feature = "boring")] + Boring(boring::creds::Store), +} + +#[derive(Clone, Debug)] +pub enum Receiver { + #[cfg(feature = "rustls")] + Rustls(rustls::creds::Receiver), + + #[cfg(feature = "boring")] + Boring(boring::creds::Receiver), +} + +// === impl Store === + +impl Credentials for Store { + fn dns_name(&self) -> &Name { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.dns_name(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(store) = self { + return store.dns_name(); + } + + unreachable!() + } + + fn gen_certificate_signing_request(&mut self) -> DerX509 { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.gen_certificate_signing_request(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(store) = self { + return store.gen_certificate_signing_request(); + } + + unreachable!() + } + + fn set_certificate( + &mut self, + leaf: DerX509, + chain: Vec, + expiry: std::time::SystemTime, + ) -> Result<()> { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + #[cfg(feature = "boring")] + if let Self::Boring(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + unreachable!() + } +} + +// === impl Receiver === + +#[cfg(feature = "boring")] +impl From for Receiver { + fn from(rx: boring::creds::Receiver) -> Self { + Self::Boring(rx) + } +} + +#[cfg(feature = "rustls")] +impl From for Receiver { + fn from(rx: rustls::creds::Receiver) -> Self { + Self::Rustls(rx) + } +} + +impl Receiver { + pub fn name(&self) -> &Name { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return receiver.name(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(receiver) = self { + return receiver.name(); + } + + unreachable!() + } + + pub fn new_client(&self) -> NewClient { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return NewClient::Rustls(receiver.new_client()); + } + + #[cfg(feature = "boring")] + if let Self::Boring(receiver) = self { + return NewClient::Boring(receiver.new_client()); + } + + unreachable!() + } + + pub fn server(&self) -> Server { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return Server::Rustls(receiver.server()); + } + + #[cfg(feature = "boring")] + if let Self::Boring(receiver) = self { + return Server::Boring(receiver.server()); + } + + unreachable!() + } +} diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs index 055b7c732f..eeff197e35 100644 --- a/linkerd/meshtls/src/lib.rs +++ b/linkerd/meshtls/src/lib.rs @@ -1,15 +1,15 @@ #![allow(irrefutable_let_patterns)] -use futures::Future; -use linkerd_error::Result; -use linkerd_identity::{Credentials, DerX509, LocalId, Name}; -use linkerd_io as io; -use linkerd_stack::{NewService, Param, Service}; -use linkerd_tls::{ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerTls}; -use std::{ - pin::Pin, - task::{Context, Poll}, +mod client; +pub mod creds; +mod server; + +pub use self::{ + client::{ClientIo, Connect, ConnectFuture, NewClient}, + server::{Server, ServerIo, TerminateFuture}, }; +use linkerd_error::Result; +use linkerd_identity::Name; #[cfg(feature = "boring")] pub use linkerd_meshtls_boring as boring; @@ -26,71 +26,6 @@ pub enum Mode { Boring, } -#[derive(Clone, Debug)] -pub enum NewClient { - #[cfg(feature = "rustls")] - Rustls(linkerd_meshtls_rustls::NewClient), - - #[cfg(feature = "boring")] - Boring(linkerd_meshtls_boring::NewClient), -} - -#[derive(Clone)] -pub enum Connect { - #[cfg(feature = "rustls")] - Rustls(linkerd_meshtls_rustls::Connect), - - #[cfg(feature = "boring")] - Boring(linkerd_meshtls_boring::Connect), -} - -#[pin_project::pin_project(project = ConnectFutureProj)] -pub enum ConnectFuture { - #[cfg(feature = "rustls")] - Rustls(#[pin] linkerd_meshtls_rustls::ConnectFuture), - - #[cfg(feature = "boring")] - Boring(#[pin] linkerd_meshtls_boring::ConnectFuture), -} - -#[pin_project::pin_project(project = ClientIoProj)] -#[derive(Debug)] -pub enum ClientIo { - #[cfg(feature = "rustls")] - Rustls(#[pin] linkerd_meshtls_rustls::ClientIo), - - #[cfg(feature = "boring")] - Boring(#[pin] linkerd_meshtls_boring::ClientIo), -} - -#[derive(Clone)] -pub enum Server { - #[cfg(feature = "rustls")] - Rustls(linkerd_meshtls_rustls::Server), - - #[cfg(feature = "boring")] - Boring(linkerd_meshtls_boring::Server), -} - -#[pin_project::pin_project(project = TerminateFutureProj)] -pub enum TerminateFuture { - #[cfg(feature = "rustls")] - Rustls(#[pin] linkerd_meshtls_rustls::TerminateFuture), - - #[cfg(feature = "boring")] - Boring(#[pin] linkerd_meshtls_boring::TerminateFuture), -} - -#[pin_project::pin_project(project = ServerIoProj)] -#[derive(Debug)] -pub enum ServerIo { - #[cfg(feature = "rustls")] - Rustls(#[pin] linkerd_meshtls_rustls::ServerIo), - - #[cfg(feature = "boring")] - Boring(#[pin] linkerd_meshtls_boring::ServerIo), -} - // === impl Mode === #[cfg(feature = "rustls")] @@ -117,8 +52,7 @@ impl Mode { ) -> Result<(creds::Store, creds::Receiver)> { #[cfg(feature = "rustls")] if let Self::Rustls = self { - let (store, receiver) = - linkerd_meshtls_rustls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; + let (store, receiver) = rustls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; return Ok(( creds::Store::Rustls(store), creds::Receiver::Rustls(receiver), @@ -127,8 +61,7 @@ impl Mode { #[cfg(feature = "boring")] if let Self::Boring = self { - let (store, receiver) = - linkerd_meshtls_boring::creds::watch(identity, roots_pem, key_pkcs8, csr)?; + let (store, receiver) = boring::creds::watch(identity, roots_pem, key_pkcs8, csr)?; return Ok(( creds::Store::Boring(store), creds::Receiver::Boring(receiver), @@ -138,580 +71,3 @@ impl Mode { unreachable!() } } - -// === impl NewClient === - -impl NewService for NewClient { - type Service = Connect; - - fn new_service(&self, target: ClientTls) -> Self::Service { - #[cfg(feature = "rustls")] - if let Self::Rustls(new_client) = self { - return Connect::Rustls(new_client.new_service(target)); - } - - #[cfg(feature = "boring")] - if let Self::Boring(new_client) = self { - return Connect::Boring(new_client.new_service(target)); - } - - unreachable!() - } -} - -// === impl Connect === - -impl Service for Connect -where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, -{ - type Response = ClientIo; - type Error = io::Error; - type Future = ConnectFuture; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - #[cfg(feature = "rustls")] - if let Self::Rustls(connect) = self { - return >::poll_ready(connect, cx); - } - - #[cfg(feature = "boring")] - if let Self::Boring(connect) = self { - return >::poll_ready(connect, cx); - } - - unreachable!() - } - - #[inline] - fn call(&mut self, io: I) -> Self::Future { - #[cfg(feature = "rustls")] - if let Self::Rustls(connect) = self { - return ConnectFuture::Rustls(connect.call(io)); - } - - #[cfg(feature = "boring")] - if let Self::Boring(connect) = self { - return ConnectFuture::Boring(connect.call(io)); - } - - unreachable!() - } -} - -// === impl ConnectFuture === - -impl Future for ConnectFuture -where - I: io::AsyncRead + io::AsyncWrite + Unpin, -{ - type Output = io::Result>; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ConnectFutureProj::Rustls(f) = this { - let res = futures::ready!(f.poll(cx)); - return Poll::Ready(res.map(ClientIo::Rustls)); - } - - #[cfg(feature = "boring")] - if let ConnectFutureProj::Boring(f) = this { - let res = futures::ready!(f.poll(cx)); - return Poll::Ready(res.map(ClientIo::Boring)); - } - - unreachable!() - } -} - -// === impl ClientIo === - -impl io::AsyncRead for ClientIo { - #[inline] - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut io::ReadBuf<'_>, - ) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ClientIoProj::Rustls(io) = this { - return io.poll_read(cx, buf); - } - - #[cfg(feature = "boring")] - if let ClientIoProj::Boring(io) = this { - return io.poll_read(cx, buf); - } - - unreachable!() - } -} - -impl io::AsyncWrite for ClientIo { - #[inline] - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ClientIoProj::Rustls(io) = this { - return io.poll_flush(cx); - } - - #[cfg(feature = "boring")] - if let ClientIoProj::Boring(io) = this { - return io.poll_flush(cx); - } - - unreachable!() - } - - #[inline] - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ClientIoProj::Rustls(io) = this { - return io.poll_shutdown(cx); - } - - #[cfg(feature = "boring")] - if let ClientIoProj::Boring(io) = this { - return io.poll_shutdown(cx); - } - - unreachable!() - } - - #[inline] - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ClientIoProj::Rustls(io) = this { - return io.poll_write(cx, buf); - } - - #[cfg(feature = "boring")] - if let ClientIoProj::Boring(io) = this { - return io.poll_write(cx, buf); - } - - unreachable!() - } - - #[inline] - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> Poll> { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ClientIoProj::Rustls(io) = this { - return io.poll_write_vectored(cx, bufs); - } - - #[cfg(feature = "boring")] - if let ClientIoProj::Boring(io) = this { - return io.poll_write_vectored(cx, bufs); - } - - unreachable!() - } - - #[inline] - fn is_write_vectored(&self) -> bool { - unimplemented!() - } -} - -impl HasNegotiatedProtocol for ClientIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - unimplemented!() - } -} - -impl io::PeerAddr for ClientIo { - #[inline] - fn peer_addr(&self) -> io::Result { - #[cfg(feature = "rustls")] - if let Self::Rustls(io) = self { - return io.peer_addr(); - } - - #[cfg(feature = "boring")] - if let Self::Boring(io) = self { - return io.peer_addr(); - } - - unreachable!() - } -} - -// === impl Server === - -impl Param for Server { - fn param(&self) -> LocalId { - #[cfg(feature = "rustls")] - if let Self::Rustls(srv) = self { - return srv.param(); - } - - #[cfg(feature = "boring")] - if let Self::Boring(srv) = self { - return srv.param(); - } - - unreachable!() - } -} - -impl Server { - pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { - #[cfg(feature = "rustls")] - if let Self::Rustls(srv) = self { - return srv - .spawn_with_alpn(alpn_protocols) - .map(Self::Rustls) - .map_err(Into::into); - } - - #[cfg(feature = "boring")] - if let Self::Boring(srv) = self { - return srv.spawn_with_alpn(alpn_protocols).map(Self::Boring); - } - - unreachable!() - } -} - -impl Service for Server -where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, -{ - type Response = (ServerTls, ServerIo); - type Error = io::Error; - type Future = TerminateFuture; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - #[cfg(feature = "rustls")] - if let Self::Rustls(svc) = self { - return >::poll_ready(svc, cx); - } - - #[cfg(feature = "boring")] - if let Self::Boring(svc) = self { - return >::poll_ready(svc, cx); - } - - unreachable!() - } - - #[inline] - fn call(&mut self, io: I) -> Self::Future { - #[cfg(feature = "rustls")] - if let Self::Rustls(svc) = self { - return TerminateFuture::Rustls(svc.call(io)); - } - - #[cfg(feature = "boring")] - if let Self::Boring(svc) = self { - return TerminateFuture::Boring(svc.call(io)); - } - - unreachable!() - } -} - -// === impl TerminateFuture === - -impl Future for TerminateFuture -where - I: io::AsyncRead + io::AsyncWrite + Unpin, -{ - type Output = io::Result<(ServerTls, ServerIo)>; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let TerminateFutureProj::Rustls(f) = this { - let res = futures::ready!(f.poll(cx)); - return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Rustls(io)))); - } - - #[cfg(feature = "boring")] - if let TerminateFutureProj::Boring(f) = this { - let res = futures::ready!(f.poll(cx)); - return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Boring(io)))); - } - - unreachable!() - } -} - -// === impl ServerIo === - -impl io::AsyncRead for ServerIo { - #[inline] - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut io::ReadBuf<'_>, - ) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ServerIoProj::Rustls(io) = this { - return io.poll_read(cx, buf); - } - - #[cfg(feature = "boring")] - if let ServerIoProj::Boring(io) = this { - return io.poll_read(cx, buf); - } - - unreachable!() - } -} - -impl io::AsyncWrite for ServerIo { - #[inline] - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ServerIoProj::Rustls(io) = this { - return io.poll_flush(cx); - } - - #[cfg(feature = "boring")] - if let ServerIoProj::Boring(io) = this { - return io.poll_flush(cx); - } - - unreachable!() - } - - #[inline] - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ServerIoProj::Rustls(io) = this { - return io.poll_shutdown(cx); - } - - #[cfg(feature = "boring")] - if let ServerIoProj::Boring(io) = this { - return io.poll_shutdown(cx); - } - - unreachable!() - } - - #[inline] - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ServerIoProj::Rustls(io) = this { - return io.poll_write(cx, buf); - } - - #[cfg(feature = "boring")] - if let ServerIoProj::Boring(io) = this { - return io.poll_write(cx, buf); - } - - unreachable!() - } - - #[inline] - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> Poll> { - let this = self.project(); - - #[cfg(feature = "rustls")] - if let ServerIoProj::Rustls(io) = this { - return io.poll_write_vectored(cx, bufs); - } - - #[cfg(feature = "boring")] - if let ServerIoProj::Boring(io) = this { - return io.poll_write_vectored(cx, bufs); - } - - unreachable!() - } - - #[inline] - fn is_write_vectored(&self) -> bool { - unimplemented!() - } -} - -impl HasNegotiatedProtocol for ServerIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - unimplemented!() - } -} - -impl io::PeerAddr for ServerIo { - #[inline] - fn peer_addr(&self) -> io::Result { - #[cfg(feature = "rustls")] - if let Self::Rustls(io) = self { - return io.peer_addr(); - } - - #[cfg(feature = "boring")] - if let Self::Boring(io) = self { - return io.peer_addr(); - } - - unreachable!() - } -} - -pub mod creds { - use super::*; - - pub enum Store { - #[cfg(feature = "rustls")] - Rustls(linkerd_meshtls_rustls::creds::Store), - - #[cfg(feature = "boring")] - Boring(linkerd_meshtls_boring::creds::Store), - } - - #[derive(Clone, Debug)] - pub enum Receiver { - #[cfg(feature = "rustls")] - Rustls(linkerd_meshtls_rustls::creds::Receiver), - - #[cfg(feature = "boring")] - Boring(linkerd_meshtls_boring::creds::Receiver), - } - - // === impl Store === - - impl Credentials for Store { - fn dns_name(&self) -> &Name { - #[cfg(feature = "rustls")] - if let Self::Rustls(store) = self { - return store.dns_name(); - } - - #[cfg(feature = "boring")] - if let Self::Boring(store) = self { - return store.dns_name(); - } - - unreachable!() - } - - fn gen_certificate_signing_request(&mut self) -> DerX509 { - #[cfg(feature = "rustls")] - if let Self::Rustls(store) = self { - return store.gen_certificate_signing_request(); - } - - #[cfg(feature = "boring")] - if let Self::Boring(store) = self { - return store.gen_certificate_signing_request(); - } - - unreachable!() - } - - fn set_certificate( - &mut self, - leaf: DerX509, - chain: Vec, - expiry: std::time::SystemTime, - ) -> Result<()> { - #[cfg(feature = "rustls")] - if let Self::Rustls(store) = self { - return store.set_certificate(leaf, chain, expiry); - } - - #[cfg(feature = "boring")] - if let Self::Boring(store) = self { - return store.set_certificate(leaf, chain, expiry); - } - - unreachable!() - } - } - - // === impl Receiver === - - #[cfg(feature = "boring")] - impl From for Receiver { - fn from(rx: boring::creds::Receiver) -> Self { - Self::Boring(rx) - } - } - - #[cfg(feature = "rustls")] - impl From for Receiver { - fn from(rx: rustls::creds::Receiver) -> Self { - Self::Rustls(rx) - } - } - - impl Receiver { - pub fn name(&self) -> &Name { - #[cfg(feature = "rustls")] - if let Self::Rustls(receiver) = self { - return receiver.name(); - } - - #[cfg(feature = "boring")] - if let Self::Boring(receiver) = self { - return receiver.name(); - } - - unreachable!() - } - - pub fn new_client(&self) -> NewClient { - #[cfg(feature = "rustls")] - if let Self::Rustls(receiver) = self { - return NewClient::Rustls(receiver.new_client()); - } - - #[cfg(feature = "boring")] - if let Self::Boring(receiver) = self { - return NewClient::Boring(receiver.new_client()); - } - - unreachable!() - } - - pub fn server(&self) -> Server { - #[cfg(feature = "rustls")] - if let Self::Rustls(receiver) = self { - return Server::Rustls(receiver.server()); - } - - #[cfg(feature = "boring")] - if let Self::Boring(receiver) = self { - return Server::Boring(receiver.server()); - } - - unreachable!() - } - } -} diff --git a/linkerd/meshtls/src/server.rs b/linkerd/meshtls/src/server.rs new file mode 100644 index 0000000000..1406f44bf2 --- /dev/null +++ b/linkerd/meshtls/src/server.rs @@ -0,0 +1,277 @@ +use linkerd_error::Result; +use linkerd_identity::LocalId; +use linkerd_io as io; +use linkerd_stack::{Param, Service}; +use linkerd_tls::ServerTls; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +#[cfg(feature = "boring")] +use crate::boring; + +#[cfg(feature = "rustls")] +use crate::rustls; + +#[derive(Clone)] +pub enum Server { + #[cfg(feature = "rustls")] + Rustls(rustls::Server), + + #[cfg(feature = "boring")] + Boring(boring::Server), +} + +#[pin_project::pin_project(project = TerminateFutureProj)] +pub enum TerminateFuture { + #[cfg(feature = "rustls")] + Rustls(#[pin] rustls::TerminateFuture), + + #[cfg(feature = "boring")] + Boring(#[pin] boring::TerminateFuture), +} + +#[pin_project::pin_project(project = ServerIoProj)] +#[derive(Debug)] +pub enum ServerIo { + #[cfg(feature = "rustls")] + Rustls(#[pin] rustls::ServerIo), + + #[cfg(feature = "boring")] + Boring(#[pin] boring::ServerIo), +} + +// === impl Server === + +impl Param for Server { + fn param(&self) -> LocalId { + #[cfg(feature = "rustls")] + if let Self::Rustls(srv) = self { + return srv.param(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(srv) = self { + return srv.param(); + } + + unreachable!() + } +} + +impl Server { + pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(srv) = self { + return srv + .spawn_with_alpn(alpn_protocols) + .map(Self::Rustls) + .map_err(Into::into); + } + + #[cfg(feature = "boring")] + if let Self::Boring(srv) = self { + return srv.spawn_with_alpn(alpn_protocols).map(Self::Boring); + } + + unreachable!() + } +} + +impl Service for Server +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, +{ + type Response = (ServerTls, ServerIo); + type Error = io::Error; + type Future = TerminateFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + #[cfg(feature = "rustls")] + if let Self::Rustls(svc) = self { + return >::poll_ready(svc, cx); + } + + #[cfg(feature = "boring")] + if let Self::Boring(svc) = self { + return >::poll_ready(svc, cx); + } + + unreachable!() + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + #[cfg(feature = "rustls")] + if let Self::Rustls(svc) = self { + return TerminateFuture::Rustls(svc.call(io)); + } + + #[cfg(feature = "boring")] + if let Self::Boring(svc) = self { + return TerminateFuture::Boring(svc.call(io)); + } + + unreachable!() + } +} + +// === impl TerminateFuture === + +impl Future for TerminateFuture +where + I: io::AsyncRead + io::AsyncWrite + Unpin, +{ + type Output = io::Result<(ServerTls, ServerIo)>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let TerminateFutureProj::Rustls(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Rustls(io)))); + } + + #[cfg(feature = "boring")] + if let TerminateFutureProj::Boring(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Boring(io)))); + } + + unreachable!() + } +} + +// === impl ServerIo === + +impl io::AsyncRead for ServerIo { + #[inline] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_read(cx, buf); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_read(cx, buf); + } + + unreachable!() + } +} + +impl io::AsyncWrite for ServerIo { + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_flush(cx); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_flush(cx); + } + + unreachable!() + } + + #[inline] + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_shutdown(cx); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_shutdown(cx); + } + + unreachable!() + } + + #[inline] + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_write(cx, buf); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_write(cx, buf); + } + + unreachable!() + } + + #[inline] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + #[cfg(feature = "boring")] + if let ServerIoProj::Boring(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + unreachable!() + } + + #[inline] + fn is_write_vectored(&self) -> bool { + #[cfg(feature = "rustls")] + if let Self::Rustls(io) = self { + return io.is_write_vectored(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(io) = self { + return io.is_write_vectored(); + } + + unreachable!() + } +} + +impl io::PeerAddr for ServerIo { + #[inline] + fn peer_addr(&self) -> io::Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(io) = self { + return io.peer_addr(); + } + + #[cfg(feature = "boring")] + if let Self::Boring(io) = self { + return io.peer_addr(); + } + + unreachable!() + } +} From 0bd11046328b7fa7acec1d6dfd16be15a0d1816e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Wed, 3 Nov 2021 01:52:54 +0000 Subject: [PATCH 19/85] fixup tokio features --- linkerd/meshtls/rustls/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkerd/meshtls/rustls/Cargo.toml b/linkerd/meshtls/rustls/Cargo.toml index 8411eaef8f..6b5b030c11 100644 --- a/linkerd/meshtls/rustls/Cargo.toml +++ b/linkerd/meshtls/rustls/Cargo.toml @@ -19,7 +19,7 @@ linkerd-tls = { path = "../../tls" } linkerd-tls-test-util = { path = "../../tls/test-util", optional = true } ring = { version = "0.16.19", features = ["std"] } thiserror = "1" -tokio = { version = "1", features = ["macros", "sync"] } +tokio = { version = "1", features = ["macros", "rt", "sync"] } tokio-rustls = "0.22" tracing = "0.1" webpki = "0.21" From 56da589e430043190b56ce16d9549a5214a2f712 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Wed, 3 Nov 2021 02:45:10 +0000 Subject: [PATCH 20/85] fixup fuzzer --- .github/workflows/rust.yml | 6 +++--- linkerd/app/inbound/src/test_util.rs | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7225eba600..f95c536eca 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,7 +15,7 @@ jobs: # Ensures we don't take unintended dependencies. audit: - timeout-minutes: 5 + timeout-minutes: 10 runs-on: ubuntu-latest permissions: contents: read @@ -27,7 +27,7 @@ jobs: # Linting clippy: - timeout-minutes: 5 + timeout-minutes: 10 runs-on: ubuntu-latest container: image: docker://rust:1.56.0-buster @@ -54,7 +54,7 @@ jobs: # Enforce automated formatting. fmt: - timeout-minutes: 5 + timeout-minutes: 10 runs-on: ubuntu-latest container: image: docker://rust:1.56.0-buster diff --git a/linkerd/app/inbound/src/test_util.rs b/linkerd/app/inbound/src/test_util.rs index f0c5ac482f..35d1ae9757 100644 --- a/linkerd/app/inbound/src/test_util.rs +++ b/linkerd/app/inbound/src/test_util.rs @@ -3,7 +3,9 @@ pub use futures::prelude::*; use linkerd_app_core::{ config, dns::Suffix, - drain, exp_backoff, metrics, + drain, exp_backoff, + identity::rustls, + metrics, proxy::{ http::{h1, h2}, tap, @@ -73,7 +75,7 @@ pub fn runtime() -> (ProxyRuntime, drain::Signal) { let (tap, _) = tap::new(); let (metrics, _) = metrics::Metrics::new(std::time::Duration::from_secs(10)); let runtime = ProxyRuntime { - identity: linkerd_meshtls_rustls::creds::default_for_test().1.into(), + identity: rustls::creds::default_for_test().1.into(), metrics: metrics.proxy, tap, span_sink: None, From 5ffc10b5856060ffafeddbb7a35d8bb502068145 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 1 Nov 2021 22:11:44 +0000 Subject: [PATCH 21/85] Introduce `meshtls` facade to hide rustls crate In #1351, we add an alternate identity/mtls implementation that uses `boring`. To setup for that, this change introduces a new `meshtls` crate that serves as a facade for application crates to depend on, independently of the actual crypto implementation. This change does not change any runtime logic and sets up for #1351 to enable an alternate TLS implementation as a build-time configuration. --- Cargo.lock | 53 +++-- Cargo.toml | 3 +- linkerd/app/core/Cargo.toml | 7 +- linkerd/app/core/src/lib.rs | 8 +- linkerd/app/inbound/Cargo.toml | 2 +- linkerd/app/inbound/fuzz/Cargo.toml | 2 +- linkerd/app/inbound/src/test_util.rs | 6 +- linkerd/app/outbound/Cargo.toml | 2 +- linkerd/app/outbound/src/test_util.rs | 4 +- linkerd/app/src/env.rs | 10 +- linkerd/app/src/identity.rs | 16 +- linkerd/meshtls/Cargo.toml | 22 ++ .../default => meshtls/rustls}/Cargo.toml | 4 +- .../default => meshtls/rustls}/src/client.rs | 0 .../default => meshtls/rustls}/src/creds.rs | 0 .../rustls}/src/creds/receiver.rs | 0 .../rustls}/src/creds/store.rs | 0 .../default => meshtls/rustls}/src/lib.rs | 1 - .../default => meshtls/rustls}/src/server.rs | 0 .../default => meshtls/rustls}/src/tests.rs | 0 .../rustls}/tests/tls_accept.rs | 29 ++- linkerd/meshtls/src/client.rs | 200 +++++++++++++++++ linkerd/meshtls/src/creds.rs | 91 ++++++++ linkerd/meshtls/src/lib.rs | 51 +++++ linkerd/meshtls/src/server.rs | 204 ++++++++++++++++++ linkerd/proxy/tap/Cargo.toml | 2 +- linkerd/proxy/tap/src/accept.rs | 4 +- 27 files changed, 652 insertions(+), 69 deletions(-) create mode 100644 linkerd/meshtls/Cargo.toml rename linkerd/{identity/default => meshtls/rustls}/Cargo.toml (91%) rename linkerd/{identity/default => meshtls/rustls}/src/client.rs (100%) rename linkerd/{identity/default => meshtls/rustls}/src/creds.rs (100%) rename linkerd/{identity/default => meshtls/rustls}/src/creds/receiver.rs (100%) rename linkerd/{identity/default => meshtls/rustls}/src/creds/store.rs (100%) rename linkerd/{identity/default => meshtls/rustls}/src/lib.rs (88%) rename linkerd/{identity/default => meshtls/rustls}/src/server.rs (100%) rename linkerd/{identity/default => meshtls/rustls}/src/tests.rs (100%) rename linkerd/{identity/default => meshtls/rustls}/tests/tls_accept.rs (94%) create mode 100644 linkerd/meshtls/src/client.rs create mode 100644 linkerd/meshtls/src/creds.rs create mode 100644 linkerd/meshtls/src/lib.rs create mode 100644 linkerd/meshtls/src/server.rs diff --git a/Cargo.lock b/Cargo.lock index 587f224cfb..3456620272 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,8 +674,9 @@ dependencies = [ "linkerd-http-classify", "linkerd-http-metrics", "linkerd-http-retry", - "linkerd-identity-default", + "linkerd-identity", "linkerd-io", + "linkerd-meshtls", "linkerd-metrics", "linkerd-opencensus", "linkerd-proxy-api-resolve", @@ -743,8 +744,8 @@ dependencies = [ "libfuzzer-sys", "linkerd-app-core", "linkerd-app-test", - "linkerd-identity-default", "linkerd-io", + "linkerd-meshtls-rustls", "linkerd-server-policy", "linkerd-tonic-watch", "linkerd-tracing", @@ -799,8 +800,8 @@ dependencies = [ "linkerd-app-test", "linkerd-http-retry", "linkerd-identity", - "linkerd-identity-default", "linkerd-io", + "linkerd-meshtls-rustls", "linkerd-tracing", "parking_lot", "pin-project", @@ -1000,7 +1001,35 @@ dependencies = [ ] [[package]] -name = "linkerd-identity-default" +name = "linkerd-io" +version = "0.1.0" +dependencies = [ + "async-trait", + "bytes", + "futures", + "linkerd-errno", + "pin-project", + "tokio", + "tokio-test", + "tokio-util", +] + +[[package]] +name = "linkerd-meshtls" +version = "0.1.0" +dependencies = [ + "futures", + "linkerd-error", + "linkerd-identity", + "linkerd-io", + "linkerd-meshtls-rustls", + "linkerd-stack", + "linkerd-tls", + "pin-project", +] + +[[package]] +name = "linkerd-meshtls-rustls" version = "0.1.0" dependencies = [ "futures", @@ -1022,20 +1051,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "linkerd-io" -version = "0.1.0" -dependencies = [ - "async-trait", - "bytes", - "futures", - "linkerd-errno", - "pin-project", - "tokio", - "tokio-test", - "tokio-util", -] - [[package]] name = "linkerd-metrics" version = "0.1.0" @@ -1202,8 +1217,8 @@ dependencies = [ "ipnet", "linkerd-conditional", "linkerd-error", - "linkerd-identity-default", "linkerd-io", + "linkerd-meshtls", "linkerd-proxy-http", "linkerd-stack", "linkerd-tls", diff --git a/Cargo.toml b/Cargo.toml index 5d6f81c998..eb20c52303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,9 @@ members = [ "linkerd/http-metrics", "linkerd/http-retry", "linkerd/identity", - "linkerd/identity/default", "linkerd/io", + "linkerd/meshtls", + "linkerd/meshtls/rustls", "linkerd/metrics", "linkerd/opencensus", "linkerd/proxy/api-resolve", diff --git a/linkerd/app/core/Cargo.toml b/linkerd/app/core/Cargo.toml index 3d5c1327e1..20aa7b59b4 100644 --- a/linkerd/app/core/Cargo.toml +++ b/linkerd/app/core/Cargo.toml @@ -12,6 +12,10 @@ This crate conglomerates proxy configuration, runtime administration, etc, independently of the inbound and outbound proxy logic. """ +[features] +default = ["meshtls-rustls"] +meshtls-rustls = ["linkerd-meshtls/rustls"] + [dependencies] bytes = "1" drain = { version = "0.1.0", features = ["retain"] } @@ -33,8 +37,9 @@ linkerd-exp-backoff = { path = "../../exp-backoff" } linkerd-http-classify = { path = "../../http-classify" } linkerd-http-metrics = { path = "../../http-metrics" } linkerd-http-retry = { path = "../../http-retry" } -linkerd-identity-default = { path = "../../identity/default" } +linkerd-identity = { path = "../../identity" } linkerd-io = { path = "../../io" } +linkerd-meshtls = { path = "../../meshtls", default-features = false } linkerd-metrics = { path = "../../metrics", features = ["linkerd-stack"] } linkerd-opencensus = { path = "../../opencensus" } linkerd-proxy-core = { path = "../../proxy/core" } diff --git a/linkerd/app/core/src/lib.rs b/linkerd/app/core/src/lib.rs index faa9d425e8..5c418cdd5c 100644 --- a/linkerd/app/core/src/lib.rs +++ b/linkerd/app/core/src/lib.rs @@ -20,10 +20,8 @@ pub use linkerd_dns; pub use linkerd_error::{is_error, Error, Infallible, Recover, Result}; pub use linkerd_exp_backoff as exp_backoff; pub use linkerd_http_metrics as http_metrics; -pub use linkerd_identity_default as identity; pub use linkerd_io as io; pub use linkerd_opencensus as opencensus; -pub use linkerd_proxy_identity_client as identity_client; pub use linkerd_service_profiles as profiles; pub use linkerd_stack_metrics as stack_metrics; pub use linkerd_stack_tracing as stack_tracing; @@ -51,6 +49,12 @@ pub mod transport; pub use self::addr_match::{AddrMatch, IpMatch, NameMatch}; +pub mod identity { + pub use linkerd_identity::*; + pub use linkerd_meshtls::*; + pub use linkerd_proxy_identity_client as client; +} + pub const CANONICAL_DST_HEADER: &str = "l5d-dst-canonical"; const DEFAULT_PORT: u16 = 80; diff --git a/linkerd/app/inbound/Cargo.toml b/linkerd/app/inbound/Cargo.toml index d9f3fdb6ea..e378a777f2 100644 --- a/linkerd/app/inbound/Cargo.toml +++ b/linkerd/app/inbound/Cargo.toml @@ -34,7 +34,7 @@ libfuzzer-sys = { version = "0.4.2", features = ["arbitrary-derive"] } hyper = { version = "0.14.14", features = ["http1", "http2"] } linkerd-app-test = { path = "../test" } linkerd-io = { path = "../../io", features = ["tokio-test"] } -linkerd-identity-default = { path = "../../identity/default", features = ["test-util"] } +linkerd-meshtls-rustls = { path = "../../meshtls/rustls", features = ["test-util"] } linkerd-tracing = { path = "../../tracing", features = ["ansi"] } tokio = { version = "1", features = ["full", "macros"] } tokio-test = "0.4" diff --git a/linkerd/app/inbound/fuzz/Cargo.toml b/linkerd/app/inbound/fuzz/Cargo.toml index 9ae622f451..eb83bcf975 100644 --- a/linkerd/app/inbound/fuzz/Cargo.toml +++ b/linkerd/app/inbound/fuzz/Cargo.toml @@ -17,7 +17,7 @@ libfuzzer-sys = { version = "0.4.2", features = ["arbitrary-derive"] } linkerd-app-core = { path = "../../core" } linkerd-app-inbound = { path = ".." } linkerd-app-test = { path = "../../test" } -linkerd-identity-default = { path = "../../../identity/default", features = ["test-util"] } +linkerd-meshtls-rustls = { path = "../../../meshtls/rustls", features = ["test-util"] } linkerd-tracing = { path = "../../../tracing", features = ["ansi"] } tokio = { version = "1", features = ["full"] } tracing = "0.1" diff --git a/linkerd/app/inbound/src/test_util.rs b/linkerd/app/inbound/src/test_util.rs index d77fd87568..35d1ae9757 100644 --- a/linkerd/app/inbound/src/test_util.rs +++ b/linkerd/app/inbound/src/test_util.rs @@ -3,7 +3,9 @@ pub use futures::prelude::*; use linkerd_app_core::{ config, dns::Suffix, - drain, exp_backoff, identity, metrics, + drain, exp_backoff, + identity::rustls, + metrics, proxy::{ http::{h1, h2}, tap, @@ -73,7 +75,7 @@ pub fn runtime() -> (ProxyRuntime, drain::Signal) { let (tap, _) = tap::new(); let (metrics, _) = metrics::Metrics::new(std::time::Duration::from_secs(10)); let runtime = ProxyRuntime { - identity: identity::creds::default_for_test().1, + identity: rustls::creds::default_for_test().1.into(), metrics: metrics.proxy, tap, span_sink: None, diff --git a/linkerd/app/outbound/Cargo.toml b/linkerd/app/outbound/Cargo.toml index 796c25003c..fc2a51e2a1 100644 --- a/linkerd/app/outbound/Cargo.toml +++ b/linkerd/app/outbound/Cargo.toml @@ -32,7 +32,7 @@ pin-project = "1" hyper = { version = "0.14.14", features = ["http1", "http2"] } linkerd-app-test = { path = "../test" } linkerd-io = { path = "../../io", features = ["tokio-test"] } -linkerd-identity-default = { path = "../../identity/default", features = ["test-util"] } +linkerd-meshtls-rustls = { path = "../../meshtls/rustls", features = ["test-util"] } linkerd-tracing = { path = "../../tracing", features = ["ansi"] } parking_lot = "0.11" tokio = { version = "1", features = ["time", "macros"] } diff --git a/linkerd/app/outbound/src/test_util.rs b/linkerd/app/outbound/src/test_util.rs index 5da08b586c..4afd5c5a88 100644 --- a/linkerd/app/outbound/src/test_util.rs +++ b/linkerd/app/outbound/src/test_util.rs @@ -1,7 +1,7 @@ use crate::Config; pub use futures::prelude::*; use linkerd_app_core::{ - config, drain, exp_backoff, identity, metrics, + config, drain, exp_backoff, metrics, proxy::{ http::{h1, h2}, tap, @@ -53,7 +53,7 @@ pub(crate) fn runtime() -> (ProxyRuntime, drain::Signal) { let (tap, _) = tap::new(); let (metrics, _) = metrics::Metrics::new(std::time::Duration::from_secs(10)); let runtime = ProxyRuntime { - identity: identity::creds::default_for_test().1, + identity: linkerd_meshtls_rustls::creds::default_for_test().1.into(), metrics: metrics.proxy, tap, span_sink: None, diff --git a/linkerd/app/src/env.rs b/linkerd/app/src/env.rs index ec1e086197..00069a6038 100644 --- a/linkerd/app/src/env.rs +++ b/linkerd/app/src/env.rs @@ -2,7 +2,6 @@ use crate::core::{ addr, config::*, control::{Config as ControlConfig, ControlAddr}, - identity_client, proxy::http::{h1, h2}, tls, transport::{Keepalive, ListenAddr}, @@ -1102,14 +1101,7 @@ pub fn parse_control_addr( pub fn parse_identity_config( strings: &S, -) -> Result< - ( - ControlAddr, - identity_client::certify::Config, - identity::Documents, - ), - EnvError, -> { +) -> Result<(ControlAddr, identity::certify::Config, identity::Documents), EnvError> { let control = parse_control_addr(strings, ENV_IDENTITY_SVC_BASE); let ta = parse(strings, ENV_IDENTITY_TRUST_ANCHORS, |s| { if s.is_empty() { diff --git a/linkerd/app/src/identity.rs b/linkerd/app/src/identity.rs index b6c635c289..040bd06516 100644 --- a/linkerd/app/src/identity.rs +++ b/linkerd/app/src/identity.rs @@ -1,15 +1,17 @@ +pub use linkerd_app_core::identity::{ + client::{certify, TokenSource}, + InvalidName, LocalId, Name, +}; use linkerd_app_core::{ control, dns, exp_backoff::{ExponentialBackoff, ExponentialBackoffStream}, - identity::{creds, Credentials, DerX509}, - identity_client::{Certify, Metrics as IdentityMetrics}, + identity::{ + client::{Certify, Metrics as IdentityMetrics}, + creds, Credentials, DerX509, Mode, + }, metrics::ControlHttp as ClientMetrics, Error, Result, }; -pub use linkerd_app_core::{ - identity::{InvalidName, LocalId, Name}, - identity_client::{certify, TokenSource}, -}; use std::{future::Future, pin::Pin}; use tokio::sync::watch; use tracing::Instrument; @@ -53,7 +55,7 @@ struct NotifyReady { impl Config { pub fn build(self, dns: dns::Resolver, client_metrics: ClientMetrics) -> Result { - let (store, receiver) = creds::watch( + let (store, receiver) = Mode::default().watch( (*self.documents.id).clone(), &self.documents.trust_anchors_pem, &self.documents.key_pkcs8, diff --git a/linkerd/meshtls/Cargo.toml b/linkerd/meshtls/Cargo.toml new file mode 100644 index 0000000000..1ea25bf229 --- /dev/null +++ b/linkerd/meshtls/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "linkerd-meshtls" +version = "0.1.0" +authors = ["Linkerd Developers "] +license = "Apache-2.0" +edition = "2018" +publish = false + +[features] +default = ["rustls"] +rustls = ["linkerd-meshtls-rustls"] + + +[dependencies] +futures = { version = "0.3", default-features = false } +linkerd-error = { path = "../error" } +linkerd-identity = { path = "../identity" } +linkerd-io = { path = "../io" } +linkerd-meshtls-rustls = { path = "rustls", optional = true } +linkerd-stack = { path = "../stack" } +linkerd-tls = { path = "../tls" } +pin-project = "1" diff --git a/linkerd/identity/default/Cargo.toml b/linkerd/meshtls/rustls/Cargo.toml similarity index 91% rename from linkerd/identity/default/Cargo.toml rename to linkerd/meshtls/rustls/Cargo.toml index eedb64872e..6b5b030c11 100644 --- a/linkerd/identity/default/Cargo.toml +++ b/linkerd/meshtls/rustls/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "linkerd-identity-default" +name = "linkerd-meshtls-rustls" version = "0.1.0" authors = ["Linkerd Developers "] license = "Apache-2.0" @@ -19,7 +19,7 @@ linkerd-tls = { path = "../../tls" } linkerd-tls-test-util = { path = "../../tls/test-util", optional = true } ring = { version = "0.16.19", features = ["std"] } thiserror = "1" -tokio = { version = "1", features = ["macros", "sync"] } +tokio = { version = "1", features = ["macros", "rt", "sync"] } tokio-rustls = "0.22" tracing = "0.1" webpki = "0.21" diff --git a/linkerd/identity/default/src/client.rs b/linkerd/meshtls/rustls/src/client.rs similarity index 100% rename from linkerd/identity/default/src/client.rs rename to linkerd/meshtls/rustls/src/client.rs diff --git a/linkerd/identity/default/src/creds.rs b/linkerd/meshtls/rustls/src/creds.rs similarity index 100% rename from linkerd/identity/default/src/creds.rs rename to linkerd/meshtls/rustls/src/creds.rs diff --git a/linkerd/identity/default/src/creds/receiver.rs b/linkerd/meshtls/rustls/src/creds/receiver.rs similarity index 100% rename from linkerd/identity/default/src/creds/receiver.rs rename to linkerd/meshtls/rustls/src/creds/receiver.rs diff --git a/linkerd/identity/default/src/creds/store.rs b/linkerd/meshtls/rustls/src/creds/store.rs similarity index 100% rename from linkerd/identity/default/src/creds/store.rs rename to linkerd/meshtls/rustls/src/creds/store.rs diff --git a/linkerd/identity/default/src/lib.rs b/linkerd/meshtls/rustls/src/lib.rs similarity index 88% rename from linkerd/identity/default/src/lib.rs rename to linkerd/meshtls/rustls/src/lib.rs index 0f37758c91..bcc6cc0461 100644 --- a/linkerd/identity/default/src/lib.rs +++ b/linkerd/meshtls/rustls/src/lib.rs @@ -9,4 +9,3 @@ pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, server::{Server, ServerIo, TerminateFuture}, }; -pub use linkerd_identity::*; diff --git a/linkerd/identity/default/src/server.rs b/linkerd/meshtls/rustls/src/server.rs similarity index 100% rename from linkerd/identity/default/src/server.rs rename to linkerd/meshtls/rustls/src/server.rs diff --git a/linkerd/identity/default/src/tests.rs b/linkerd/meshtls/rustls/src/tests.rs similarity index 100% rename from linkerd/identity/default/src/tests.rs rename to linkerd/meshtls/rustls/src/tests.rs diff --git a/linkerd/identity/default/tests/tls_accept.rs b/linkerd/meshtls/rustls/tests/tls_accept.rs similarity index 94% rename from linkerd/identity/default/tests/tls_accept.rs rename to linkerd/meshtls/rustls/tests/tls_accept.rs index 5f014cb228..9357688ee1 100644 --- a/linkerd/identity/default/tests/tls_accept.rs +++ b/linkerd/meshtls/rustls/tests/tls_accept.rs @@ -8,8 +8,9 @@ use futures::prelude::*; use linkerd_conditional::Conditional; use linkerd_error::Infallible; -use linkerd_identity_default::{self as identity, Credentials, DerX509, Name}; +use linkerd_identity::{Credentials, DerX509, Name}; use linkerd_io::{self as io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use linkerd_meshtls_rustls as meshtls; use linkerd_proxy_transport::{ addrs::*, listen::{Addrs, Bind, BindTcp}, @@ -28,18 +29,12 @@ use tracing::instrument::Instrument; type ServerConn = ( (tls::ConditionalServerTls, T), - io::EitherIo>, tls::server::DetectIo>, + io::EitherIo>, tls::server::DetectIo>, ); -fn load( - ent: &test_util::Entity, -) -> ( - identity::creds::Store, - identity::NewClient, - identity::Server, -) { +fn load(ent: &test_util::Entity) -> (meshtls::creds::Store, meshtls::NewClient, meshtls::Server) { let roots_pem = std::str::from_utf8(ent.trust_anchors).expect("valid PEM"); - let (mut store, rx) = identity::creds::watch( + let (mut store, rx) = meshtls::creds::watch( ent.name.parse().unwrap(), roots_pem, ent.key, @@ -152,19 +147,19 @@ struct Transported { #[derive(Clone)] struct ServerParams { - identity: identity::Server, + identity: meshtls::Server, } -type ClientIo = io::EitherIo, identity::ClientIo>>; +type ClientIo = io::EitherIo, meshtls::ClientIo>>; /// Runs a test for a single TCP connection. `client` processes the connection /// on the client side and `server` processes the connection on the server /// side. async fn run_test( - client_tls: identity::NewClient, + client_tls: meshtls::NewClient, client_server_id: Conditional, client: C, - server_id: identity::Server, + server_id: meshtls::Server, server: S, ) -> ( Transported, @@ -187,7 +182,7 @@ where // Saves the result of every connection. let (sender, receiver) = mpsc::channel::>(); - let detect = tls::NewDetectTls::::new( + let detect = tls::NewDetectTls::::new( ServerParams { identity: server_id, }, @@ -375,8 +370,8 @@ impl ExtractParam for ServerParams { } } -impl ExtractParam for ServerParams { - fn extract_param(&self, _: &T) -> identity::Server { +impl ExtractParam for ServerParams { + fn extract_param(&self, _: &T) -> meshtls::Server { self.identity.clone() } } diff --git a/linkerd/meshtls/src/client.rs b/linkerd/meshtls/src/client.rs new file mode 100644 index 0000000000..6a03c23fca --- /dev/null +++ b/linkerd/meshtls/src/client.rs @@ -0,0 +1,200 @@ +use linkerd_io as io; +use linkerd_stack::{NewService, Service}; +use linkerd_tls::{ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +#[cfg(feature = "rustls")] +use crate::rustls; + +#[derive(Clone, Debug)] +pub enum NewClient { + #[cfg(feature = "rustls")] + Rustls(rustls::NewClient), +} + +#[derive(Clone)] +pub enum Connect { + #[cfg(feature = "rustls")] + Rustls(rustls::Connect), +} + +#[pin_project::pin_project(project = ConnectFutureProj)] +pub enum ConnectFuture { + #[cfg(feature = "rustls")] + Rustls(#[pin] rustls::ConnectFuture), +} + +#[pin_project::pin_project(project = ClientIoProj)] +#[derive(Debug)] +pub enum ClientIo { + #[cfg(feature = "rustls")] + Rustls(#[pin] rustls::ClientIo), +} + +// === impl NewClient === + +impl NewService for NewClient { + type Service = Connect; + + fn new_service(&self, target: ClientTls) -> Self::Service { + #[cfg(feature = "rustls")] + if let Self::Rustls(new_client) = self { + return Connect::Rustls(new_client.new_service(target)); + } + + unreachable!() + } +} + +// === impl Connect === + +impl Service for Connect +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, +{ + type Response = ClientIo; + type Error = io::Error; + type Future = ConnectFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + #[cfg(feature = "rustls")] + if let Self::Rustls(connect) = self { + return >::poll_ready(connect, cx); + } + + unreachable!() + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + #[cfg(feature = "rustls")] + if let Self::Rustls(connect) = self { + return ConnectFuture::Rustls(connect.call(io)); + } + + unreachable!() + } +} + +// === impl ConnectFuture === + +impl Future for ConnectFuture +where + I: io::AsyncRead + io::AsyncWrite + Unpin, +{ + type Output = io::Result>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ConnectFutureProj::Rustls(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(ClientIo::Rustls)); + } + + unreachable!() + } +} + +// === impl ClientIo === + +impl io::AsyncRead for ClientIo { + #[inline] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_read(cx, buf); + } + + unreachable!() + } +} + +impl io::AsyncWrite for ClientIo { + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_flush(cx); + } + + unreachable!() + } + + #[inline] + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_shutdown(cx); + } + + unreachable!() + } + + #[inline] + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_write(cx, buf); + } + + unreachable!() + } + + #[inline] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ClientIoProj::Rustls(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + unreachable!() + } + + #[inline] + fn is_write_vectored(&self) -> bool { + unimplemented!() + } +} + +impl HasNegotiatedProtocol for ClientIo { + #[inline] + fn negotiated_protocol(&self) -> Option> { + unimplemented!() + } +} + +impl io::PeerAddr for ClientIo { + #[inline] + fn peer_addr(&self) -> io::Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(io) = self { + return io.peer_addr(); + } + + unreachable!() + } +} diff --git a/linkerd/meshtls/src/creds.rs b/linkerd/meshtls/src/creds.rs new file mode 100644 index 0000000000..20017bec69 --- /dev/null +++ b/linkerd/meshtls/src/creds.rs @@ -0,0 +1,91 @@ +use crate::{NewClient, Server}; +use linkerd_error::Result; +use linkerd_identity::{Credentials, DerX509, Name}; + +#[cfg(feature = "rustls")] +pub use crate::rustls; + +pub enum Store { + #[cfg(feature = "rustls")] + Rustls(rustls::creds::Store), +} + +#[derive(Clone, Debug)] +pub enum Receiver { + #[cfg(feature = "rustls")] + Rustls(rustls::creds::Receiver), +} + +// === impl Store === + +impl Credentials for Store { + fn dns_name(&self) -> &Name { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.dns_name(); + } + + unreachable!() + } + + fn gen_certificate_signing_request(&mut self) -> DerX509 { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.gen_certificate_signing_request(); + } + + unreachable!() + } + + fn set_certificate( + &mut self, + leaf: DerX509, + chain: Vec, + expiry: std::time::SystemTime, + ) -> Result<()> { + #[cfg(feature = "rustls")] + if let Self::Rustls(store) = self { + return store.set_certificate(leaf, chain, expiry); + } + + unreachable!() + } +} + +// === impl Receiver === + +#[cfg(feature = "rustls")] +impl From for Receiver { + fn from(rx: rustls::creds::Receiver) -> Self { + Self::Rustls(rx) + } +} + +impl Receiver { + pub fn name(&self) -> &Name { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return receiver.name(); + } + + unreachable!() + } + + pub fn new_client(&self) -> NewClient { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return NewClient::Rustls(receiver.new_client()); + } + + unreachable!() + } + + pub fn server(&self) -> Server { + #[cfg(feature = "rustls")] + if let Self::Rustls(receiver) = self { + return Server::Rustls(receiver.server()); + } + + unreachable!() + } +} diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs new file mode 100644 index 0000000000..6600507d31 --- /dev/null +++ b/linkerd/meshtls/src/lib.rs @@ -0,0 +1,51 @@ +#![allow(irrefutable_let_patterns)] + +mod client; +pub mod creds; +mod server; + +pub use self::{ + client::{ClientIo, Connect, ConnectFuture, NewClient}, + server::{Server, ServerIo, TerminateFuture}, +}; +use linkerd_error::Result; +use linkerd_identity::Name; + +#[cfg(feature = "rustls")] +pub use linkerd_meshtls_rustls as rustls; + +#[derive(Copy, Clone, Debug)] +pub enum Mode { + #[cfg(feature = "rustls")] + Rustls, +} + +// === impl Mode === + +#[cfg(feature = "rustls")] +impl Default for Mode { + fn default() -> Self { + Self::Rustls + } +} + +impl Mode { + pub fn watch( + self, + identity: Name, + roots_pem: &str, + key_pkcs8: &[u8], + csr: &[u8], + ) -> Result<(creds::Store, creds::Receiver)> { + #[cfg(feature = "rustls")] + if let Self::Rustls = self { + let (store, receiver) = rustls::creds::watch(identity, roots_pem, key_pkcs8, csr)?; + return Ok(( + creds::Store::Rustls(store), + creds::Receiver::Rustls(receiver), + )); + } + + unreachable!() + } +} diff --git a/linkerd/meshtls/src/server.rs b/linkerd/meshtls/src/server.rs new file mode 100644 index 0000000000..d8e66771a3 --- /dev/null +++ b/linkerd/meshtls/src/server.rs @@ -0,0 +1,204 @@ +use linkerd_error::Result; +use linkerd_identity::LocalId; +use linkerd_io as io; +use linkerd_stack::{Param, Service}; +use linkerd_tls::ServerTls; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +#[cfg(feature = "rustls")] +use crate::rustls; + +#[derive(Clone)] +pub enum Server { + #[cfg(feature = "rustls")] + Rustls(rustls::Server), +} + +#[pin_project::pin_project(project = TerminateFutureProj)] +pub enum TerminateFuture { + #[cfg(feature = "rustls")] + Rustls(#[pin] rustls::TerminateFuture), +} + +#[pin_project::pin_project(project = ServerIoProj)] +#[derive(Debug)] +pub enum ServerIo { + #[cfg(feature = "rustls")] + Rustls(#[pin] rustls::ServerIo), +} + +// === impl Server === + +impl Param for Server { + fn param(&self) -> LocalId { + #[cfg(feature = "rustls")] + if let Self::Rustls(srv) = self { + return srv.param(); + } + + unreachable!() + } +} + +impl Server { + pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(srv) = self { + return srv + .spawn_with_alpn(alpn_protocols) + .map(Self::Rustls) + .map_err(Into::into); + } + + unreachable!() + } +} + +impl Service for Server +where + I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, +{ + type Response = (ServerTls, ServerIo); + type Error = io::Error; + type Future = TerminateFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + #[cfg(feature = "rustls")] + if let Self::Rustls(svc) = self { + return >::poll_ready(svc, cx); + } + + unreachable!() + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + #[cfg(feature = "rustls")] + if let Self::Rustls(svc) = self { + return TerminateFuture::Rustls(svc.call(io)); + } + + unreachable!() + } +} + +// === impl TerminateFuture === + +impl Future for TerminateFuture +where + I: io::AsyncRead + io::AsyncWrite + Unpin, +{ + type Output = io::Result<(ServerTls, ServerIo)>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let TerminateFutureProj::Rustls(f) = this { + let res = futures::ready!(f.poll(cx)); + return Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Rustls(io)))); + } + + unreachable!() + } +} + +// === impl ServerIo === + +impl io::AsyncRead for ServerIo { + #[inline] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_read(cx, buf); + } + + unreachable!() + } +} + +impl io::AsyncWrite for ServerIo { + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_flush(cx); + } + + unreachable!() + } + + #[inline] + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_shutdown(cx); + } + + unreachable!() + } + + #[inline] + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_write(cx, buf); + } + + unreachable!() + } + + #[inline] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let this = self.project(); + + #[cfg(feature = "rustls")] + if let ServerIoProj::Rustls(io) = this { + return io.poll_write_vectored(cx, bufs); + } + + unreachable!() + } + + #[inline] + fn is_write_vectored(&self) -> bool { + #[cfg(feature = "rustls")] + if let Self::Rustls(io) = self { + return io.is_write_vectored(); + } + + unreachable!() + } +} + +impl io::PeerAddr for ServerIo { + #[inline] + fn peer_addr(&self) -> io::Result { + #[cfg(feature = "rustls")] + if let Self::Rustls(io) = self { + return io.peer_addr(); + } + + unreachable!() + } +} diff --git a/linkerd/proxy/tap/Cargo.toml b/linkerd/proxy/tap/Cargo.toml index 1aa9732a40..8d59bc0511 100644 --- a/linkerd/proxy/tap/Cargo.toml +++ b/linkerd/proxy/tap/Cargo.toml @@ -17,7 +17,7 @@ ipnet = "2.3" linkerd2-proxy-api = { version = "0.3", features = ["tap", "server"] } linkerd-conditional = { path = "../../conditional" } linkerd-error = { path = "../../error" } -linkerd-identity-default = { path = "../../identity/default" } +linkerd-meshtls = { path = "../../meshtls" } linkerd-io = { path = "../../io" } linkerd-proxy-http = { path = "../http" } linkerd-stack = { path = "../../stack" } diff --git a/linkerd/proxy/tap/src/accept.rs b/linkerd/proxy/tap/src/accept.rs index 4d414b6ae9..5bcb50c862 100644 --- a/linkerd/proxy/tap/src/accept.rs +++ b/linkerd/proxy/tap/src/accept.rs @@ -3,8 +3,8 @@ use futures::future; use linkerd2_proxy_api::tap::tap_server::{Tap, TapServer}; use linkerd_conditional::Conditional; use linkerd_error::Error; -use linkerd_identity_default as identity; use linkerd_io as io; +use linkerd_meshtls as meshtls; use linkerd_proxy_http::{trace, HyperServerSvc}; use linkerd_tls as tls; use std::{ @@ -24,7 +24,7 @@ pub struct AcceptPermittedClients { type Connection = ( (tls::ConditionalServerTls, T), - io::EitherIo>, tls::server::DetectIo>, + io::EitherIo>, tls::server::DetectIo>, ); pub type ServeFuture = Pin> + Send + 'static>>; From 5c1f34f102261760fba0860dc49c9c0c11f880e4 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Thu, 4 Nov 2021 00:28:48 +0000 Subject: [PATCH 22/85] cleanup task type imports --- linkerd/meshtls/boring/src/client.rs | 26 +++++++++----------------- linkerd/meshtls/boring/src/server.rs | 25 +++++++++---------------- linkerd/meshtls/rustls/src/client.rs | 25 +++++++++---------------- linkerd/meshtls/rustls/src/server.rs | 22 +++++++++------------- 4 files changed, 36 insertions(+), 62 deletions(-) diff --git a/linkerd/meshtls/boring/src/client.rs b/linkerd/meshtls/boring/src/client.rs index bf18d8db31..c2061c1abe 100644 --- a/linkerd/meshtls/boring/src/client.rs +++ b/linkerd/meshtls/boring/src/client.rs @@ -1,12 +1,11 @@ use boring::ssl; -use linkerd_error::Result; use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{NewService, Service}; use linkerd_tls::{ client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerId, }; -use std::{future::Future, pin::Pin}; +use std::{future::Future, pin::Pin, task::Context}; use tokio::sync::watch; #[derive(Clone)] @@ -72,11 +71,8 @@ where type Error = io::Error; type Future = ConnectFuture; - fn poll_ready( - &mut self, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(Ok(())) + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> io::Poll<()> { + io::Poll::Ready(Ok(())) } fn call(&mut self, io: I) -> Self::Future { @@ -103,7 +99,7 @@ impl io::AsyncRead for ClientIo { #[inline] fn poll_read( mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + cx: &mut Context<'_>, buf: &mut io::ReadBuf<'_>, ) -> io::Poll<()> { Pin::new(&mut self.0).poll_read(cx, buf) @@ -112,30 +108,26 @@ impl io::AsyncRead for ClientIo { impl io::AsyncWrite for ClientIo { #[inline] - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_flush(cx) } #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_shutdown(cx) } #[inline] - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> io::Poll { + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { Pin::new(&mut self.0).poll_write(cx, buf) } #[inline] fn poll_write_vectored( mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], - ) -> std::task::Poll> { + ) -> io::Poll { Pin::new(&mut self.0).poll_write_vectored(cx, bufs) } diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index 3a9f623ac5..69d9e633e7 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -4,7 +4,7 @@ use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{Param, Service}; use linkerd_tls::{ClientId, LocalId, NegotiatedProtocolRef, ServerTls}; -use std::{future::Future, pin::Pin}; +use std::{future::Future, pin::Pin, task::Context}; use tokio::sync::watch; use tracing::debug; @@ -51,11 +51,8 @@ where type Future = TerminateFuture; #[inline] - fn poll_ready( - &mut self, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(Ok(())) + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> io::Poll<()> { + io::Poll::Ready(Ok(())) } #[inline] @@ -104,7 +101,7 @@ impl io::AsyncRead for ServerIo { #[inline] fn poll_read( mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + cx: &mut Context<'_>, buf: &mut io::ReadBuf<'_>, ) -> io::Poll<()> { Pin::new(&mut self.0).poll_read(cx, buf) @@ -113,30 +110,26 @@ impl io::AsyncRead for ServerIo { impl io::AsyncWrite for ServerIo { #[inline] - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_flush(cx) } #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_shutdown(cx) } #[inline] - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> io::Poll { + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { Pin::new(&mut self.0).poll_write(cx, buf) } #[inline] fn poll_write_vectored( mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], - ) -> std::task::Poll> { + ) -> io::Poll { Pin::new(&mut self.0).poll_write_vectored(cx, bufs) } diff --git a/linkerd/meshtls/rustls/src/client.rs b/linkerd/meshtls/rustls/src/client.rs index 052f445020..87420b4009 100644 --- a/linkerd/meshtls/rustls/src/client.rs +++ b/linkerd/meshtls/rustls/src/client.rs @@ -2,7 +2,7 @@ use futures::prelude::*; use linkerd_io as io; use linkerd_stack::{NewService, Service}; use linkerd_tls::{client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef}; -use std::{pin::Pin, sync::Arc}; +use std::{pin::Pin, sync::Arc, task::Context}; use tokio::sync::watch; use tokio_rustls::rustls::{ClientConfig, Session}; @@ -84,11 +84,8 @@ where type Error = io::Error; type Future = ConnectFuture; - fn poll_ready( - &mut self, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(Ok(())) + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> io::Poll<()> { + io::Poll::Ready(Ok(())) } fn call(&mut self, io: I) -> Self::Future { @@ -104,7 +101,7 @@ impl io::AsyncRead for ClientIo { #[inline] fn poll_read( mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + cx: &mut Context<'_>, buf: &mut io::ReadBuf<'_>, ) -> io::Poll<()> { Pin::new(&mut self.0).poll_read(cx, buf) @@ -113,30 +110,26 @@ impl io::AsyncRead for ClientIo { impl io::AsyncWrite for ClientIo { #[inline] - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_flush(cx) } #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_shutdown(cx) } #[inline] - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> io::Poll { + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { Pin::new(&mut self.0).poll_write(cx, buf) } #[inline] fn poll_write_vectored( mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], - ) -> std::task::Poll> { + ) -> io::Poll { Pin::new(&mut self.0).poll_write_vectored(cx, bufs) } diff --git a/linkerd/meshtls/rustls/src/server.rs b/linkerd/meshtls/rustls/src/server.rs index 6fd5806761..e57e0feb47 100644 --- a/linkerd/meshtls/rustls/src/server.rs +++ b/linkerd/meshtls/rustls/src/server.rs @@ -5,7 +5,7 @@ use linkerd_stack::{Param, Service}; use linkerd_tls::{ ClientId, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, ServerTls, }; -use std::{pin::Pin, sync::Arc, task}; +use std::{pin::Pin, sync::Arc, task::Context}; use thiserror::Error; use tokio::sync::watch; use tokio_rustls::rustls::{Certificate, ServerConfig, Session}; @@ -99,8 +99,8 @@ where type Future = TerminateFuture; #[inline] - fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { - task::Poll::Ready(Ok(())) + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> io::Poll<()> { + io::Poll::Ready(Ok(())) } #[inline] @@ -152,7 +152,7 @@ impl io::AsyncRead for ServerIo { #[inline] fn poll_read( mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + cx: &mut Context<'_>, buf: &mut io::ReadBuf<'_>, ) -> io::Poll<()> { Pin::new(&mut self.0).poll_read(cx, buf) @@ -161,30 +161,26 @@ impl io::AsyncRead for ServerIo { impl io::AsyncWrite for ServerIo { #[inline] - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_flush(cx) } #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_shutdown(cx) } #[inline] - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> io::Poll { + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { Pin::new(&mut self.0).poll_write(cx, buf) } #[inline] fn poll_write_vectored( mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, + cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], - ) -> std::task::Poll> { + ) -> io::Poll { Pin::new(&mut self.0).poll_write_vectored(cx, bufs) } From 6e35ad9e8895ccb140fa5864452a4696b481879a Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Thu, 4 Nov 2021 14:58:29 +0000 Subject: [PATCH 23/85] Add an ALPN serializer --- linkerd/meshtls/boring/src/lib.rs | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index bcc6cc0461..5982227b58 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -9,3 +9,36 @@ pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, server::{Server, ServerIo, TerminateFuture}, }; +use linkerd_error::Result; + +/// Encodes a list of ALPN protocols into a slice of bytes. +/// +/// `boring` requires that the list of protocols be encoded in the wire format. +#[allow(dead_code)] +fn serialize_alpn(protos: &[Vec]) -> Result> { + let mut bytes = { + // One additional byte for each protocol's length prefix. + let cap = protos.len() + protos.iter().map(|p| p.len()).sum::(); + Vec::with_capacity(cap) + }; + + for proto in protos { + if proto.len() > 255 { + return Err("ALPN protocol must be less than 255 bytes".into()); + } + bytes.push(proto.len() as u8); + bytes.extend(&*proto); + } + + Ok(bytes) +} + +#[cfg(test)] +#[test] +fn test_serialize_alpn() { + assert_eq!(serialize_alpn(&[b"h2".to_vec()]).unwrap(), b"\x02h2"); + assert_eq!( + serialize_alpn(&[b"h2".to_vec(), b"http/1.1".to_vec()]).unwrap(), + b"\x02h2\x08http/1.1" + ); +} From da4a29ca8637002351dd289e246eec17da7adaa9 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Thu, 4 Nov 2021 16:00:50 +0000 Subject: [PATCH 24/85] fixup compilation --- linkerd/app/gateway/src/lib.rs | 3 ++- linkerd/app/outbound/src/endpoint.rs | 10 +++------- linkerd/app/outbound/src/logical.rs | 10 +++------- linkerd/app/outbound/src/tcp/connect.rs | 5 +++-- linkerd/meshtls/boring/src/client.rs | 5 ++++- linkerd/meshtls/build.rs | 2 +- linkerd/meshtls/src/client.rs | 12 ++++++++++-- linkerd/meshtls/src/creds.rs | 21 ++++++++++++++++++++- linkerd/meshtls/src/lib.rs | 14 ++++++++++++++ linkerd/meshtls/src/server.rs | 11 ++++++++--- 10 files changed, 68 insertions(+), 25 deletions(-) diff --git a/linkerd/app/gateway/src/lib.rs b/linkerd/app/gateway/src/lib.rs index 558c2cbb7f..6690e8d02b 100644 --- a/linkerd/app/gateway/src/lib.rs +++ b/linkerd/app/gateway/src/lib.rs @@ -72,7 +72,8 @@ where O: Clone + Send + Sync + Unpin + 'static, O: svc::Service, O::Response: - io::AsyncRead + io::AsyncWrite + tls::HasNegotiatedProtocol + Send + Sync + Unpin + 'static, + io::AsyncRead + io::AsyncWrite + tls::HasNegotiatedProtocol + Send + Unpin + 'static, + O::Response: Sync + std::fmt::Debug, // Needed by `boring`. O::Future: Send + Unpin + 'static, P: profiles::GetProfile + Clone + Send + Sync + Unpin + 'static, P::Future: Send + 'static, diff --git a/linkerd/app/outbound/src/endpoint.rs b/linkerd/app/outbound/src/endpoint.rs index 69cfe7e442..d7bc4dd82f 100644 --- a/linkerd/app/outbound/src/endpoint.rs +++ b/linkerd/app/outbound/src/endpoint.rs @@ -210,13 +210,9 @@ impl Outbound { where Self: Clone + 'static, S: svc::Service + Clone + Send + Sync + Unpin + 'static, - S::Response: tls::HasNegotiatedProtocol - + io::AsyncRead - + io::AsyncWrite - + Send - + Sync - + Unpin - + 'static, + S::Response: + tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, + S::Response: Sync + std::fmt::Debug, // Needed by `boring` S::Future: Send + Unpin, I: io::AsyncRead + io::AsyncWrite + io::PeerAddr, I: fmt::Debug + Send + Sync + Unpin + 'static, diff --git a/linkerd/app/outbound/src/logical.rs b/linkerd/app/outbound/src/logical.rs index 732e4bfaef..d9fde51070 100644 --- a/linkerd/app/outbound/src/logical.rs +++ b/linkerd/app/outbound/src/logical.rs @@ -119,13 +119,9 @@ impl Outbound { Self: Clone + 'static, C: Clone + Send + Sync + Unpin + 'static, C: svc::Service, - C::Response: tls::HasNegotiatedProtocol - + io::AsyncRead - + io::AsyncWrite - + Send - + Sync - + Unpin - + 'static, + C::Response: + tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, + C::Response: Sync + std::fmt::Debug, // Needed by `boring` C::Future: Send + Unpin, R: Clone + Send + 'static, R: Resolve + Sync, diff --git a/linkerd/app/outbound/src/tcp/connect.rs b/linkerd/app/outbound/src/tcp/connect.rs index 11e1b55b05..a5e775ca07 100644 --- a/linkerd/app/outbound/src/tcp/connect.rs +++ b/linkerd/app/outbound/src/tcp/connect.rs @@ -51,8 +51,9 @@ impl Outbound { + svc::Param> + svc::Param, C: svc::Service + Clone + Send + 'static, - C::Response: tls::HasNegotiatedProtocol, - C::Response: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, + C::Response: + tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, + C::Response: Sync + std::fmt::Debug, // Needed by `boring` C::Future: Send + 'static, { self.map_stack(|config, rt, connect| { diff --git a/linkerd/meshtls/boring/src/client.rs b/linkerd/meshtls/boring/src/client.rs index c2061c1abe..1ca01b3d3b 100644 --- a/linkerd/meshtls/boring/src/client.rs +++ b/linkerd/meshtls/boring/src/client.rs @@ -66,6 +66,8 @@ impl Connect { impl Service for Connect where I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, + // The boring error types don't implement error unless the socket is `Debug`. + I: std::fmt::Debug, { type Response = ClientIo; type Error = io::Error; @@ -85,8 +87,9 @@ where let io = tokio_boring::connect(config, id.as_str(), io) .await .map_err(|e| match e.as_io_error() { + // TODO(ver) boring should let us take ownership of the error directly. Some(ioe) => io::Error::new(ioe.kind(), ioe.to_string()), - None => io::Error::new(io::ErrorKind::Other, "unexpected TLS error"), + None => io::Error::new(io::ErrorKind::Other, e), })?; Ok(ClientIo(io)) }) diff --git a/linkerd/meshtls/build.rs b/linkerd/meshtls/build.rs index 222161cbc4..2036af8e63 100644 --- a/linkerd/meshtls/build.rs +++ b/linkerd/meshtls/build.rs @@ -1,6 +1,6 @@ fn main() -> Result<(), Box> { // Ensure that at least one TLS implementation feature is enabled. - static TLS_FEATURES: &[&str] = &["rustls"]; + static TLS_FEATURES: &[&str] = &["boring", "rustls"]; if !TLS_FEATURES .iter() .any(|f| std::env::var_os(&*format!("CARGO_FEATURE_{}", f.to_ascii_uppercase())).is_some()) diff --git a/linkerd/meshtls/src/client.rs b/linkerd/meshtls/src/client.rs index 6bd95fc515..ca9fd464a7 100644 --- a/linkerd/meshtls/src/client.rs +++ b/linkerd/meshtls/src/client.rs @@ -71,6 +71,8 @@ impl NewService for NewClient { impl Service for Connect where I: io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, + // XXX `boring` has these additional constraints: + I: Sync + std::fmt::Debug, { type Response = ClientIo; type Error = io::Error; @@ -79,7 +81,7 @@ where fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self { #[cfg(feature = "boring")] - Self::Boring(connect) => >::poll_ready(connect, cx), + Self::Boring(connect) => >::poll_ready(connect, cx), #[cfg(feature = "rustls")] Self::Rustls(connect) => >::poll_ready(connect, cx), @@ -111,7 +113,7 @@ where #[cfg(feature = "boring")] ConnectFutureProj::Boring(f) => { let res = futures::ready!(f.poll(cx)); - Poll::Ready(res.map(ClientIo::Rustls)) + Poll::Ready(res.map(ClientIo::Boring)) } #[cfg(feature = "rustls")] @@ -183,6 +185,9 @@ impl io::AsyncWrite for ClientIo { bufs: &[io::IoSlice<'_>], ) -> Poll> { match self.project() { + #[cfg(feature = "boring")] + ClientIoProj::Boring(io) => io.poll_write_vectored(cx, bufs), + #[cfg(feature = "rustls")] ClientIoProj::Rustls(io) => io.poll_write_vectored(cx, bufs), } @@ -191,6 +196,9 @@ impl io::AsyncWrite for ClientIo { #[inline] fn is_write_vectored(&self) -> bool { match self { + #[cfg(feature = "boring")] + Self::Boring(io) => io.is_write_vectored(), + #[cfg(feature = "rustls")] Self::Rustls(io) => io.is_write_vectored(), } diff --git a/linkerd/meshtls/src/creds.rs b/linkerd/meshtls/src/creds.rs index 06772d8918..09f9fc730a 100644 --- a/linkerd/meshtls/src/creds.rs +++ b/linkerd/meshtls/src/creds.rs @@ -2,6 +2,9 @@ use crate::{NewClient, Server}; use linkerd_error::Result; use linkerd_identity::{Credentials, DerX509, Name}; +#[cfg(feature = "boring")] +pub use crate::boring; + #[cfg(feature = "rustls")] pub use crate::rustls; @@ -27,6 +30,9 @@ pub enum Receiver { impl Credentials for Store { fn dns_name(&self) -> &Name { match self { + #[cfg(feature = "boring")] + Self::Boring(store) => store.dns_name(), + #[cfg(feature = "rustls")] Self::Rustls(store) => store.dns_name(), } @@ -34,6 +40,9 @@ impl Credentials for Store { fn gen_certificate_signing_request(&mut self) -> DerX509 { match self { + #[cfg(feature = "boring")] + Self::Boring(store) => store.gen_certificate_signing_request(), + #[cfg(feature = "rustls")] Self::Rustls(store) => store.gen_certificate_signing_request(), } @@ -46,6 +55,9 @@ impl Credentials for Store { expiry: std::time::SystemTime, ) -> Result<()> { match self { + #[cfg(feature = "boring")] + Self::Boring(store) => store.set_certificate(leaf, chain, expiry), + #[cfg(feature = "rustls")] Self::Rustls(store) => store.set_certificate(leaf, chain, expiry), } @@ -54,6 +66,13 @@ impl Credentials for Store { // === impl Receiver === +#[cfg(feature = "boring")] +impl From for Receiver { + fn from(rx: boring::creds::Receiver) -> Self { + Self::Boring(rx) + } +} + #[cfg(feature = "rustls")] impl From for Receiver { fn from(rx: rustls::creds::Receiver) -> Self { @@ -85,7 +104,7 @@ impl Receiver { pub fn server(&self) -> Server { match self { #[cfg(feature = "boring")] - Self::Boring(receiver) => Server::Rustls(receiver.server()), + Self::Boring(receiver) => Server::Boring(receiver.server()), #[cfg(feature = "rustls")] Self::Rustls(receiver) => Server::Rustls(receiver.server()), diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs index 81e85d46c0..f3e60baf22 100644 --- a/linkerd/meshtls/src/lib.rs +++ b/linkerd/meshtls/src/lib.rs @@ -12,11 +12,17 @@ pub use self::{ use linkerd_error::Result; use linkerd_identity::Name; +#[cfg(feature = "boring")] +pub use linkerd_meshtls_boring as boring; + #[cfg(feature = "rustls")] pub use linkerd_meshtls_rustls as rustls; #[derive(Copy, Clone, Debug)] pub enum Mode { + #[cfg(feature = "boring")] + Boring, + #[cfg(feature = "rustls")] Rustls, } @@ -30,6 +36,14 @@ impl Default for Mode { } } +// FIXME(ver) We should have a way to opt into boring by configuration when both are enabled. +#[cfg(all(feature = "boring", not(feature = "rustls")))] +impl Default for Mode { + fn default() -> Self { + Self::Boring + } +} + impl Mode { pub fn watch( self, diff --git a/linkerd/meshtls/src/server.rs b/linkerd/meshtls/src/server.rs index 38f997a6b5..d2627ffb72 100644 --- a/linkerd/meshtls/src/server.rs +++ b/linkerd/meshtls/src/server.rs @@ -79,6 +79,8 @@ impl Server { impl Service for Server where I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, + // XXX `boring` requires that the socket type implements `Debug` for its error types. + I: std::fmt::Debug, { type Response = (ServerTls, ServerIo); type Error = io::Error; @@ -87,7 +89,7 @@ where fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self { #[cfg(feature = "boring")] - Self::Boring(svc) => >::poll_ready(svc, cx), + Self::Boring(svc) => >::poll_ready(svc, cx), #[cfg(feature = "rustls")] Self::Rustls(svc) => >::poll_ready(svc, cx), @@ -98,7 +100,7 @@ where fn call(&mut self, io: I) -> Self::Future { match self { #[cfg(feature = "boring")] - Self::Boring(svc) => TerminateFuture::Rustls(svc.call(io)), + Self::Boring(svc) => TerminateFuture::Boring(svc.call(io)), #[cfg(feature = "rustls")] Self::Rustls(svc) => TerminateFuture::Rustls(svc.call(io)), @@ -119,7 +121,7 @@ where #[cfg(feature = "boring")] TerminateFutureProj::Boring(f) => { let res = futures::ready!(f.poll(cx)); - Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Rustls(io)))) + Poll::Ready(res.map(|(tls, io)| (tls, ServerIo::Boring(io)))) } #[cfg(feature = "rustls")] @@ -191,6 +193,9 @@ impl io::AsyncWrite for ServerIo { bufs: &[io::IoSlice<'_>], ) -> Poll> { match self.project() { + #[cfg(feature = "boring")] + ServerIoProj::Boring(io) => io.poll_write_vectored(cx, bufs), + #[cfg(feature = "rustls")] ServerIoProj::Rustls(io) => io.poll_write_vectored(cx, bufs), } From 79af20a9e8023daceb5a8e00640078247d60a398 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Thu, 4 Nov 2021 16:34:49 +0000 Subject: [PATCH 25/85] avoid needless constraints by using an obtuse error message with handshake errors --- linkerd/app/gateway/src/lib.rs | 1 - linkerd/app/outbound/src/endpoint.rs | 1 - linkerd/app/outbound/src/logical.rs | 1 - linkerd/app/outbound/src/tcp/connect.rs | 5 ++--- linkerd/io/src/boxed.rs | 6 +++--- linkerd/meshtls/boring/src/client.rs | 8 ++++---- linkerd/meshtls/boring/src/server.rs | 7 ++++--- linkerd/meshtls/src/client.rs | 2 -- linkerd/meshtls/src/server.rs | 2 -- 9 files changed, 13 insertions(+), 20 deletions(-) diff --git a/linkerd/app/gateway/src/lib.rs b/linkerd/app/gateway/src/lib.rs index 6690e8d02b..dfb9be0484 100644 --- a/linkerd/app/gateway/src/lib.rs +++ b/linkerd/app/gateway/src/lib.rs @@ -73,7 +73,6 @@ where O: svc::Service, O::Response: io::AsyncRead + io::AsyncWrite + tls::HasNegotiatedProtocol + Send + Unpin + 'static, - O::Response: Sync + std::fmt::Debug, // Needed by `boring`. O::Future: Send + Unpin + 'static, P: profiles::GetProfile + Clone + Send + Sync + Unpin + 'static, P::Future: Send + 'static, diff --git a/linkerd/app/outbound/src/endpoint.rs b/linkerd/app/outbound/src/endpoint.rs index d7bc4dd82f..e46e3ce62c 100644 --- a/linkerd/app/outbound/src/endpoint.rs +++ b/linkerd/app/outbound/src/endpoint.rs @@ -212,7 +212,6 @@ impl Outbound { S: svc::Service + Clone + Send + Sync + Unpin + 'static, S::Response: tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, - S::Response: Sync + std::fmt::Debug, // Needed by `boring` S::Future: Send + Unpin, I: io::AsyncRead + io::AsyncWrite + io::PeerAddr, I: fmt::Debug + Send + Sync + Unpin + 'static, diff --git a/linkerd/app/outbound/src/logical.rs b/linkerd/app/outbound/src/logical.rs index d9fde51070..8daa362a2c 100644 --- a/linkerd/app/outbound/src/logical.rs +++ b/linkerd/app/outbound/src/logical.rs @@ -121,7 +121,6 @@ impl Outbound { C: svc::Service, C::Response: tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, - C::Response: Sync + std::fmt::Debug, // Needed by `boring` C::Future: Send + Unpin, R: Clone + Send + 'static, R: Resolve + Sync, diff --git a/linkerd/app/outbound/src/tcp/connect.rs b/linkerd/app/outbound/src/tcp/connect.rs index a5e775ca07..11d88ab4d4 100644 --- a/linkerd/app/outbound/src/tcp/connect.rs +++ b/linkerd/app/outbound/src/tcp/connect.rs @@ -51,9 +51,8 @@ impl Outbound { + svc::Param> + svc::Param, C: svc::Service + Clone + Send + 'static, - C::Response: - tls::HasNegotiatedProtocol + io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, - C::Response: Sync + std::fmt::Debug, // Needed by `boring` + C::Response: tls::HasNegotiatedProtocol, + C::Response: io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, C::Future: Send + 'static, { self.map_stack(|config, rt, connect| { diff --git a/linkerd/io/src/boxed.rs b/linkerd/io/src/boxed.rs index 0840ea4f18..f28f12c225 100644 --- a/linkerd/io/src/boxed.rs +++ b/linkerd/io/src/boxed.rs @@ -10,14 +10,14 @@ pub struct BoxedIo(Pin>); /// This is necessary for `BoxedIo`, as `dyn AsyncRead + AsyncWrite + PeerAddr` /// is not a valid trait object. However, it needn't be public --- it's just /// used internally. -trait Io: AsyncRead + AsyncWrite + PeerAddr + Send + Sync {} +trait Io: AsyncRead + AsyncWrite + PeerAddr + Send {} -impl Io for I where I: AsyncRead + AsyncWrite + PeerAddr + Send + Sync {} +impl Io for I where I: AsyncRead + AsyncWrite + PeerAddr + Send {} impl BoxedIo { pub fn new(io: T) -> Self where - T: AsyncRead + AsyncWrite + PeerAddr + Send + Sync + Unpin + 'static, + T: AsyncRead + AsyncWrite + PeerAddr + Send + Unpin + 'static, { BoxedIo(Box::pin(io)) } diff --git a/linkerd/meshtls/boring/src/client.rs b/linkerd/meshtls/boring/src/client.rs index 1ca01b3d3b..04374ab9d9 100644 --- a/linkerd/meshtls/boring/src/client.rs +++ b/linkerd/meshtls/boring/src/client.rs @@ -65,9 +65,7 @@ impl Connect { impl Service for Connect where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, - // The boring error types don't implement error unless the socket is `Debug`. - I: std::fmt::Debug, + I: io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, { type Response = ClientIo; type Error = io::Error; @@ -89,7 +87,9 @@ where .map_err(|e| match e.as_io_error() { // TODO(ver) boring should let us take ownership of the error directly. Some(ioe) => io::Error::new(ioe.kind(), ioe.to_string()), - None => io::Error::new(io::ErrorKind::Other, e), + // XXX(ver) to use the boring error directly here we have to constraint the socket on Sync + + // std::fmt::Debug, which is a pain. + None => io::Error::new(io::ErrorKind::Other, "unexpected TLS handshake error"), })?; Ok(ClientIo(io)) }) diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index 69d9e633e7..9c57764eb2 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -44,7 +44,7 @@ impl Param for Server { impl Service for Server where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + std::fmt::Debug + 'static, + I: io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, { type Response = (ServerTls, ServerIo); type Error = std::io::Error; @@ -55,7 +55,6 @@ where io::Poll::Ready(Ok(())) } - #[inline] fn call(&mut self, io: I) -> Self::Future { let acc = (*self.rx.borrow()).clone(); Box::pin(async move { @@ -64,7 +63,9 @@ where .map(ServerIo) .map_err(|e| match e.as_io_error() { Some(ioe) => io::Error::new(ioe.kind(), ioe.to_string()), - None => io::Error::new(io::ErrorKind::Other, e), + // XXX(ver) to use the boring error directly here we have to constraint the + // socket on Sync + std::fmt::Debug, which is a pain. + None => io::Error::new(io::ErrorKind::Other, "unexpected TLS handshake error"), })?; let client_id = io.client_identity(); diff --git a/linkerd/meshtls/src/client.rs b/linkerd/meshtls/src/client.rs index ca9fd464a7..2f3821699d 100644 --- a/linkerd/meshtls/src/client.rs +++ b/linkerd/meshtls/src/client.rs @@ -71,8 +71,6 @@ impl NewService for NewClient { impl Service for Connect where I: io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, - // XXX `boring` has these additional constraints: - I: Sync + std::fmt::Debug, { type Response = ClientIo; type Error = io::Error; diff --git a/linkerd/meshtls/src/server.rs b/linkerd/meshtls/src/server.rs index d2627ffb72..98cde47bbc 100644 --- a/linkerd/meshtls/src/server.rs +++ b/linkerd/meshtls/src/server.rs @@ -79,8 +79,6 @@ impl Server { impl Service for Server where I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, - // XXX `boring` requires that the socket type implements `Debug` for its error types. - I: std::fmt::Debug, { type Response = (ServerTls, ServerIo); type Error = io::Error; From 420b71718fd5a040484a05c4bad6e4e672b09ff0 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Thu, 4 Nov 2021 16:37:45 +0000 Subject: [PATCH 26/85] more inline, less contraints --- linkerd/meshtls/src/client.rs | 3 +++ linkerd/meshtls/src/server.rs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/linkerd/meshtls/src/client.rs b/linkerd/meshtls/src/client.rs index 2f3821699d..a20b97e623 100644 --- a/linkerd/meshtls/src/client.rs +++ b/linkerd/meshtls/src/client.rs @@ -55,6 +55,7 @@ pub enum ClientIo { impl NewService for NewClient { type Service = Connect; + #[inline] fn new_service(&self, target: ClientTls) -> Self::Service { match self { #[cfg(feature = "boring")] @@ -76,6 +77,7 @@ where type Error = io::Error; type Future = ConnectFuture; + #[inline] fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self { #[cfg(feature = "boring")] @@ -106,6 +108,7 @@ where { type Output = io::Result>; + #[inline] fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { #[cfg(feature = "boring")] diff --git a/linkerd/meshtls/src/server.rs b/linkerd/meshtls/src/server.rs index 98cde47bbc..ebeceed5bb 100644 --- a/linkerd/meshtls/src/server.rs +++ b/linkerd/meshtls/src/server.rs @@ -78,12 +78,13 @@ impl Server { impl Service for Server where - I: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, + I: io::AsyncRead + io::AsyncWrite + Send + Unpin + 'static, { type Response = (ServerTls, ServerIo); type Error = io::Error; type Future = TerminateFuture; + #[inline] fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self { #[cfg(feature = "boring")] @@ -114,6 +115,7 @@ where { type Output = io::Result<(ServerTls, ServerIo)>; + #[inline] fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { #[cfg(feature = "boring")] From 730bc15afba03d7dbc538d50c51e8f2b829ff1d3 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 18:43:56 +0000 Subject: [PATCH 27/85] touchup alpn encoding, tests --- linkerd/meshtls/boring/src/lib.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index 5982227b58..c177bd0308 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -15,19 +15,21 @@ use linkerd_error::Result; /// /// `boring` requires that the list of protocols be encoded in the wire format. #[allow(dead_code)] -fn serialize_alpn(protos: &[Vec]) -> Result> { +fn serialize_alpn(protocols: &[Vec]) -> Result> { + // Allocate a buffer to hold the encoded protocols. let mut bytes = { // One additional byte for each protocol's length prefix. - let cap = protos.len() + protos.iter().map(|p| p.len()).sum::(); + let cap = protocols.len() + protocols.iter().map(Vec::len).sum::(); Vec::with_capacity(cap) }; - for proto in protos { - if proto.len() > 255 { - return Err("ALPN protocol must be less than 255 bytes".into()); + // Encode each protocol as a length-prefixed string. + for p in protocols { + if p.len() > 255 { + return Err("ALPN protocols must be less than 256 bytes".into()); } - bytes.push(proto.len() as u8); - bytes.extend(&*proto); + bytes.push(p.len() as u8); + bytes.extend(p); } Ok(bytes) @@ -41,4 +43,7 @@ fn test_serialize_alpn() { serialize_alpn(&[b"h2".to_vec(), b"http/1.1".to_vec()]).unwrap(), b"\x02h2\x08http/1.1" ); + + assert!(serialize_alpn(&[(0..255).collect()]).is_ok()); + assert!(serialize_alpn(&[(0..=255).collect()]).is_err()); } From 88e5cf0d2323e7b9debd2dd61a3c45f3c27dcfa4 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 18:52:35 +0000 Subject: [PATCH 28/85] Support a set of trust roots --- linkerd/meshtls/boring/src/creds.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index fe9f3c694a..4d96d084eb 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -18,10 +18,11 @@ pub fn watch( csr: &[u8], ) -> Result<(Store, Receiver)> { let roots = { + let certs = X509::stack_from_pem(roots_pem.as_bytes())? let mut store = X509StoreBuilder::new()?; - // FIXME(ver) This should handle a list of PEM-encoded certificates. - let cert = X509::from_pem(roots_pem.as_bytes())?; - store.add_cert(cert)?; + for c in certs.into_iter() { + store.add_cert(c)?; + } store.build() }; From cbf2279fb89e9e75324c9bfa531ee40f82993861 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 19:20:35 +0000 Subject: [PATCH 29/85] fixup! Support a set of trust roots --- linkerd/meshtls/boring/src/creds.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 4d96d084eb..62b2c6cef5 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -18,7 +18,7 @@ pub fn watch( csr: &[u8], ) -> Result<(Store, Receiver)> { let roots = { - let certs = X509::stack_from_pem(roots_pem.as_bytes())? + let certs = X509::stack_from_pem(roots_pem.as_bytes())?; let mut store = X509StoreBuilder::new()?; for c in certs.into_iter() { store.add_cert(c)?; From 4fe82b2399cdc064b55d21e828e341f9c598a6c2 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 19:24:36 +0000 Subject: [PATCH 30/85] nit --- linkerd/meshtls/boring/src/creds/store.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linkerd/meshtls/boring/src/creds/store.rs b/linkerd/meshtls/boring/src/creds/store.rs index b0c9f74d28..78e9780f7a 100644 --- a/linkerd/meshtls/boring/src/creds/store.rs +++ b/linkerd/meshtls/boring/src/creds/store.rs @@ -88,7 +88,7 @@ impl id::Credentials for Store { let mut chain = boring::stack::Stack::new()?; chain.push(cert.clone())?; - for id::DerX509(der) in intermediates.iter() { + for id::DerX509(der) in &intermediates { let cert = X509::from_der(der)?; chain.push(cert)?; } @@ -104,7 +104,7 @@ impl id::Credentials for Store { b.set_private_key(self.key.as_ref())?; b.set_cert_store(self.clone_roots()?); b.set_certificate(cert.as_ref())?; - for id::DerX509(der) in intermediates.iter() { + for id::DerX509(der) in &intermediates { let cert = X509::from_der(der)?; b.add_extra_chain_cert(cert)?; } @@ -118,7 +118,7 @@ impl id::Credentials for Store { b.set_private_key(self.key.as_ref())?; b.set_cert_store(self.clone_roots()?); b.set_certificate(cert.as_ref())?; - for id::DerX509(der) in intermediates.iter() { + for id::DerX509(der) in &intermediates { let cert = X509::from_der(der)?; b.add_extra_chain_cert(cert)?; } From 4e12638b9142974f08313d5b49a0c5c5d2bea2ff Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 19:35:25 +0000 Subject: [PATCH 31/85] configure the initial connector with the trust roots --- linkerd/meshtls/boring/src/creds.rs | 19 ++++++++++++++++++- linkerd/meshtls/boring/src/creds/store.rs | 15 ++------------- linkerd/meshtls/boring/src/lib.rs | 1 + 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 62b2c6cef5..3c5df7e6b5 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -29,7 +29,9 @@ pub fn watch( let key = PKey::private_key_from_pkcs8(key_pkcs8)?; let (client_tx, client_rx) = { - let conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; + let mut conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; + let roots = clone_roots(&roots)?; + conn.set_cert_store(roots); watch::channel(conn.build()) }; let (server_tx, server_rx) = { @@ -41,3 +43,18 @@ pub fn watch( Ok((store, rx)) } + +/// Duplicates an `X509Store`. +/// +/// For unknown reasons, `X509Store` does not implement `Clone`. What this function presupposes is: +/// but, what if it did? +fn clone_roots(orig: &boring::x509::store::X509Store) -> Result { + // X509Store does not implement clone, so we need to manually copy it. + let mut roots = boring::x509::store::X509StoreBuilder::new()?; + for obj in orig.objects() { + if let Some(c) = obj.x509() { + roots.add_cert(c.to_owned())?; + } + } + Ok(roots.build()) +} diff --git a/linkerd/meshtls/boring/src/creds/store.rs b/linkerd/meshtls/boring/src/creds/store.rs index 78e9780f7a..9d928d62e5 100644 --- a/linkerd/meshtls/boring/src/creds/store.rs +++ b/linkerd/meshtls/boring/src/creds/store.rs @@ -50,17 +50,6 @@ impl Store { false } - - fn clone_roots(&self) -> Result { - // X509Store does not implement clone, so we need to manually copy it. - let mut roots = boring::x509::store::X509StoreBuilder::new()?; - for obj in self.roots.objects() { - if let Some(c) = obj.x509() { - roots.add_cert(c.to_owned())?; - } - } - Ok(roots.build()) - } } impl id::Credentials for Store { @@ -102,7 +91,7 @@ impl id::Credentials for Store { // FIXME(ver) Restrict TLS version, algorithms, etc. let mut b = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; b.set_private_key(self.key.as_ref())?; - b.set_cert_store(self.clone_roots()?); + b.set_cert_store(super::clone_roots(&self.roots)?); b.set_certificate(cert.as_ref())?; for id::DerX509(der) in &intermediates { let cert = X509::from_der(der)?; @@ -116,7 +105,7 @@ impl id::Credentials for Store { // TODO(ver) Ensure that this configuration includes FIPS-approved algorithms. let mut b = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; b.set_private_key(self.key.as_ref())?; - b.set_cert_store(self.clone_roots()?); + b.set_cert_store(super::clone_roots(&self.roots)?); b.set_certificate(cert.as_ref())?; for id::DerX509(der) in &intermediates { let cert = X509::from_der(der)?; diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index c177bd0308..53d4fff544 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -13,6 +13,7 @@ use linkerd_error::Result; /// Encodes a list of ALPN protocols into a slice of bytes. /// +/// /// `boring` requires that the list of protocols be encoded in the wire format. #[allow(dead_code)] fn serialize_alpn(protocols: &[Vec]) -> Result> { From d578b2e57e18eb6aa5fab23a08188c419f20c310 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 19:49:32 +0000 Subject: [PATCH 32/85] comments --- linkerd/meshtls/boring/src/creds/store.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/linkerd/meshtls/boring/src/creds/store.rs b/linkerd/meshtls/boring/src/creds/store.rs index 9d928d62e5..0b348ecbbe 100644 --- a/linkerd/meshtls/boring/src/creds/store.rs +++ b/linkerd/meshtls/boring/src/creds/store.rs @@ -88,7 +88,7 @@ impl id::Credentials for Store { } let conn = { - // FIXME(ver) Restrict TLS version, algorithms, etc. + // TODO(ver) Restrict TLS version, algorithms, etc. let mut b = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; b.set_private_key(self.key.as_ref())?; b.set_cert_store(super::clone_roots(&self.roots)?); @@ -102,7 +102,7 @@ impl id::Credentials for Store { let acc = { // mozilla_intermediate_v5 is the only variant that enables TLSv1.3, so we use that. - // TODO(ver) Ensure that this configuration includes FIPS-approved algorithms. + // TODO(ver) We should set explicit TLS versions, algorithms, etc. let mut b = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; b.set_private_key(self.key.as_ref())?; b.set_cert_store(super::clone_roots(&self.roots)?); @@ -116,6 +116,8 @@ impl id::Credentials for Store { b.build() }; + // If receivers are dropped, we don't return an error (as this would likely cause the + // updater to retry more aggressively). It's fine to silently ignore these errors. let _ = self.server_tx.send(acc); let _ = self.client_tx.send(conn); From 4be2403921d0a3541203d36b4c4bd80baeb35bec Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 23:10:09 +0000 Subject: [PATCH 33/85] Create a separate boring CI workflow --- .github/workflows/boring.yml | 36 ++++++++++++++++++++++++++++++++++++ .github/workflows/check.yml | 8 ++++---- .github/workflows/test.yml | 4 ++-- 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/boring.yml diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml new file mode 100644 index 0000000000..310f46ed31 --- /dev/null +++ b/.github/workflows/boring.yml @@ -0,0 +1,36 @@ +name: boring + +on: + pull_request: {} + +env: + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + RUST_BACKTRACE: short + RUSTUP_MAX_RETRIES: 10 + +permissions: + contents: read + +jobs: + + # Linting + boring-clippy: + timeout-minutes: 10 + runs-on: ubuntu-latest + container: + image: docker://rust:1.56.0-buster + steps: + - run: apt update && apt install -y cmake clang golang + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - run: rustup component add clippy + - run: cargo clippy --all --exclude linkerd-meshtls-boring + + boring-test: + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - run: apt update && apt install -y cmake clang golang + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - run: cargo test --package=linkerd-meshtls-boring + diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 66a00a2c33..49cdd5f10b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -21,10 +21,9 @@ jobs: container: image: docker://rust:1.56.0-buster steps: - - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - - run: make lint + - run: cargo clippy --all --exclude linkerd-meshtls-boring # Iterate through all (non-fuzzer) sub-crates to ensure each compiles independently. check-each-crate: @@ -33,10 +32,11 @@ jobs: container: image: docker://rust:1.56.0-buster steps: - - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: | - for d in $(for toml in $(find . -mindepth 2 -name Cargo.toml -not -path '*/fuzz/*') ; do echo ${toml%/*} ; done | sort -r ) ; do + for toml in $(find . -mindepth 2 -name Cargo.toml -not -path '*/fuzz/*' -and -not -path linkerd/meshtls/boring/\* | sort -r) + do + d="${toml%/*}" echo "# $d" (cd $d ; cargo check --all-targets) done diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 77bb9f68cb..a65da0fe66 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,8 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cargo test --all --exclude=linkerd-app-integration --no-run - - run: cargo test --all --exclude=linkerd-app-integration + - run: cargo test --all --exclude=linkerd-app-integration --exclude=linkerd-meshtls-boring --no-run + - run: cargo test --all --exclude=linkerd-app-integration --exclude=linkerd-meshtls-boring # Run only the integration tests. These have the potential to be flakey as they depend on opening # sockets and may have timing sensitivity. From d1a777f9206062d2e2d90c38c946b7f6870fcef6 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 23:13:56 +0000 Subject: [PATCH 34/85] fixup! Create a separate boring CI workflow --- .github/workflows/boring.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml index 310f46ed31..dea863e41f 100644 --- a/.github/workflows/boring.yml +++ b/.github/workflows/boring.yml @@ -29,6 +29,8 @@ jobs: boring-test: timeout-minutes: 15 runs-on: ubuntu-latest + container: + image: docker://rust:1.56.0-buster steps: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 From 6dde3d5a1b9fc2303b3718f2e6a98fd31e6a4c0a Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 5 Nov 2021 23:43:05 +0000 Subject: [PATCH 35/85] fixup! fixup! Create a separate boring CI workflow --- .github/workflows/boring.yml | 4 ++-- .github/workflows/check.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml index dea863e41f..3f3b00a57e 100644 --- a/.github/workflows/boring.yml +++ b/.github/workflows/boring.yml @@ -24,7 +24,7 @@ jobs: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - - run: cargo clippy --all --exclude linkerd-meshtls-boring + - run: cd linkerd-meshtls-boring && cargo clippy boring-test: timeout-minutes: 15 @@ -34,5 +34,5 @@ jobs: steps: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cargo test --package=linkerd-meshtls-boring + - run: cd linkerd-meshtls-boring && cargo test diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 49cdd5f10b..c5cf901a32 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -34,7 +34,7 @@ jobs: steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: | - for toml in $(find . -mindepth 2 -name Cargo.toml -not -path '*/fuzz/*' -and -not -path linkerd/meshtls/boring/\* | sort -r) + for toml in $(find . -mindepth 2 -name Cargo.toml -not -path '*/fuzz/*' -and -not -path './linkerd/meshtls/boring/*' | sort -r) do d="${toml%/*}" echo "# $d" From 3a86113285d670586dc954a49d012fb80d6c7a8d Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 05:15:22 +0000 Subject: [PATCH 36/85] fmt --- .github/workflows/boring.yml | 4 +++- .github/workflows/check.yml | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml index 3f3b00a57e..9a3eb6f47c 100644 --- a/.github/workflows/boring.yml +++ b/.github/workflows/boring.yml @@ -24,7 +24,9 @@ jobs: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - - run: cd linkerd-meshtls-boring && cargo clippy + - run: | + cd linkerd-meshtls-boring + cargo clippy --all-targets boring-test: timeout-minutes: 15 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index c5cf901a32..44b4f12a6c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -34,7 +34,11 @@ jobs: steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: | - for toml in $(find . -mindepth 2 -name Cargo.toml -not -path '*/fuzz/*' -and -not -path './linkerd/meshtls/boring/*' | sort -r) + for toml in $(find . -mindepth 2 \ + -not -path '*/fuzz/*' \ + -not -path './linkerd/meshtls/boring/*' \ + -name Cargo.toml \ + | sort -r) do d="${toml%/*}" echo "# $d" From 1f15b4a3f68ec9609f9ce30187b76b805e57538e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 05:20:56 +0000 Subject: [PATCH 37/85] use working-directory --- .github/workflows/boring.yml | 9 ++++----- .github/workflows/fuzzers.yml | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml index 9a3eb6f47c..c706aaed24 100644 --- a/.github/workflows/boring.yml +++ b/.github/workflows/boring.yml @@ -24,9 +24,8 @@ jobs: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - - run: | - cd linkerd-meshtls-boring - cargo clippy --all-targets + - working-directory: linkerd-meshtls-boring + run: cargo clippy --all-targets boring-test: timeout-minutes: 15 @@ -36,5 +35,5 @@ jobs: steps: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cd linkerd-meshtls-boring && cargo test - + - working-directory: linkerd-meshtls-boring + run: cargo test diff --git a/.github/workflows/fuzzers.yml b/.github/workflows/fuzzers.yml index b5f0ff2fbb..f4acf8ac9b 100644 --- a/.github/workflows/fuzzers.yml +++ b/.github/workflows/fuzzers.yml @@ -35,5 +35,5 @@ jobs: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup toolchain add nightly - run: cargo install cargo-fuzz - # Iterate through all fuzz crates to ensure each compiles independently. - - run: cd linkerd/${{matrix.dir}}/fuzz && cargo +nightly fuzz build + - working-directory: linkerd/${{matrix.dir}}/fuzz + run: cargo +nightly fuzz build From 6ce6be0d701ddc43683cfd960cceaf2ab310193f Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 05:25:02 +0000 Subject: [PATCH 38/85] fixup boringdirectory --- .github/workflows/boring.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml index c706aaed24..082ae0da44 100644 --- a/.github/workflows/boring.yml +++ b/.github/workflows/boring.yml @@ -24,7 +24,7 @@ jobs: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - - working-directory: linkerd-meshtls-boring + - working-directory: linkerd/meshtls/boring run: cargo clippy --all-targets boring-test: @@ -35,5 +35,5 @@ jobs: steps: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - working-directory: linkerd-meshtls-boring + - working-directory: linkerd/meshtls/boring run: cargo test From 5418539127ae5764804f0a1992c9b56311a41359 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 06:04:05 +0000 Subject: [PATCH 39/85] Update dockerfile to support boring when the feature is enabled --- Dockerfile | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 898c7f395f..7627d42797 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,23 +32,29 @@ FROM $RUST_IMAGE as build ARG PROXY_UNOPTIMIZED # Controls what features are enabled in the proxy. -ARG PROXY_FEATURES +ARG PROXY_FEATURES="multicore,meshtls-rustls" RUN --mount=type=cache,target=/var/lib/apt/lists \ - --mount=type=cache,target=/var/tmp \ - apt update && apt install -y time cmake + --mount=type=cache,target=/var/tmp \ + apt update && apt install -y time + +RUN --mount=type=cache,target=/var/lib/apt/lists \ + --mount=type=cache,target=/var/tmp \ + if $(echo "$PROXY_FEATURES" | grep "meshtls-boring" >/dev/null); then \ + apt install -y cmake clang golang ; \ + fi WORKDIR /usr/src/linkerd2-proxy COPY . . RUN --mount=type=cache,target=target \ - --mount=type=cache,from=rust:1.56.0-buster,source=/usr/local/cargo,target=/usr/local/cargo \ + --mount=type=cache,from=rust:1.56.0-buster,source=/usr/local/cargo,target=/usr/local/cargo \ mkdir -p /out && \ if [ -n "$PROXY_UNOPTIMIZED" ]; then \ - (cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --features="$PROXY_FEATURES") && \ - mv target/debug/linkerd2-proxy /out/linkerd2-proxy ; \ + (cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --no-default-features --features="$PROXY_FEATURES") && \ + mv target/debug/linkerd2-proxy /out/linkerd2-proxy ; \ else \ - (cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --release --features="$PROXY_FEATURES") && \ - mv target/release/linkerd2-proxy /out/linkerd2-proxy ; \ + (cd linkerd2-proxy && /usr/bin/time -v cargo build --locked --no-default-features --features="$PROXY_FEATURES" --release) && \ + mv target/release/linkerd2-proxy /out/linkerd2-proxy ; \ fi ## Install the proxy binary into the base runtime image. @@ -61,8 +67,9 @@ ARG SKIP_IDENTITY_WRAPPER WORKDIR /linkerd COPY --from=build /out/linkerd2-proxy /usr/lib/linkerd/linkerd2-proxy ENV LINKERD2_PROXY_LOG=warn,linkerd=info -RUN if [ -n "$SKIP_IDENTITY_WRAPPER" ] ; then \ - rm -f /usr/bin/linkerd2-proxy-run && \ - ln /usr/lib/linkerd/linkerd2-proxy /usr/bin/linkerd2-proxy-run ; \ +RUN \ + if [ -n "$SKIP_IDENTITY_WRAPPER" ] ; then \ + rm -f /usr/bin/linkerd2-proxy-run && \ + ln /usr/lib/linkerd/linkerd2-proxy /usr/bin/linkerd2-proxy-run ; \ fi # Inherits the ENTRYPOINT from the runtime image. From cf51441c359903bb19cc85dd6d8160424c91a890 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 15:05:32 +0000 Subject: [PATCH 40/85] avoid panicking on ALPN initialization (for testing) --- linkerd/meshtls/boring/src/client.rs | 3 ++- linkerd/meshtls/boring/src/server.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/linkerd/meshtls/boring/src/client.rs b/linkerd/meshtls/boring/src/client.rs index 04374ab9d9..7d8bf499cb 100644 --- a/linkerd/meshtls/boring/src/client.rs +++ b/linkerd/meshtls/boring/src/client.rs @@ -52,7 +52,8 @@ impl Connect { if let Some(AlpnProtocols(protocols)) = client_tls.alpn { if !protocols.is_empty() { - todo!("support ALPN") + // todo!("support ALPN") + tracing::warn!("ALPN not supported"); } } diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index 9c57764eb2..28aae94799 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -32,7 +32,9 @@ impl Server { return Ok(self); } - todo!("support ALPN") + // todo!("support ALPN") + tracing::warn!("ALPN not supported"); + Ok(self) } } From 88c32bd567c27f6df15ca7f7c1d7d009c87c17d7 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 17:50:40 +0000 Subject: [PATCH 41/85] Add support for ALPN --- linkerd/app/inbound/src/direct.rs | 2 +- linkerd/meshtls/boring/src/client.rs | 34 ++-- linkerd/meshtls/boring/src/creds.rs | 159 +++++++++++++++---- linkerd/meshtls/boring/src/creds/receiver.rs | 22 +-- linkerd/meshtls/boring/src/creds/store.rs | 96 ++++------- linkerd/meshtls/boring/src/lib.rs | 39 ----- linkerd/meshtls/boring/src/server.rs | 35 ++-- linkerd/meshtls/src/server.rs | 7 +- 8 files changed, 209 insertions(+), 185 deletions(-) diff --git a/linkerd/app/inbound/src/direct.rs b/linkerd/app/inbound/src/direct.rs index e0b8442c58..f55b11e5c9 100644 --- a/linkerd/app/inbound/src/direct.rs +++ b/linkerd/app/inbound/src/direct.rs @@ -97,7 +97,7 @@ impl Inbound { let identity = rt .identity .server() - .spawn_with_alpn(vec![transport_header::PROTOCOL.into()]) + .with_alpn(vec![transport_header::PROTOCOL.into()]) .expect("TLS credential store must be held"); inner diff --git a/linkerd/meshtls/boring/src/client.rs b/linkerd/meshtls/boring/src/client.rs index 7d8bf499cb..6876fdaf4c 100644 --- a/linkerd/meshtls/boring/src/client.rs +++ b/linkerd/meshtls/boring/src/client.rs @@ -1,20 +1,20 @@ -use boring::ssl; +use crate::creds::CredsRx; use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{NewService, Service}; use linkerd_tls::{ client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerId, }; -use std::{future::Future, pin::Pin, task::Context}; -use tokio::sync::watch; +use std::{future::Future, pin::Pin, sync::Arc, task::Context}; #[derive(Clone)] -pub struct NewClient(watch::Receiver); +pub struct NewClient(CredsRx); #[derive(Clone)] pub struct Connect { + rx: CredsRx, + alpn: Option]>>, server_id: Name, - connector: ssl::SslConnector, } pub type ConnectFuture = Pin>> + Send>>; @@ -25,7 +25,7 @@ pub struct ClientIo(tokio_boring::SslStream); // === impl NewClient === impl NewClient { - pub(crate) fn new(rx: watch::Receiver) -> Self { + pub(crate) fn new(rx: CredsRx) -> Self { Self(rx) } } @@ -34,7 +34,7 @@ impl NewService for NewClient { type Service = Connect; fn new_service(&self, target: ClientTls) -> Self::Service { - Connect::new(target, (*self.0.borrow()).clone()) + Connect::new(target, self.0.clone()) } } @@ -47,19 +47,13 @@ impl std::fmt::Debug for NewClient { // === impl Connect === impl Connect { - pub(crate) fn new(client_tls: ClientTls, connector: ssl::SslConnector) -> Self { + pub(crate) fn new(client_tls: ClientTls, rx: CredsRx) -> Self { let ServerId(server_id) = client_tls.server_id; - - if let Some(AlpnProtocols(protocols)) = client_tls.alpn { - if !protocols.is_empty() { - // todo!("support ALPN") - tracing::warn!("ALPN not supported"); - } - } - + let alpn = client_tls.alpn.map(|AlpnProtocols(ps)| ps.into()); Self { + rx, + alpn, server_id, - connector, } } } @@ -77,9 +71,13 @@ where } fn call(&mut self, io: I) -> Self::Future { - let conn = self.connector.clone(); let id = self.server_id.clone(); + let connector = match &self.alpn { + None => self.rx.borrow().connector(&[]), + Some(alpn) => self.rx.borrow().connector(alpn), + }; Box::pin(async move { + let conn = connector.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let config = conn .configure() .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 3c5df7e6b5..582e2267df 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -3,12 +3,13 @@ mod store; pub use self::{receiver::Receiver, store::Store}; use boring::{ - pkey::PKey, + pkey::{PKey, Private}, ssl, x509::{store::X509StoreBuilder, X509}, }; use linkerd_error::Result; use linkerd_identity as id; +use std::sync::Arc; use tokio::sync::watch; pub fn watch( @@ -17,44 +18,142 @@ pub fn watch( key_pkcs8: &[u8], csr: &[u8], ) -> Result<(Store, Receiver)> { - let roots = { - let certs = X509::stack_from_pem(roots_pem.as_bytes())?; - let mut store = X509StoreBuilder::new()?; - for c in certs.into_iter() { - store.add_cert(c)?; - } - store.build() + let creds = { + let roots = X509::stack_from_pem(roots_pem.as_bytes())?; + let key = PKey::private_key_from_pkcs8(key_pkcs8)?; + Arc::new(BaseCreds { roots, key }) }; - let key = PKey::private_key_from_pkcs8(key_pkcs8)?; + let (tx, rx) = watch::channel(Creds::from(creds.clone())); + let rx = Receiver::new(identity.clone(), rx); + let store = Store::new(creds, csr, identity, tx); + + Ok((store, rx)) +} + +pub(crate) struct Creds { + base: Arc, + certs: Option, +} + +struct BaseCreds { + roots: Vec, + key: PKey, +} + +struct Certs { + leaf: X509, + intermediates: Vec, +} + +pub(crate) type CredsRx = watch::Receiver; + +type CredsTx = watch::Sender; + +// === impl Creds === + +impl From> for Creds { + fn from(base: Arc) -> Self { + Creds { base, certs: None } + } +} + +impl Creds { + // TODO(ver) Restrict TLS version, algorithms, etc. + pub(crate) fn acceptor(&self, alpn_protocols: &[Vec]) -> Result { + // mozilla_intermediate_v5 is the only variant that enables TLSv1.3, so we use that. + // TODO(ver) We should set explicit TLS versions, algorithms, etc. + let mut conn = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; + + let roots = self.root_store()?; + conn.set_cert_store(roots); + + if let Some(certs) = &self.certs { + conn.set_private_key(&self.base.key)?; + conn.set_certificate(&certs.leaf)?; + for c in &certs.intermediates { + conn.add_extra_chain_cert(c.to_owned())?; + } + } + + if !alpn_protocols.is_empty() { + let p = serialize_alpn(alpn_protocols)?; + conn.set_alpn_protos(&*p)?; + } + + Ok(conn.build()) + } - let (client_tx, client_rx) = { + // TODO(ver) Restrict TLS version, algorithms, etc. + pub(crate) fn connector(&self, alpn_protocols: &[Vec]) -> Result { + // XXX(ver) This function reads from the environment and/or the filesystem. This likely is + // at best wasteful and at worst unsafe (if another thread were to mutate these environment + // variables simultaneously, for instance). Unfortunately, the boring APIs don't really give + // us an alternative AFAICT. let mut conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; - let roots = clone_roots(&roots)?; + + let roots = self.root_store()?; conn.set_cert_store(roots); - watch::channel(conn.build()) - }; - let (server_tx, server_rx) = { - let acc = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; - watch::channel(acc.build()) - }; - let rx = Receiver::new(identity.clone(), client_rx, server_rx); - let store = Store::new(roots, key, csr, identity, client_tx, server_tx); - Ok((store, rx)) + if let Some(certs) = &self.certs { + conn.set_private_key(&self.base.key)?; + conn.set_certificate(&certs.leaf)?; + for c in &certs.intermediates { + conn.add_extra_chain_cert(c.to_owned())?; + } + } + + if !alpn_protocols.is_empty() { + let p = serialize_alpn(alpn_protocols)?; + conn.set_alpn_protos(&*p)?; + } + + Ok(conn.build()) + } + + fn root_store(&self) -> Result { + let mut store = X509StoreBuilder::new()?; + for c in &self.base.roots { + store.add_cert(c.to_owned())?; + } + + Ok(store.build()) + } } -/// Duplicates an `X509Store`. +/// Encodes a list of ALPN protocols into a slice of bytes. /// -/// For unknown reasons, `X509Store` does not implement `Clone`. What this function presupposes is: -/// but, what if it did? -fn clone_roots(orig: &boring::x509::store::X509Store) -> Result { - // X509Store does not implement clone, so we need to manually copy it. - let mut roots = boring::x509::store::X509StoreBuilder::new()?; - for obj in orig.objects() { - if let Some(c) = obj.x509() { - roots.add_cert(c.to_owned())?; +/// `boring` requires that the list of protocols be encoded in the wire format. +#[allow(dead_code)] +fn serialize_alpn(protocols: &[Vec]) -> Result> { + // Allocate a buffer to hold the encoded protocols. + let mut bytes = { + // One additional byte for each protocol's length prefix. + let cap = protocols.len() + protocols.iter().map(Vec::len).sum::(); + Vec::with_capacity(cap) + }; + + // Encode each protocol as a length-prefixed string. + for p in protocols { + if p.len() > 255 { + return Err("ALPN protocols must be less than 256 bytes".into()); } + bytes.push(p.len() as u8); + bytes.extend(p); } - Ok(roots.build()) + + Ok(bytes) +} + +#[cfg(test)] +#[test] +fn test_serialize_alpn() { + assert_eq!(serialize_alpn(&[b"h2".to_vec()]).unwrap(), b"\x02h2"); + assert_eq!( + serialize_alpn(&[b"h2".to_vec(), b"http/1.1".to_vec()]).unwrap(), + b"\x02h2\x08http/1.1" + ); + + assert!(serialize_alpn(&[(0..255).collect()]).is_ok()); + assert!(serialize_alpn(&[(0..=255).collect()]).is_err()); } diff --git a/linkerd/meshtls/boring/src/creds/receiver.rs b/linkerd/meshtls/boring/src/creds/receiver.rs index 82a3d8c68d..9e78dea160 100644 --- a/linkerd/meshtls/boring/src/creds/receiver.rs +++ b/linkerd/meshtls/boring/src/creds/receiver.rs @@ -1,26 +1,16 @@ +use super::CredsRx; use crate::{NewClient, Server}; -use boring::ssl; use linkerd_identity::Name; -use tokio::sync::watch; #[derive(Clone)] pub struct Receiver { name: Name, - client_rx: watch::Receiver, - server_rx: watch::Receiver, + rx: CredsRx, } impl Receiver { - pub(crate) fn new( - name: Name, - client_rx: watch::Receiver, - server_rx: watch::Receiver, - ) -> Self { - Self { - name, - client_rx, - server_rx, - } + pub(crate) fn new(name: Name, rx: CredsRx) -> Self { + Self { name, rx } } /// Returns the local identity. @@ -30,12 +20,12 @@ impl Receiver { /// Returns a `NewClient` that can be used to establish TLS on client connections. pub fn new_client(&self) -> NewClient { - NewClient::new(self.client_rx.clone()) + NewClient::new(self.rx.clone()) } /// Returns a `Server` that can be used to terminate TLS on server connections. pub fn server(&self) -> Server { - Server::new(self.name.clone(), self.server_rx.clone()) + Server::new(self.name.clone(), self.rx.clone()) } } diff --git a/linkerd/meshtls/boring/src/creds/store.rs b/linkerd/meshtls/boring/src/creds/store.rs index 0b348ecbbe..bdabc7dbad 100644 --- a/linkerd/meshtls/boring/src/creds/store.rs +++ b/linkerd/meshtls/boring/src/creds/store.rs @@ -1,39 +1,25 @@ -use boring::{ - pkey::{PKey, Private}, - ssl, - x509::{store::X509Store, X509StoreContext, X509}, -}; +use super::{BaseCreds, Certs, Creds, CredsTx}; +use boring::x509::{X509StoreContext, X509}; use linkerd_error::Result; use linkerd_identity as id; -use tokio::sync::watch; +use std::sync::Arc; pub struct Store { - roots: X509Store, - key: PKey, + creds: Arc, csr: Vec, name: id::Name, - client_tx: watch::Sender, - server_tx: watch::Sender, + tx: CredsTx, } // === impl Store === impl Store { - pub(super) fn new( - roots: X509Store, - key: PKey, - csr: &[u8], - name: id::Name, - client_tx: watch::Sender, - server_tx: watch::Sender, - ) -> Self { + pub(super) fn new(creds: Arc, csr: &[u8], name: id::Name, tx: CredsTx) -> Self { Self { - roots, - key, + creds, csr: csr.into(), name, - client_tx, - server_tx, + tx, } } @@ -70,56 +56,42 @@ impl id::Credentials for Store { intermediates: Vec, _expiry: std::time::SystemTime, ) -> Result<()> { - let cert = X509::from_der(&leaf)?; - if !self.cert_matches_name(&cert) { + let leaf = X509::from_der(&leaf)?; + if !self.cert_matches_name(&leaf) { return Err("certificate does not have a DNS name SAN for the local identity".into()); } - let mut chain = boring::stack::Stack::new()?; - chain.push(cert.clone())?; - for id::DerX509(der) in &intermediates { - let cert = X509::from_der(der)?; - chain.push(cert)?; - } + let intermediates = intermediates + .into_iter() + .map(|id::DerX509(der)| X509::from_der(&der).map_err(Into::into)) + .collect::>>()?; - let mut context = X509StoreContext::new()?; - if !context.init(&self.roots, &cert, &chain, |c| c.verify_cert())? { - return Err("certificate could not be validated against the trust chain".into()); - } - - let conn = { - // TODO(ver) Restrict TLS version, algorithms, etc. - let mut b = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; - b.set_private_key(self.key.as_ref())?; - b.set_cert_store(super::clone_roots(&self.roots)?); - b.set_certificate(cert.as_ref())?; - for id::DerX509(der) in &intermediates { - let cert = X509::from_der(der)?; - b.add_extra_chain_cert(cert)?; - } - b.build() + let creds = Creds { + base: self.creds.clone(), + certs: Some(Certs { + leaf, + intermediates, + }), }; - let acc = { - // mozilla_intermediate_v5 is the only variant that enables TLSv1.3, so we use that. - // TODO(ver) We should set explicit TLS versions, algorithms, etc. - let mut b = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; - b.set_private_key(self.key.as_ref())?; - b.set_cert_store(super::clone_roots(&self.roots)?); - b.set_certificate(cert.as_ref())?; - for id::DerX509(der) in &intermediates { - let cert = X509::from_der(der)?; - b.add_extra_chain_cert(cert)?; - } - b.set_verify(ssl::SslVerifyMode::PEER); - b.check_private_key()?; - b.build() + let mut context = X509StoreContext::new()?; + let roots = creds.root_store()?; + + let mut chain = boring::stack::Stack::new()?; + for i in &creds.certs.as_ref().unwrap().intermediates { + chain.push(i.to_owned())?; + } + let init = { + let leaf = &creds.certs.as_ref().unwrap().leaf; + context.init(&roots, leaf, &chain, |c| c.verify_cert())? }; + if !init { + return Err("certificate could not be validated against the trust chain".into()); + } // If receivers are dropped, we don't return an error (as this would likely cause the // updater to retry more aggressively). It's fine to silently ignore these errors. - let _ = self.server_tx.send(acc); - let _ = self.client_tx.send(conn); + let _ = self.tx.send(creds); Ok(()) } diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index 53d4fff544..bcc6cc0461 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -9,42 +9,3 @@ pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, server::{Server, ServerIo, TerminateFuture}, }; -use linkerd_error::Result; - -/// Encodes a list of ALPN protocols into a slice of bytes. -/// -/// -/// `boring` requires that the list of protocols be encoded in the wire format. -#[allow(dead_code)] -fn serialize_alpn(protocols: &[Vec]) -> Result> { - // Allocate a buffer to hold the encoded protocols. - let mut bytes = { - // One additional byte for each protocol's length prefix. - let cap = protocols.len() + protocols.iter().map(Vec::len).sum::(); - Vec::with_capacity(cap) - }; - - // Encode each protocol as a length-prefixed string. - for p in protocols { - if p.len() > 255 { - return Err("ALPN protocols must be less than 256 bytes".into()); - } - bytes.push(p.len() as u8); - bytes.extend(p); - } - - Ok(bytes) -} - -#[cfg(test)] -#[test] -fn test_serialize_alpn() { - assert_eq!(serialize_alpn(&[b"h2".to_vec()]).unwrap(), b"\x02h2"); - assert_eq!( - serialize_alpn(&[b"h2".to_vec(), b"http/1.1".to_vec()]).unwrap(), - b"\x02h2\x08http/1.1" - ); - - assert!(serialize_alpn(&[(0..255).collect()]).is_ok()); - assert!(serialize_alpn(&[(0..=255).collect()]).is_err()); -} diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index 28aae94799..714855e0f4 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -1,17 +1,16 @@ -use boring::ssl; -use linkerd_error::Result; +use crate::creds::CredsRx; use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{Param, Service}; use linkerd_tls::{ClientId, LocalId, NegotiatedProtocolRef, ServerTls}; -use std::{future::Future, pin::Pin, task::Context}; -use tokio::sync::watch; +use std::{future::Future, pin::Pin, sync::Arc, task::Context}; use tracing::debug; #[derive(Clone)] pub struct Server { name: Name, - rx: watch::Receiver, + rx: CredsRx, + alpn: Option]>>, } pub type TerminateFuture = @@ -23,18 +22,20 @@ pub struct ServerIo(tokio_boring::SslStream); // === impl Server === impl Server { - pub(crate) fn new(name: Name, rx: watch::Receiver) -> Self { - Self { name, rx } + pub(crate) fn new(name: Name, rx: CredsRx) -> Self { + Self { + name, + rx, + alpn: None, + } } - pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { - if alpn_protocols.is_empty() { - return Ok(self); + pub fn with_alpn(mut self, alpn_protocols: Vec>) -> Self { + if !alpn_protocols.is_empty() { + self.alpn = Some(alpn_protocols.into()); } - // todo!("support ALPN") - tracing::warn!("ALPN not supported"); - Ok(self) + self } } @@ -58,8 +59,14 @@ where } fn call(&mut self, io: I) -> Self::Future { - let acc = (*self.rx.borrow()).clone(); + // TODO(ver) we should avoid creating a new context for each connection. + let acceptor = match &self.alpn { + Some(alpn) => self.rx.borrow().acceptor(alpn), + None => self.rx.borrow().acceptor(&[]), + }; + Box::pin(async move { + let acc = acceptor.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let io = tokio_boring::accept(&acc, io) .await .map(ServerIo) diff --git a/linkerd/meshtls/src/server.rs b/linkerd/meshtls/src/server.rs index cee4ca0aec..cceb9c570a 100644 --- a/linkerd/meshtls/src/server.rs +++ b/linkerd/meshtls/src/server.rs @@ -74,13 +74,10 @@ impl Param for Server { } impl Server { - pub fn spawn_with_alpn(self, alpn_protocols: Vec>) -> Result { + pub fn with_alpn(self, alpn_protocols: Vec>) -> Result { match self { #[cfg(feature = "boring")] - Self::Boring(srv) => srv - .spawn_with_alpn(alpn_protocols) - .map(Self::Boring) - .map_err(Into::into), + Self::Boring(srv) => Ok(Self::Boring(srv.with_alpn(alpn_protocols))), #[cfg(feature = "rustls")] Self::Rustls(srv) => srv From 3a72cfbf4cf39d9198a088331ded747153fe9e2b Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 18:10:51 +0000 Subject: [PATCH 42/85] Improve ALPN encoding to skip empty protocols --- linkerd/meshtls/boring/src/creds.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 582e2267df..7086231355 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -135,6 +135,9 @@ fn serialize_alpn(protocols: &[Vec]) -> Result> { // Encode each protocol as a length-prefixed string. for p in protocols { + if p.len() == 0 { + continue; + } if p.len() > 255 { return Err("ALPN protocols must be less than 256 bytes".into()); } @@ -153,6 +156,14 @@ fn test_serialize_alpn() { serialize_alpn(&[b"h2".to_vec(), b"http/1.1".to_vec()]).unwrap(), b"\x02h2\x08http/1.1" ); + assert_eq!( + serialize_alpn(&[b"h2".to_vec(), b"http/1.1".to_vec()]).unwrap(), + b"\x02h2\x08http/1.1" + ); + assert_eq!( + serialize_alpn(&[b"h2".to_vec(), vec![], b"http/1.1".to_vec()]).unwrap(), + b"\x02h2\x08http/1.1" + ); assert!(serialize_alpn(&[(0..255).collect()]).is_ok()); assert!(serialize_alpn(&[(0..=255).collect()]).is_err()); From 9a40d3152a42cb07013f45ba962b23198fc8c4e4 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 19:05:09 +0000 Subject: [PATCH 43/85] lints --- linkerd/meshtls/boring/src/creds.rs | 2 +- linkerd/meshtls/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 7086231355..0324f14c0a 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -135,7 +135,7 @@ fn serialize_alpn(protocols: &[Vec]) -> Result> { // Encode each protocol as a length-prefixed string. for p in protocols { - if p.len() == 0 { + if p.is_empty() { continue; } if p.len() > 255 { diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs index 80d9164e9d..d9ed3efd09 100644 --- a/linkerd/meshtls/src/lib.rs +++ b/linkerd/meshtls/src/lib.rs @@ -79,10 +79,10 @@ impl Mode { #[cfg(feature = "boring")] Self::Boring => { let (store, receiver) = boring::creds::watch(identity, roots_pem, key_pkcs8, csr)?; - return Ok(( + Ok(( creds::Store::Boring(store), creds::Receiver::Boring(receiver), - )); + )) } #[cfg(feature = "rustls")] From 9ba4e4cc1f8a58db33aba12713495bb728d2711d Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 19:17:50 +0000 Subject: [PATCH 44/85] Restore rustls credential tests In a prior change, the rustls credential-loading tests were accidentally disabled. This change restores these tests and updates them to use the newer API. --- linkerd/meshtls/rustls/src/lib.rs | 2 + linkerd/meshtls/rustls/src/tests.rs | 59 ++++++++++++++++++----------- linkerd/tls/test-util/src/lib.rs | 7 ++++ 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/linkerd/meshtls/rustls/src/lib.rs b/linkerd/meshtls/rustls/src/lib.rs index bcc6cc0461..e14269e98c 100644 --- a/linkerd/meshtls/rustls/src/lib.rs +++ b/linkerd/meshtls/rustls/src/lib.rs @@ -4,6 +4,8 @@ mod client; pub mod creds; mod server; +#[cfg(test)] +mod tests; pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, diff --git a/linkerd/meshtls/rustls/src/tests.rs b/linkerd/meshtls/rustls/src/tests.rs index abf27117d0..1fd56856b3 100644 --- a/linkerd/meshtls/rustls/src/tests.rs +++ b/linkerd/meshtls/rustls/src/tests.rs @@ -1,35 +1,48 @@ -use super::test_util::*; +use linkerd_identity::{Credentials, DerX509}; +use linkerd_tls_test_util::*; +use std::time::Duration; + +fn load(ent: &Entity) -> crate::creds::Store { + let roots_pem = std::str::from_utf8(ent.trust_anchors).expect("valid PEM"); + let (store, _) = crate::creds::watch( + ent.name.parse().unwrap(), + roots_pem, + ent.key, + b"fake CSR data", + ) + .expect("credentials must be readable"); + store +} #[test] fn can_construct_client_and_server_config_from_valid_settings() { - FOO_NS1.validate().expect("foo.ns1 must be valid"); + assert!(load(&FOO_NS1) + .set_certificate( + DerX509(FOO_NS1.crt.to_vec()), + vec![], + std::time::SystemTime::now() + Duration::from_secs(600) + ) + .is_ok()); } #[test] fn recognize_ca_did_not_issue_cert() { - let s = Identity { - trust_anchors: include_bytes!("testdata/ca2.pem"), - ..FOO_NS1 - }; - assert!(s.validate().is_err(), "ca2 should not validate foo.ns1"); + assert!(load(&FOO_NS1_CA2) + .set_certificate( + DerX509(FOO_NS1.crt.to_vec()), + vec![], + std::time::SystemTime::now() + Duration::from_secs(600) + ) + .is_err()); } #[test] fn recognize_cert_is_not_valid_for_identity() { - let s = Identity { - crt: BAR_NS1.crt, - key: BAR_NS1.key, - ..FOO_NS1 - }; - assert!(s.validate().is_err(), "identity should not be valid"); -} - -#[test] -#[ignore] // XXX this doesn't fail because we don't actually check the key against the cert... -fn recognize_private_key_is_not_valid_for_cert() { - let s = Identity { - key: BAR_NS1.key, - ..FOO_NS1 - }; - assert!(s.validate().is_err(), "identity should not be valid"); + assert!(load(&BAR_NS1) + .set_certificate( + DerX509(FOO_NS1.crt.to_vec()), + vec![], + std::time::SystemTime::now() + Duration::from_secs(600) + ) + .is_err()); } diff --git a/linkerd/tls/test-util/src/lib.rs b/linkerd/tls/test-util/src/lib.rs index d62b43c5f9..7e185d266b 100644 --- a/linkerd/tls/test-util/src/lib.rs +++ b/linkerd/tls/test-util/src/lib.rs @@ -19,6 +19,13 @@ pub static FOO_NS1: Entity = Entity { key: include_bytes!("testdata/foo-ns1-ca1/key.p8"), }; +pub static FOO_NS1_CA2: Entity = Entity { + name: "foo.ns1.serviceaccount.identity.linkerd.cluster.local", + trust_anchors: include_bytes!("testdata/ca2.pem"), + crt: include_bytes!("testdata/foo-ns1-ca2/crt.der"), + key: include_bytes!("testdata/foo-ns1-ca2/key.p8"), +}; + pub static BAR_NS1: Entity = Entity { name: "bar.ns1.serviceaccount.identity.linkerd.cluster.local", trust_anchors: include_bytes!("testdata/ca1.pem"), From c44b8788b5a92acf33785957fd8d55b619bca8fe Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 19:39:17 +0000 Subject: [PATCH 45/85] +warnings --- linkerd/server-policy/src/lib.rs | 3 +++ linkerd/tls/test-util/src/lib.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/linkerd/server-policy/src/lib.rs b/linkerd/server-policy/src/lib.rs index 29636d6e43..689500b0a1 100644 --- a/linkerd/server-policy/src/lib.rs +++ b/linkerd/server-policy/src/lib.rs @@ -1,3 +1,6 @@ +#![deny(warnings, rust_2018_idioms)] +#![forbid(unsafe_code)] + mod network; pub use self::network::Network; diff --git a/linkerd/tls/test-util/src/lib.rs b/linkerd/tls/test-util/src/lib.rs index 7e185d266b..11e8f77dea 100644 --- a/linkerd/tls/test-util/src/lib.rs +++ b/linkerd/tls/test-util/src/lib.rs @@ -1,3 +1,6 @@ +#![deny(warnings, rust_2018_idioms)] +#![forbid(unsafe_code)] + pub struct Entity { pub name: &'static str, pub trust_anchors: &'static [u8], From 19130d4bdd5c465fe30dd8b32d7042577e647edb Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 6 Nov 2021 21:01:25 +0000 Subject: [PATCH 46/85] Replicate credential tests for boring --- Cargo.lock | 1 + linkerd/meshtls/boring/Cargo.toml | 3 ++ linkerd/meshtls/boring/src/lib.rs | 2 ++ linkerd/meshtls/boring/src/tests.rs | 48 +++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 linkerd/meshtls/boring/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 5164f2f6ef..fe6d7f3881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1210,6 +1210,7 @@ dependencies = [ "linkerd-io", "linkerd-stack", "linkerd-tls", + "linkerd-tls-test-util", "tokio", "tokio-boring", "tracing", diff --git a/linkerd/meshtls/boring/Cargo.toml b/linkerd/meshtls/boring/Cargo.toml index e1db83fc2f..4d8c0ea08e 100644 --- a/linkerd/meshtls/boring/Cargo.toml +++ b/linkerd/meshtls/boring/Cargo.toml @@ -18,3 +18,6 @@ linkerd-tls = { path = "../../tls" } tokio = { version = "1", features = ["macros", "sync"] } tokio-boring = "2" tracing = "0.1" + +[dev-dependencies] +linkerd-tls-test-util = { path = "../../tls/test-util" } diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index bcc6cc0461..e14269e98c 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -4,6 +4,8 @@ mod client; pub mod creds; mod server; +#[cfg(test)] +mod tests; pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, diff --git a/linkerd/meshtls/boring/src/tests.rs b/linkerd/meshtls/boring/src/tests.rs new file mode 100644 index 0000000000..1fd56856b3 --- /dev/null +++ b/linkerd/meshtls/boring/src/tests.rs @@ -0,0 +1,48 @@ +use linkerd_identity::{Credentials, DerX509}; +use linkerd_tls_test_util::*; +use std::time::Duration; + +fn load(ent: &Entity) -> crate::creds::Store { + let roots_pem = std::str::from_utf8(ent.trust_anchors).expect("valid PEM"); + let (store, _) = crate::creds::watch( + ent.name.parse().unwrap(), + roots_pem, + ent.key, + b"fake CSR data", + ) + .expect("credentials must be readable"); + store +} + +#[test] +fn can_construct_client_and_server_config_from_valid_settings() { + assert!(load(&FOO_NS1) + .set_certificate( + DerX509(FOO_NS1.crt.to_vec()), + vec![], + std::time::SystemTime::now() + Duration::from_secs(600) + ) + .is_ok()); +} + +#[test] +fn recognize_ca_did_not_issue_cert() { + assert!(load(&FOO_NS1_CA2) + .set_certificate( + DerX509(FOO_NS1.crt.to_vec()), + vec![], + std::time::SystemTime::now() + Duration::from_secs(600) + ) + .is_err()); +} + +#[test] +fn recognize_cert_is_not_valid_for_identity() { + assert!(load(&BAR_NS1) + .set_certificate( + DerX509(FOO_NS1.crt.to_vec()), + vec![], + std::time::SystemTime::now() + Duration::from_secs(600) + ) + .is_err()); +} From 2e3870a95274445fd6696fdc5a8b5741a298cd6c Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 07:02:05 +0000 Subject: [PATCH 47/85] Instrument TLS tests --- Cargo.lock | 12 +- linkerd/meshtls/boring/Cargo.toml | 6 + linkerd/meshtls/boring/src/client.rs | 9 + linkerd/meshtls/boring/src/creds.rs | 34 ++ linkerd/meshtls/boring/src/server.rs | 38 +- linkerd/meshtls/boring/tests/tls_accept.rs | 385 +++++++++++++++++++++ 6 files changed, 474 insertions(+), 10 deletions(-) create mode 100644 linkerd/meshtls/boring/tests/tls_accept.rs diff --git a/Cargo.lock b/Cargo.lock index fe6d7f3881..473958eaa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,6 +533,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hostname" version = "0.3.1" @@ -1204,13 +1210,17 @@ version = "0.1.0" dependencies = [ "boring", "futures", + "hex 0.4.3", + "linkerd-conditional", "linkerd-dns-name", "linkerd-error", "linkerd-identity", "linkerd-io", + "linkerd-proxy-transport", "linkerd-stack", "linkerd-tls", "linkerd-tls-test-util", + "linkerd-tracing", "tokio", "tokio-boring", "tracing", @@ -1620,7 +1630,7 @@ dependencies = [ "base64", "bytes", "futures", - "hex", + "hex 0.3.2", "http", "linkerd-error", "linkerd-stack", diff --git a/linkerd/meshtls/boring/Cargo.toml b/linkerd/meshtls/boring/Cargo.toml index 4d8c0ea08e..6f4e3f938b 100644 --- a/linkerd/meshtls/boring/Cargo.toml +++ b/linkerd/meshtls/boring/Cargo.toml @@ -19,5 +19,11 @@ tokio = { version = "1", features = ["macros", "sync"] } tokio-boring = "2" tracing = "0.1" +hex = "0.4" + [dev-dependencies] +linkerd-conditional = { path = "../../conditional" } +linkerd-proxy-transport = { path = "../../proxy/transport" } linkerd-tls-test-util = { path = "../../tls/test-util" } +linkerd-tracing = { path = "../../tracing" } +tokio = { version = "1", features = ["rt-multi-thread"] } diff --git a/linkerd/meshtls/boring/src/client.rs b/linkerd/meshtls/boring/src/client.rs index 6876fdaf4c..cd3648269b 100644 --- a/linkerd/meshtls/boring/src/client.rs +++ b/linkerd/meshtls/boring/src/client.rs @@ -6,6 +6,7 @@ use linkerd_tls::{ client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerId, }; use std::{future::Future, pin::Pin, sync::Arc, task::Context}; +use tracing::debug; #[derive(Clone)] pub struct NewClient(CredsRx); @@ -90,6 +91,14 @@ where // std::fmt::Debug, which is a pain. None => io::Error::new(io::ErrorKind::Other, "unexpected TLS handshake error"), })?; + + debug!( + tls = io.ssl().version_str(), + client.cert = ?io.ssl().certificate().and_then(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()).map(|d| hex::ToHex::encode_hex::(&&*d)), + peer.cret = ?io.ssl().peer_certificate().and_then(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()).map(|d| hex::ToHex::encode_hex::(&&*d)), + alpn = ?io.ssl().selected_alpn_protocol(), + "Initiated TLS connection" + ); Ok(ClientIo(io)) }) } diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 0324f14c0a..1ed9137442 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -66,11 +66,29 @@ impl Creds { let mut conn = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; let roots = self.root_store()?; + tracing::debug!( + roots = ?self + .base + .roots + .iter() + .filter_map(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()) + .map(|d| hex::ToHex::encode_hex(&&*d)) + .collect::>(), + "Configuring acceptor roots", + ); conn.set_cert_store(roots); + // Ensure that client certificates are validated when present. + conn.set_verify(ssl::SslVerifyMode::PEER); + if let Some(certs) = &self.certs { + tracing::debug!( + cert = ?certs.leaf.digest(boring::hash::MessageDigest::sha256()).ok().map(|d| hex::ToHex::encode_hex::(&&*d)), + "Configuring acceptor certificate", + ); conn.set_private_key(&self.base.key)?; conn.set_certificate(&certs.leaf)?; + conn.check_private_key()?; for c in &certs.intermediates { conn.add_extra_chain_cert(c.to_owned())?; } @@ -92,12 +110,28 @@ impl Creds { // us an alternative AFAICT. let mut conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; + tracing::debug!( + roots = ?self + .base + .roots + .iter() + .filter_map(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()) + .map(|d| hex::ToHex::encode_hex(&&*d)) + .collect::>(), + "Configuring connector roots", + ); let roots = self.root_store()?; conn.set_cert_store(roots); if let Some(certs) = &self.certs { + tracing::debug!( + cert = ?certs.leaf.digest(boring::hash::MessageDigest::sha256()).ok().map(|d| hex::ToHex::encode_hex::(&&*d)), + intermediates = %certs.intermediates.len(), + "Configuring connector certificate", + ); conn.set_private_key(&self.base.key)?; conn.set_certificate(&certs.leaf)?; + conn.check_private_key()?; for c in &certs.intermediates { conn.add_extra_chain_cert(c.to_owned())?; } diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index 714855e0f4..ce67db4dc9 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -2,7 +2,7 @@ use crate::creds::CredsRx; use linkerd_identity::Name; use linkerd_io as io; use linkerd_stack::{Param, Service}; -use linkerd_tls::{ClientId, LocalId, NegotiatedProtocolRef, ServerTls}; +use linkerd_tls::{ClientId, LocalId, NegotiatedProtocol, ServerTls}; use std::{future::Future, pin::Pin, sync::Arc, task::Context}; use tracing::debug; @@ -78,9 +78,16 @@ where })?; let client_id = io.client_identity(); - let negotiated_protocol = io.negotiated_protocol_ref().map(|p| p.to_owned()); - - debug!(client.id = ?client_id, alpn = ?negotiated_protocol, "Accepted TLS connection"); + let negotiated_protocol = io.negotiated_protocol().map(|p| p.to_owned()); + + debug!( + tls = io.0.ssl().version_str(), + srv.cert = ?io.0.ssl().certificate().and_then(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()).map(|d| hex::ToHex::encode_hex::(&&*d)), + peer.cert = ?io.0.ssl().peer_certificate().and_then(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()).map(|d| hex::ToHex::encode_hex::(&&*d)), + client.id = ?client_id, + alpn = ?negotiated_protocol, + "Accepted TLS connection" + ); let tls = ServerTls::Established { client_id, negotiated_protocol, @@ -93,17 +100,30 @@ where // === impl ServerIo === impl ServerIo { - fn negotiated_protocol_ref(&self) -> Option> { + #[inline] + fn negotiated_protocol(&self) -> Option { self.0 .ssl() .selected_alpn_protocol() - .map(NegotiatedProtocolRef) + .map(|p| NegotiatedProtocol(p.to_vec())) } fn client_identity(&self) -> Option { - let cert = self.0.ssl().peer_certificate()?; - let peer = cert.subject_alt_names()?.pop()?; - peer.dnsname()?.parse().ok() + let cert = self.0.ssl().peer_certificate().or_else(|| { + debug!("Connection missing peer certificate"); + None + })?; + let sans = cert.subject_alt_names().or_else(|| { + debug!("Peer certificate missing SANs"); + None + })?; + sans.into_iter() + .filter_map(|san| san.dnsname()?.parse().ok()) + .next() + .or_else(|| { + debug!("Peer certificate missing DNS SANs"); + None + }) } } diff --git a/linkerd/meshtls/boring/tests/tls_accept.rs b/linkerd/meshtls/boring/tests/tls_accept.rs new file mode 100644 index 0000000000..452524b60d --- /dev/null +++ b/linkerd/meshtls/boring/tests/tls_accept.rs @@ -0,0 +1,385 @@ +#![cfg(test)] + +// These are basically integration tests for the `connection` submodule, but +// they cannot be "real" integration tests because `connection` isn't a public +// interface and because `connection` exposes a `#[cfg(test)]`-only API for use +// by these tests. + +use futures::prelude::*; +use linkerd_conditional::Conditional; +use linkerd_error::Infallible; +use linkerd_identity::{Credentials, DerX509, Name}; +use linkerd_io::{self as io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use linkerd_meshtls_boring as meshtls; +use linkerd_proxy_transport::{ + addrs::*, + listen::{Addrs, Bind, BindTcp}, + ConnectTcp, Keepalive, ListenAddr, +}; +use linkerd_stack::{ + layer::Layer, service_fn, ExtractParam, InsertParam, NewService, Param, ServiceExt, +}; +use linkerd_tls as tls; +use linkerd_tls_test_util as test_util; +use std::{future::Future, net::SocketAddr, sync::mpsc, time::Duration}; +use tokio::net::TcpStream; +use tracing::instrument::Instrument; + +type ServerConn = ( + (tls::ConditionalServerTls, T), + io::EitherIo>, tls::server::DetectIo>, +); + +fn load(ent: &test_util::Entity) -> (meshtls::creds::Store, meshtls::NewClient, meshtls::Server) { + let roots_pem = std::str::from_utf8(ent.trust_anchors).expect("valid PEM"); + let (mut store, rx) = meshtls::creds::watch( + ent.name.parse().unwrap(), + roots_pem, + ent.key, + b"fake CSR data", + ) + .expect("credentials must be readable"); + + let expiry = std::time::SystemTime::now() + Duration::from_secs(600); + store + .set_certificate(DerX509(ent.crt.to_vec()), vec![], expiry) + .expect("certificate must be valid"); + + (store, rx.new_client(), rx.server()) +} + +#[tokio::test(flavor = "current_thread")] +async fn plaintext() { + let (_foo, _, server_tls) = load(&test_util::FOO_NS1); + let (_bar, client_tls, _) = load(&test_util::BAR_NS1); + let (client_result, server_result) = run_test( + client_tls, + Conditional::None(tls::NoClientTls::NotProvidedByServiceDiscovery), + |conn| write_then_read(conn, PING), + server_tls, + |(_, conn)| read_then_write(conn, PING.len(), PONG), + ) + .await; + assert_eq!( + client_result.tls, + Some(Conditional::None( + tls::NoClientTls::NotProvidedByServiceDiscovery + )) + ); + assert_eq!(&client_result.result.expect("pong")[..], PONG); + assert_eq!( + server_result.tls, + Some(Conditional::None(tls::NoServerTls::NoClientHello)) + ); + assert_eq!(&server_result.result.expect("ping")[..], PING); +} + +#[tokio::test(flavor = "current_thread")] +async fn proxy_to_proxy_tls_works() { + let (_foo, _, server_tls) = load(&test_util::FOO_NS1); + let (_bar, client_tls, _) = load(&test_util::BAR_NS1); + let server_id = tls::ServerId(test_util::FOO_NS1.name.parse().unwrap()); + let (client_result, server_result) = run_test( + client_tls.clone(), + Conditional::Some(server_id.clone()), + |conn| write_then_read(conn, PING), + server_tls, + |(_, conn)| read_then_write(conn, PING.len(), PONG), + ) + .await; + assert_eq!( + client_result.tls, + Some(Conditional::Some(tls::ClientTls { + server_id, + alpn: None, + })) + ); + assert_eq!(&client_result.result.expect("pong")[..], PONG); + assert_eq!( + server_result.tls, + Some(Conditional::Some(tls::ServerTls::Established { + client_id: Some(tls::ClientId(test_util::BAR_NS1.name.parse().unwrap())), + negotiated_protocol: None, + })) + ); + assert_eq!(&server_result.result.expect("ping")[..], PING); +} + +#[tokio::test(flavor = "current_thread")] +async fn proxy_to_proxy_tls_pass_through_when_identity_does_not_match() { + let (_foo, _, server_tls) = load(&test_util::FOO_NS1); + + // Misuse the client's identity instead of the server's identity. Any + // identity other than `server_tls.server_identity` would work. + let (_bar, client_tls, _) = load(&test_util::BAR_NS1); + let sni = test_util::BAR_NS1.name.parse::().unwrap(); + + let (client_result, server_result) = run_test( + client_tls, + Conditional::Some(tls::ServerId(sni.clone())), + |conn| write_then_read(conn, PING), + server_tls, + |(_, conn)| read_then_write(conn, START_OF_TLS.len(), PONG), + ) + .await; + + // The server's connection will succeed with the TLS client hello passed + // through, because the SNI doesn't match its identity. + assert_eq!(client_result.tls, None); + assert!(client_result.result.is_err()); + assert_eq!( + server_result.tls, + Some(Conditional::Some(tls::ServerTls::Passthru { + sni: tls::ServerId(sni) + })) + ); + assert_eq!(&server_result.result.unwrap()[..], START_OF_TLS); +} + +struct Transported { + tls: Option, + + /// The connection's result. + result: Result, +} + +#[derive(Clone)] +struct ServerParams { + identity: meshtls::Server, +} + +type ClientIo = io::EitherIo, meshtls::ClientIo>>; + +/// Runs a test for a single TCP connection. `client` processes the connection +/// on the client side and `server` processes the connection on the server +/// side. +async fn run_test( + client_tls: meshtls::NewClient, + client_server_id: Conditional, + client: C, + server_id: meshtls::Server, + server: S, +) -> ( + Transported, + Transported, +) +where + // Client + C: FnOnce(ClientIo) -> CF + Clone + Send + 'static, + CF: Future> + Send + 'static, + CR: Send + 'static, + // Server + S: Fn(ServerConn) -> SF + Clone + Send + 'static, + SF: Future> + Send + 'static, + SR: Send + 'static, +{ + let _trace = linkerd_tracing::test::trace_init(); + + // A future that will receive a single connection. + let (server, server_addr, server_result) = { + // Saves the result of every connection. + let (sender, receiver) = mpsc::channel::>(); + + let detect = tls::NewDetectTls::::new( + ServerParams { + identity: server_id, + }, + move |meta: (tls::ConditionalServerTls, Addrs)| { + let server = server.clone(); + let sender = sender.clone(); + let tls = meta.0.clone().map(Into::into); + service_fn(move |conn| { + let server = server.clone(); + let sender = sender.clone(); + let tls = Some(tls.clone()); + let future = server((meta.clone(), conn)); + Box::pin( + async move { + let result = future.await; + sender + .send(Transported { tls, result }) + .expect("send result"); + Ok::<(), Infallible>(()) + } + .instrument(tracing::info_span!("test_svc")), + ) + }) + }, + ); + + let (listen_addr, listen) = BindTcp::default().bind(&Server).expect("must bind"); + let server = async move { + futures::pin_mut!(listen); + let (addrs, io) = listen + .next() + .await + .expect("listen failed") + .expect("listener closed"); + tracing::debug!("incoming connection"); + let accept = detect.new_service(addrs); + accept.oneshot(io).await.expect("connection failed"); + tracing::debug!("done"); + } + .instrument(tracing::info_span!("server", %listen_addr)); + + (server, listen_addr, receiver) + }; + + // A future that will open a single connection to the server. + let (client, client_result) = { + // Saves the result of the single connection. This could be a simpler + // type, e.g. `Arc`, but using a channel simplifies the code and + // parallels the server side. + let (sender, receiver) = mpsc::channel::>(); + let sender_clone = sender.clone(); + + let tls = Some(client_server_id.clone().map(Into::into)); + let client = async move { + let conn = tls::Client::layer(client_tls) + .layer(ConnectTcp::new(Keepalive(None))) + .oneshot(Target(server_addr.into(), client_server_id.map(Into::into))) + .await; + match conn { + Err(e) => { + sender_clone + .send(Transported { + tls: None, + result: Err(e), + }) + .expect("send result"); + } + Ok(conn) => { + let result = client(conn).await; + sender + .send(Transported { tls, result }) + .expect("send result"); + } + }; + } + .instrument(tracing::info_span!("client")); + + (client, receiver) + }; + + futures::future::join(server, client).await; + + let client_result = client_result.try_recv().expect("client complete"); + + // XXX: This assumes that only one connection is accepted. TODO: allow the + // caller to observe the results for every connection, once we have tests + // that allow accepting multiple connections. + let server_result = server_result.try_recv().expect("server complete"); + + (client_result, server_result) +} + +/// Writes `to_write` and shuts down the write side, then reads until EOF, +/// returning the bytes read. +async fn write_then_read( + conn: impl AsyncRead + AsyncWrite + Unpin, + to_write: &'static [u8], +) -> Result, io::Error> { + let conn = write_and_shutdown(conn, to_write).await; + if let Err(ref write_err) = conn { + tracing::error!(%write_err); + } + let mut conn = conn?; + let mut vec = Vec::new(); + tracing::debug!("read_to_end"); + let read = conn.read_to_end(&mut vec).await; + tracing::debug!(?read, ?vec); + read?; + Ok(vec) +} + +/// Reads until EOF then writes `to_write` and shuts down the write side, +/// returning the bytes read. +async fn read_then_write( + mut conn: impl AsyncRead + AsyncWrite + Unpin, + read_prefix_len: usize, + to_write: &'static [u8], +) -> Result, io::Error> { + let mut vec = vec![0; read_prefix_len]; + tracing::debug!("read_exact"); + let read = conn.read_exact(&mut vec[..]).await; + tracing::debug!(?read, ?vec); + read?; + write_and_shutdown(conn, to_write).await?; + Ok(vec) +} + +/// writes `to_write` to `conn` and then shuts down the write side of `conn`. +async fn write_and_shutdown( + mut conn: T, + to_write: &'static [u8], +) -> Result { + conn.write_all(to_write).await?; + tracing::debug!("shutting down..."); + conn.shutdown().await?; + tracing::debug!("shutdown done"); + Ok(conn) +} + +const PING: &[u8] = b"ping"; +const PONG: &[u8] = b"pong"; +const START_OF_TLS: &[u8] = &[22, 3, 1]; // ContentType::handshake version 3.1 + +#[derive(Copy, Clone, Debug)] +struct Server; + +#[derive(Clone)] +struct Target(SocketAddr, tls::ConditionalClientTls); + +// === impl Target === + +impl Param> for Target { + fn param(&self) -> Remote { + Remote(ServerAddr(self.0)) + } +} + +impl Param for Target { + fn param(&self) -> tls::ConditionalClientTls { + self.1.clone() + } +} + +// === impl Server === + +impl Param for Server { + fn param(&self) -> ListenAddr { + // Let the OS decide the port number and then return the resulting + // `SocketAddr` so the client can connect to it. This allows multiple + // tests to run at once, which wouldn't work if they all were bound on + // a fixed port. + ListenAddr(([127, 0, 0, 1], 0).into()) + } +} +impl Param for Server { + fn param(&self) -> Keepalive { + Keepalive(None) + } +} + +/// === impl ServerParams === + +impl ExtractParam for ServerParams { + fn extract_param(&self, _: &T) -> tls::server::Timeout { + tls::server::Timeout(Duration::from_secs(10)) + } +} + +impl ExtractParam for ServerParams { + fn extract_param(&self, _: &T) -> meshtls::Server { + self.identity.clone() + } +} + +impl InsertParam for ServerParams { + type Target = (tls::ConditionalServerTls, T); + + #[inline] + fn insert_param(&self, tls: tls::ConditionalServerTls, target: T) -> Self::Target { + (tls, target) + } +} From 96303ee7edaf1ad65e1def2c8a5aab471f72d896 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 07:16:58 +0000 Subject: [PATCH 48/85] clippay --- linkerd/meshtls/boring/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index ce67db4dc9..af35904ed7 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -78,7 +78,7 @@ where })?; let client_id = io.client_identity(); - let negotiated_protocol = io.negotiated_protocol().map(|p| p.to_owned()); + let negotiated_protocol = io.negotiated_protocol(); debug!( tls = io.0.ssl().version_str(), From 1d148d22aafc789426f5d71c52120ad153a00653 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 16:04:58 +0000 Subject: [PATCH 49/85] cleanup fingerprint formatting --- linkerd/meshtls/boring/src/client.rs | 4 ++-- linkerd/meshtls/boring/src/creds.rs | 14 ++++++-------- linkerd/meshtls/boring/src/lib.rs | 6 ++++++ linkerd/meshtls/boring/src/server.rs | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/linkerd/meshtls/boring/src/client.rs b/linkerd/meshtls/boring/src/client.rs index cd3648269b..8b1da23f35 100644 --- a/linkerd/meshtls/boring/src/client.rs +++ b/linkerd/meshtls/boring/src/client.rs @@ -94,8 +94,8 @@ where debug!( tls = io.ssl().version_str(), - client.cert = ?io.ssl().certificate().and_then(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()).map(|d| hex::ToHex::encode_hex::(&&*d)), - peer.cret = ?io.ssl().peer_certificate().and_then(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()).map(|d| hex::ToHex::encode_hex::(&&*d)), + client.cert = ?io.ssl().certificate().and_then(super::fingerprint), + peer.cert = ?io.ssl().peer_certificate().as_deref().and_then(super::fingerprint), alpn = ?io.ssl().selected_alpn_protocol(), "Initiated TLS connection" ); diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 1ed9137442..3b4e74c698 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -71,9 +71,8 @@ impl Creds { .base .roots .iter() - .filter_map(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()) - .map(|d| hex::ToHex::encode_hex(&&*d)) - .collect::>(), + .filter_map(|c| super::fingerprint(&*c)) + .collect::>(), "Configuring acceptor roots", ); conn.set_cert_store(roots); @@ -83,7 +82,7 @@ impl Creds { if let Some(certs) = &self.certs { tracing::debug!( - cert = ?certs.leaf.digest(boring::hash::MessageDigest::sha256()).ok().map(|d| hex::ToHex::encode_hex::(&&*d)), + cert = ?super::fingerprint(&*certs.leaf), "Configuring acceptor certificate", ); conn.set_private_key(&self.base.key)?; @@ -115,9 +114,8 @@ impl Creds { .base .roots .iter() - .filter_map(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()) - .map(|d| hex::ToHex::encode_hex(&&*d)) - .collect::>(), + .filter_map(|c| super::fingerprint(&*c)) + .collect::>(), "Configuring connector roots", ); let roots = self.root_store()?; @@ -125,7 +123,7 @@ impl Creds { if let Some(certs) = &self.certs { tracing::debug!( - cert = ?certs.leaf.digest(boring::hash::MessageDigest::sha256()).ok().map(|d| hex::ToHex::encode_hex::(&&*d)), + cert = ?super::fingerprint(&*certs.leaf), intermediates = %certs.intermediates.len(), "Configuring connector certificate", ); diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index e14269e98c..a85e43673c 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -11,3 +11,9 @@ pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, server::{Server, ServerIo, TerminateFuture}, }; + +fn fingerprint(c: &boring::x509::X509Ref) -> Option { + c.digest(boring::hash::MessageDigest::sha256()) + .ok() + .map(|d| hex::ToHex::encode_hex::(&&*d)[0..8].to_string()) +} diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index af35904ed7..224a3bed66 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -82,8 +82,8 @@ where debug!( tls = io.0.ssl().version_str(), - srv.cert = ?io.0.ssl().certificate().and_then(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()).map(|d| hex::ToHex::encode_hex::(&&*d)), - peer.cert = ?io.0.ssl().peer_certificate().and_then(|c| c.digest(boring::hash::MessageDigest::sha256()).ok()).map(|d| hex::ToHex::encode_hex::(&&*d)), + srv.cert = ?io.0.ssl().certificate().as_deref().and_then(super::fingerprint), + peer.cert = ?io.0.ssl().peer_certificate().as_deref().and_then(super::fingerprint), client.id = ?client_id, alpn = ?negotiated_protocol, "Accepted TLS connection" From d9cf0e2fbdf8043d4f5a309df6cc160a1c94b415 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 16:43:53 +0000 Subject: [PATCH 50/85] bump hex --- Cargo.lock | 10 ++-------- linkerd/trace-context/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 473958eaa3..ae18dd39ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -527,12 +527,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hex" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" - [[package]] name = "hex" version = "0.4.3" @@ -1210,7 +1204,7 @@ version = "0.1.0" dependencies = [ "boring", "futures", - "hex 0.4.3", + "hex", "linkerd-conditional", "linkerd-dns-name", "linkerd-error", @@ -1630,7 +1624,7 @@ dependencies = [ "base64", "bytes", "futures", - "hex 0.3.2", + "hex", "http", "linkerd-error", "linkerd-stack", diff --git a/linkerd/trace-context/Cargo.toml b/linkerd/trace-context/Cargo.toml index a8304484d8..a22225def8 100644 --- a/linkerd/trace-context/Cargo.toml +++ b/linkerd/trace-context/Cargo.toml @@ -10,7 +10,7 @@ publish = false base64 = "0.13" bytes = "1" futures = { version = "0.3", default-features = false } -hex = "0.3.2" +hex = "0.4" http = "0.2" linkerd-error = { path = "../error" } linkerd-stack = { path = "../stack" } From 8290c7cdc22dbaff18238a622904214474e60b86 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 17:11:52 +0000 Subject: [PATCH 51/85] simplify fingerprint --- linkerd/meshtls/boring/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index a85e43673c..107f6260c5 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -15,5 +15,5 @@ pub use self::{ fn fingerprint(c: &boring::x509::X509Ref) -> Option { c.digest(boring::hash::MessageDigest::sha256()) .ok() - .map(|d| hex::ToHex::encode_hex::(&&*d)[0..8].to_string()) + .map(|d| hex::encode(d)[0..8].to_string()) } From 53604b199f08bcf5d3d6e27ccc5e890806cd8c20 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 17:21:41 +0000 Subject: [PATCH 52/85] more simplify --- linkerd/meshtls/boring/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index 107f6260c5..b5367e46f1 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -13,7 +13,6 @@ pub use self::{ }; fn fingerprint(c: &boring::x509::X509Ref) -> Option { - c.digest(boring::hash::MessageDigest::sha256()) - .ok() - .map(|d| hex::encode(d)[0..8].to_string()) + let digest = c.digest(boring::hash::MessageDigest::sha256()).ok()?; + Some(hex::encode(digest)[0..8].to_string()) } From be87e63fcccffe9014be484cb7f5abf93f4aea25 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 19:27:43 +0000 Subject: [PATCH 53/85] docs, tls versions, mode::from_str --- linkerd/meshtls/boring/src/creds.rs | 17 ++++++-- linkerd/meshtls/boring/src/lib.rs | 21 +++++++++ linkerd/meshtls/src/lib.rs | 68 +++++++++++++++++++++++------ 3 files changed, 89 insertions(+), 17 deletions(-) diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 3b4e74c698..37a599c764 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -54,17 +54,20 @@ type CredsTx = watch::Sender; impl From> for Creds { fn from(base: Arc) -> Self { - Creds { base, certs: None } + Self { base, certs: None } } } impl Creds { - // TODO(ver) Restrict TLS version, algorithms, etc. + // TODO(ver) Specify certificate types, signing algorithms, cipher suites.. pub(crate) fn acceptor(&self, alpn_protocols: &[Vec]) -> Result { // mozilla_intermediate_v5 is the only variant that enables TLSv1.3, so we use that. - // TODO(ver) We should set explicit TLS versions, algorithms, etc. let mut conn = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls_server())?; + // Force use of TLSv1.3. + conn.set_options(ssl::SslOptions::NO_TLSV1_2); + conn.clear_options(ssl::SslOptions::NO_TLSV1_3); + let roots = self.root_store()?; tracing::debug!( roots = ?self @@ -101,7 +104,7 @@ impl Creds { Ok(conn.build()) } - // TODO(ver) Restrict TLS version, algorithms, etc. + // TODO(ver) Specify certificate types, signing algorithms, cipher suites.. pub(crate) fn connector(&self, alpn_protocols: &[Vec]) -> Result { // XXX(ver) This function reads from the environment and/or the filesystem. This likely is // at best wasteful and at worst unsafe (if another thread were to mutate these environment @@ -109,6 +112,12 @@ impl Creds { // us an alternative AFAICT. let mut conn = ssl::SslConnector::builder(ssl::SslMethod::tls_client())?; + // Explicitly enable use of TLSv1.3 + conn.set_options(ssl::SslOptions::NO_TLSV1 | ssl::SslOptions::NO_TLSV1_1); + // XXX(ver) if we disable use of TLSv1.2, connections just hang. + //conn.set_options(ssl::SslOptions::NO_TLSV1_2); + conn.clear_options(ssl::SslOptions::NO_TLSV1_3); + tracing::debug!( roots = ?self .base diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index b5367e46f1..8063cd90fe 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -1,6 +1,27 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] +//! This crate provides an implementation of _meshtls_ backed by `boringssl` (as provided by +//! https://github.com/cloudflare/boring). +//! +//! There are several caveats with the current implementation: +//! +//! In its current form, this crate is compatible with the `meshtls-rustls` implementation, which +//! requires of ECDSA-P256-SHA256 keys & signature algorithms. This crate doesn't actually constrain +//! the algorithms beyond the Mozilla's 'intermediate' (v5) [defaults][defaults]. But, the goal for +//! supporting `boring` is to provide a FIPS 140-2 compliant mode. There's a [PR][fips-pr] that +//! implements this, but code changes will likely be required to enable this once it's +//! merged/released. +//! +//! A new SSL context is created for each connection. This is probably unnecessary, but it's simpler +//! for now. We can revisit this if needed. +//! +//! This module is not enabled by default. See the `linkerd-meshtls` and `linkerd2-proxy` crates for +//! more information. +//! +//! [defaults]: https://wiki.mozilla.org/Security/Server_Side_TLS. +//! [fips-pr]: https://github.com/cloudflare/boring/pull/52 + mod client; pub mod creds; mod server; diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs index d9ed3efd09..51e4a4e1f9 100644 --- a/linkerd/meshtls/src/lib.rs +++ b/linkerd/meshtls/src/lib.rs @@ -1,18 +1,13 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -#[cfg(not(feature = "__has_any_tls_impls"))] -#[macro_export] -macro_rules! no_tls { - ($($field:ident),*) => { - { - $( - let _ = $field; - )* - unreachable!("compiled without any TLS implementations enabled!"); - } - }; -} +//! This crate provides a static interface for the proxy's x509 certificate provisioning and +//! creation of client/server services. It supports the `boring` and `rustls` TLS backends. +//! +//! This crate may be compiled without either implementation, in which case it will fail at runtime. +//! This enables an implementation to be chosen by the proxy's frontend, so that other crates can +//! depend on this crate without having to pin a TLS implementation. Furthermore, this crate +//! supports both backends simultaneously so it can be compiled with `--all-features`. mod client; pub mod creds; @@ -22,8 +17,9 @@ pub use self::{ client::{ClientIo, Connect, ConnectFuture, NewClient}, server::{Server, ServerIo, TerminateFuture}, }; -use linkerd_error::Result; +use linkerd_error::{Error, Result}; use linkerd_identity::Name; +use std::str::FromStr; #[cfg(feature = "boring")] pub use linkerd_meshtls_boring as boring; @@ -43,6 +39,19 @@ pub enum Mode { NoTls, } +#[cfg(not(feature = "__has_any_tls_impls"))] +#[macro_export] +macro_rules! no_tls { + ($($field:ident),*) => { + { + $( + let _ = $field; + )* + unreachable!("compiled without any TLS implementations enabled!"); + } + }; +} + // === impl Mode === #[cfg(feature = "rustls")] @@ -99,3 +108,36 @@ impl Mode { } } } + +impl FromStr for Mode { + type Err = Error; + + fn from_str(s: &str) -> Result { + #[cfg(feature = "boring")] + if s == "boring" { + return Ok(Self::Boring); + } + + #[cfg(feature = "rustls")] + if s == "rustls" { + return Ok(Self::Rustls); + } + + Err(format!("unknown TLS backend: {}", s).into()) + } +} + +impl std::fmt::Display for Mode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(feature = "boring")] + Self::Boring => "boring".fmt(f), + + #[cfg(feature = "rustls")] + Self::Rustls => "rustls".fmt(f), + + #[cfg(not(feature = "__has_any_tls_impls"))] + _ => no_tls!(f), + } + } +} From 5796529a3ec10659c72fd2b0ca0a9c05ea5cb383 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 20:28:44 +0000 Subject: [PATCH 54/85] Mode::from_str case-agnostic --- linkerd/meshtls/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs index 51e4a4e1f9..0c39827496 100644 --- a/linkerd/meshtls/src/lib.rs +++ b/linkerd/meshtls/src/lib.rs @@ -114,12 +114,12 @@ impl FromStr for Mode { fn from_str(s: &str) -> Result { #[cfg(feature = "boring")] - if s == "boring" { + if s.eq_ignore_ascii_case("boring") { return Ok(Self::Boring); } #[cfg(feature = "rustls")] - if s == "rustls" { + if s.eq_ignore_ascii_case("rustls") { return Ok(Self::Rustls); } From 8e656c7db5e8b330507fa629ec6ed89bed9859be Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 7 Nov 2021 23:39:36 +0000 Subject: [PATCH 55/85] tls tests --- .github/workflows/boring.yml | 4 ++-- linkerd/meshtls/boring/tests/tls_accept.rs | 9 +++++---- linkerd/meshtls/rustls/tests/tls_accept.rs | 9 +++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml index 082ae0da44..45853750f9 100644 --- a/.github/workflows/boring.yml +++ b/.github/workflows/boring.yml @@ -24,8 +24,8 @@ jobs: - run: apt update && apt install -y cmake clang golang - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - - working-directory: linkerd/meshtls/boring - run: cargo clippy --all-targets + - working-directory: linkerd/meshtls + run: cargo clippy --no-default-features --features=boring boring-test: timeout-minutes: 15 diff --git a/linkerd/meshtls/boring/tests/tls_accept.rs b/linkerd/meshtls/boring/tests/tls_accept.rs index 452524b60d..30b3c71deb 100644 --- a/linkerd/meshtls/boring/tests/tls_accept.rs +++ b/linkerd/meshtls/boring/tests/tls_accept.rs @@ -1,9 +1,10 @@ -#![cfg(test)] - // These are basically integration tests for the `connection` submodule, but // they cannot be "real" integration tests because `connection` isn't a public // interface and because `connection` exposes a `#[cfg(test)]`-only API for use // by these tests. +// +// This file is almost identical to ../../rustls/tests/tls_accept.rs. Making this +// generic makes this all much more complicated, though. use futures::prelude::*; use linkerd_conditional::Conditional; @@ -231,7 +232,7 @@ where // type, e.g. `Arc`, but using a channel simplifies the code and // parallels the server side. let (sender, receiver) = mpsc::channel::>(); - let sender_clone = sender.clone(); + let sender = sender.clone(); let tls = Some(client_server_id.clone().map(Into::into)); let client = async move { @@ -241,7 +242,7 @@ where .await; match conn { Err(e) => { - sender_clone + sender .send(Transported { tls: None, result: Err(e), diff --git a/linkerd/meshtls/rustls/tests/tls_accept.rs b/linkerd/meshtls/rustls/tests/tls_accept.rs index 9357688ee1..7fe9d6bd7f 100644 --- a/linkerd/meshtls/rustls/tests/tls_accept.rs +++ b/linkerd/meshtls/rustls/tests/tls_accept.rs @@ -1,9 +1,10 @@ -#![cfg(test)] - // These are basically integration tests for the `connection` submodule, but // they cannot be "real" integration tests because `connection` isn't a public // interface and because `connection` exposes a `#[cfg(test)]`-only API for use // by these tests. +// +// This file is almost identical to ../../boring/tests/tls_accept.rs. Making this +// generic makes this all much more complicated, though. use futures::prelude::*; use linkerd_conditional::Conditional; @@ -233,7 +234,7 @@ where // type, e.g. `Arc`, but using a channel simplifies the code and // parallels the server side. let (sender, receiver) = mpsc::channel::>(); - let sender_clone = sender.clone(); + let sender = sender.clone(); let tls = Some(client_server_id.clone().map(Into::into)); let client = async move { @@ -243,7 +244,7 @@ where .await; match conn { Err(e) => { - sender_clone + sender .send(Transported { tls: None, result: Err(e), From 36bcff10710f51a95cb6168cd9e2bfad15f71158 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 00:15:24 +0000 Subject: [PATCH 56/85] unify tests in the meshtls crate, testing the feature flags --- Cargo.lock | 6 + linkerd/meshtls/Cargo.toml | 8 + linkerd/meshtls/boring/tests/tls_accept.rs | 386 ------------------ linkerd/meshtls/tests/boring.rs | 20 + linkerd/meshtls/tests/rustls.rs | 20 + .../tests/tls_accept.rs => tests/util.rs} | 98 ++--- 6 files changed, 99 insertions(+), 439 deletions(-) delete mode 100644 linkerd/meshtls/boring/tests/tls_accept.rs create mode 100644 linkerd/meshtls/tests/boring.rs create mode 100644 linkerd/meshtls/tests/rustls.rs rename linkerd/meshtls/{rustls/tests/tls_accept.rs => tests/util.rs} (87%) diff --git a/Cargo.lock b/Cargo.lock index ae18dd39ba..43843a18cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,14 +1188,20 @@ name = "linkerd-meshtls" version = "0.1.0" dependencies = [ "futures", + "linkerd-conditional", "linkerd-error", "linkerd-identity", "linkerd-io", "linkerd-meshtls-boring", "linkerd-meshtls-rustls", + "linkerd-proxy-transport", "linkerd-stack", "linkerd-tls", + "linkerd-tls-test-util", + "linkerd-tracing", "pin-project", + "tokio", + "tracing", ] [[package]] diff --git a/linkerd/meshtls/Cargo.toml b/linkerd/meshtls/Cargo.toml index f8756d64fb..d8d3de19f1 100644 --- a/linkerd/meshtls/Cargo.toml +++ b/linkerd/meshtls/Cargo.toml @@ -22,3 +22,11 @@ linkerd-meshtls-rustls = { path = "rustls", optional = true } linkerd-stack = { path = "../stack" } linkerd-tls = { path = "../tls" } pin-project = "1" + +[dev-dependencies] +linkerd-conditional = { path = "../conditional" } +linkerd-proxy-transport = { path = "../proxy/transport" } +linkerd-tls-test-util = { path = "../tls/test-util" } +linkerd-tracing = { path = "../tracing", features = ["ansi"] } +tokio = { version = "1", features = ["macros", "net", "rt-multi-thread"] } +tracing = "0.1" diff --git a/linkerd/meshtls/boring/tests/tls_accept.rs b/linkerd/meshtls/boring/tests/tls_accept.rs deleted file mode 100644 index 30b3c71deb..0000000000 --- a/linkerd/meshtls/boring/tests/tls_accept.rs +++ /dev/null @@ -1,386 +0,0 @@ -// These are basically integration tests for the `connection` submodule, but -// they cannot be "real" integration tests because `connection` isn't a public -// interface and because `connection` exposes a `#[cfg(test)]`-only API for use -// by these tests. -// -// This file is almost identical to ../../rustls/tests/tls_accept.rs. Making this -// generic makes this all much more complicated, though. - -use futures::prelude::*; -use linkerd_conditional::Conditional; -use linkerd_error::Infallible; -use linkerd_identity::{Credentials, DerX509, Name}; -use linkerd_io::{self as io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use linkerd_meshtls_boring as meshtls; -use linkerd_proxy_transport::{ - addrs::*, - listen::{Addrs, Bind, BindTcp}, - ConnectTcp, Keepalive, ListenAddr, -}; -use linkerd_stack::{ - layer::Layer, service_fn, ExtractParam, InsertParam, NewService, Param, ServiceExt, -}; -use linkerd_tls as tls; -use linkerd_tls_test_util as test_util; -use std::{future::Future, net::SocketAddr, sync::mpsc, time::Duration}; -use tokio::net::TcpStream; -use tracing::instrument::Instrument; - -type ServerConn = ( - (tls::ConditionalServerTls, T), - io::EitherIo>, tls::server::DetectIo>, -); - -fn load(ent: &test_util::Entity) -> (meshtls::creds::Store, meshtls::NewClient, meshtls::Server) { - let roots_pem = std::str::from_utf8(ent.trust_anchors).expect("valid PEM"); - let (mut store, rx) = meshtls::creds::watch( - ent.name.parse().unwrap(), - roots_pem, - ent.key, - b"fake CSR data", - ) - .expect("credentials must be readable"); - - let expiry = std::time::SystemTime::now() + Duration::from_secs(600); - store - .set_certificate(DerX509(ent.crt.to_vec()), vec![], expiry) - .expect("certificate must be valid"); - - (store, rx.new_client(), rx.server()) -} - -#[tokio::test(flavor = "current_thread")] -async fn plaintext() { - let (_foo, _, server_tls) = load(&test_util::FOO_NS1); - let (_bar, client_tls, _) = load(&test_util::BAR_NS1); - let (client_result, server_result) = run_test( - client_tls, - Conditional::None(tls::NoClientTls::NotProvidedByServiceDiscovery), - |conn| write_then_read(conn, PING), - server_tls, - |(_, conn)| read_then_write(conn, PING.len(), PONG), - ) - .await; - assert_eq!( - client_result.tls, - Some(Conditional::None( - tls::NoClientTls::NotProvidedByServiceDiscovery - )) - ); - assert_eq!(&client_result.result.expect("pong")[..], PONG); - assert_eq!( - server_result.tls, - Some(Conditional::None(tls::NoServerTls::NoClientHello)) - ); - assert_eq!(&server_result.result.expect("ping")[..], PING); -} - -#[tokio::test(flavor = "current_thread")] -async fn proxy_to_proxy_tls_works() { - let (_foo, _, server_tls) = load(&test_util::FOO_NS1); - let (_bar, client_tls, _) = load(&test_util::BAR_NS1); - let server_id = tls::ServerId(test_util::FOO_NS1.name.parse().unwrap()); - let (client_result, server_result) = run_test( - client_tls.clone(), - Conditional::Some(server_id.clone()), - |conn| write_then_read(conn, PING), - server_tls, - |(_, conn)| read_then_write(conn, PING.len(), PONG), - ) - .await; - assert_eq!( - client_result.tls, - Some(Conditional::Some(tls::ClientTls { - server_id, - alpn: None, - })) - ); - assert_eq!(&client_result.result.expect("pong")[..], PONG); - assert_eq!( - server_result.tls, - Some(Conditional::Some(tls::ServerTls::Established { - client_id: Some(tls::ClientId(test_util::BAR_NS1.name.parse().unwrap())), - negotiated_protocol: None, - })) - ); - assert_eq!(&server_result.result.expect("ping")[..], PING); -} - -#[tokio::test(flavor = "current_thread")] -async fn proxy_to_proxy_tls_pass_through_when_identity_does_not_match() { - let (_foo, _, server_tls) = load(&test_util::FOO_NS1); - - // Misuse the client's identity instead of the server's identity. Any - // identity other than `server_tls.server_identity` would work. - let (_bar, client_tls, _) = load(&test_util::BAR_NS1); - let sni = test_util::BAR_NS1.name.parse::().unwrap(); - - let (client_result, server_result) = run_test( - client_tls, - Conditional::Some(tls::ServerId(sni.clone())), - |conn| write_then_read(conn, PING), - server_tls, - |(_, conn)| read_then_write(conn, START_OF_TLS.len(), PONG), - ) - .await; - - // The server's connection will succeed with the TLS client hello passed - // through, because the SNI doesn't match its identity. - assert_eq!(client_result.tls, None); - assert!(client_result.result.is_err()); - assert_eq!( - server_result.tls, - Some(Conditional::Some(tls::ServerTls::Passthru { - sni: tls::ServerId(sni) - })) - ); - assert_eq!(&server_result.result.unwrap()[..], START_OF_TLS); -} - -struct Transported { - tls: Option, - - /// The connection's result. - result: Result, -} - -#[derive(Clone)] -struct ServerParams { - identity: meshtls::Server, -} - -type ClientIo = io::EitherIo, meshtls::ClientIo>>; - -/// Runs a test for a single TCP connection. `client` processes the connection -/// on the client side and `server` processes the connection on the server -/// side. -async fn run_test( - client_tls: meshtls::NewClient, - client_server_id: Conditional, - client: C, - server_id: meshtls::Server, - server: S, -) -> ( - Transported, - Transported, -) -where - // Client - C: FnOnce(ClientIo) -> CF + Clone + Send + 'static, - CF: Future> + Send + 'static, - CR: Send + 'static, - // Server - S: Fn(ServerConn) -> SF + Clone + Send + 'static, - SF: Future> + Send + 'static, - SR: Send + 'static, -{ - let _trace = linkerd_tracing::test::trace_init(); - - // A future that will receive a single connection. - let (server, server_addr, server_result) = { - // Saves the result of every connection. - let (sender, receiver) = mpsc::channel::>(); - - let detect = tls::NewDetectTls::::new( - ServerParams { - identity: server_id, - }, - move |meta: (tls::ConditionalServerTls, Addrs)| { - let server = server.clone(); - let sender = sender.clone(); - let tls = meta.0.clone().map(Into::into); - service_fn(move |conn| { - let server = server.clone(); - let sender = sender.clone(); - let tls = Some(tls.clone()); - let future = server((meta.clone(), conn)); - Box::pin( - async move { - let result = future.await; - sender - .send(Transported { tls, result }) - .expect("send result"); - Ok::<(), Infallible>(()) - } - .instrument(tracing::info_span!("test_svc")), - ) - }) - }, - ); - - let (listen_addr, listen) = BindTcp::default().bind(&Server).expect("must bind"); - let server = async move { - futures::pin_mut!(listen); - let (addrs, io) = listen - .next() - .await - .expect("listen failed") - .expect("listener closed"); - tracing::debug!("incoming connection"); - let accept = detect.new_service(addrs); - accept.oneshot(io).await.expect("connection failed"); - tracing::debug!("done"); - } - .instrument(tracing::info_span!("server", %listen_addr)); - - (server, listen_addr, receiver) - }; - - // A future that will open a single connection to the server. - let (client, client_result) = { - // Saves the result of the single connection. This could be a simpler - // type, e.g. `Arc`, but using a channel simplifies the code and - // parallels the server side. - let (sender, receiver) = mpsc::channel::>(); - let sender = sender.clone(); - - let tls = Some(client_server_id.clone().map(Into::into)); - let client = async move { - let conn = tls::Client::layer(client_tls) - .layer(ConnectTcp::new(Keepalive(None))) - .oneshot(Target(server_addr.into(), client_server_id.map(Into::into))) - .await; - match conn { - Err(e) => { - sender - .send(Transported { - tls: None, - result: Err(e), - }) - .expect("send result"); - } - Ok(conn) => { - let result = client(conn).await; - sender - .send(Transported { tls, result }) - .expect("send result"); - } - }; - } - .instrument(tracing::info_span!("client")); - - (client, receiver) - }; - - futures::future::join(server, client).await; - - let client_result = client_result.try_recv().expect("client complete"); - - // XXX: This assumes that only one connection is accepted. TODO: allow the - // caller to observe the results for every connection, once we have tests - // that allow accepting multiple connections. - let server_result = server_result.try_recv().expect("server complete"); - - (client_result, server_result) -} - -/// Writes `to_write` and shuts down the write side, then reads until EOF, -/// returning the bytes read. -async fn write_then_read( - conn: impl AsyncRead + AsyncWrite + Unpin, - to_write: &'static [u8], -) -> Result, io::Error> { - let conn = write_and_shutdown(conn, to_write).await; - if let Err(ref write_err) = conn { - tracing::error!(%write_err); - } - let mut conn = conn?; - let mut vec = Vec::new(); - tracing::debug!("read_to_end"); - let read = conn.read_to_end(&mut vec).await; - tracing::debug!(?read, ?vec); - read?; - Ok(vec) -} - -/// Reads until EOF then writes `to_write` and shuts down the write side, -/// returning the bytes read. -async fn read_then_write( - mut conn: impl AsyncRead + AsyncWrite + Unpin, - read_prefix_len: usize, - to_write: &'static [u8], -) -> Result, io::Error> { - let mut vec = vec![0; read_prefix_len]; - tracing::debug!("read_exact"); - let read = conn.read_exact(&mut vec[..]).await; - tracing::debug!(?read, ?vec); - read?; - write_and_shutdown(conn, to_write).await?; - Ok(vec) -} - -/// writes `to_write` to `conn` and then shuts down the write side of `conn`. -async fn write_and_shutdown( - mut conn: T, - to_write: &'static [u8], -) -> Result { - conn.write_all(to_write).await?; - tracing::debug!("shutting down..."); - conn.shutdown().await?; - tracing::debug!("shutdown done"); - Ok(conn) -} - -const PING: &[u8] = b"ping"; -const PONG: &[u8] = b"pong"; -const START_OF_TLS: &[u8] = &[22, 3, 1]; // ContentType::handshake version 3.1 - -#[derive(Copy, Clone, Debug)] -struct Server; - -#[derive(Clone)] -struct Target(SocketAddr, tls::ConditionalClientTls); - -// === impl Target === - -impl Param> for Target { - fn param(&self) -> Remote { - Remote(ServerAddr(self.0)) - } -} - -impl Param for Target { - fn param(&self) -> tls::ConditionalClientTls { - self.1.clone() - } -} - -// === impl Server === - -impl Param for Server { - fn param(&self) -> ListenAddr { - // Let the OS decide the port number and then return the resulting - // `SocketAddr` so the client can connect to it. This allows multiple - // tests to run at once, which wouldn't work if they all were bound on - // a fixed port. - ListenAddr(([127, 0, 0, 1], 0).into()) - } -} -impl Param for Server { - fn param(&self) -> Keepalive { - Keepalive(None) - } -} - -/// === impl ServerParams === - -impl ExtractParam for ServerParams { - fn extract_param(&self, _: &T) -> tls::server::Timeout { - tls::server::Timeout(Duration::from_secs(10)) - } -} - -impl ExtractParam for ServerParams { - fn extract_param(&self, _: &T) -> meshtls::Server { - self.identity.clone() - } -} - -impl InsertParam for ServerParams { - type Target = (tls::ConditionalServerTls, T); - - #[inline] - fn insert_param(&self, tls: tls::ConditionalServerTls, target: T) -> Self::Target { - (tls, target) - } -} diff --git a/linkerd/meshtls/tests/boring.rs b/linkerd/meshtls/tests/boring.rs new file mode 100644 index 0000000000..853a3b24c5 --- /dev/null +++ b/linkerd/meshtls/tests/boring.rs @@ -0,0 +1,20 @@ +#![cfg(feature = "boring")] + +mod util; + +use linkerd_meshtls::Mode; + +#[tokio::test(flavor = "current_thread")] +async fn plaintext() { + util::plaintext(Mode::Boring).await; +} + +#[tokio::test(flavor = "current_thread")] +async fn proxy_to_proxy_tls_works() { + util::proxy_to_proxy_tls_works(Mode::Boring).await; +} + +#[tokio::test(flavor = "current_thread")] +async fn proxy_to_proxy_tls_pass_through_when_identity_does_not_match() { + util::proxy_to_proxy_tls_pass_through_when_identity_does_not_match(Mode::Boring).await; +} diff --git a/linkerd/meshtls/tests/rustls.rs b/linkerd/meshtls/tests/rustls.rs new file mode 100644 index 0000000000..f6385c86b1 --- /dev/null +++ b/linkerd/meshtls/tests/rustls.rs @@ -0,0 +1,20 @@ +#![cfg(feature = "rustls")] + +mod util; + +use linkerd_meshtls::Mode; + +#[tokio::test(flavor = "current_thread")] +async fn plaintext() { + util::plaintext(Mode::Rustls).await; +} + +#[tokio::test(flavor = "current_thread")] +async fn proxy_to_proxy_tls_works() { + util::proxy_to_proxy_tls_works(Mode::Rustls).await; +} + +#[tokio::test(flavor = "current_thread")] +async fn proxy_to_proxy_tls_pass_through_when_identity_does_not_match() { + util::proxy_to_proxy_tls_pass_through_when_identity_does_not_match(Mode::Rustls).await; +} diff --git a/linkerd/meshtls/rustls/tests/tls_accept.rs b/linkerd/meshtls/tests/util.rs similarity index 87% rename from linkerd/meshtls/rustls/tests/tls_accept.rs rename to linkerd/meshtls/tests/util.rs index 7fe9d6bd7f..1598f454a9 100644 --- a/linkerd/meshtls/rustls/tests/tls_accept.rs +++ b/linkerd/meshtls/tests/util.rs @@ -1,60 +1,26 @@ -// These are basically integration tests for the `connection` submodule, but -// they cannot be "real" integration tests because `connection` isn't a public -// interface and because `connection` exposes a `#[cfg(test)]`-only API for use -// by these tests. -// -// This file is almost identical to ../../boring/tests/tls_accept.rs. Making this -// generic makes this all much more complicated, though. - use futures::prelude::*; use linkerd_conditional::Conditional; use linkerd_error::Infallible; use linkerd_identity::{Credentials, DerX509, Name}; use linkerd_io::{self as io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use linkerd_meshtls_rustls as meshtls; +use linkerd_meshtls as meshtls; use linkerd_proxy_transport::{ addrs::*, listen::{Addrs, Bind, BindTcp}, ConnectTcp, Keepalive, ListenAddr, }; -use linkerd_stack::{ExtractParam, InsertParam, NewService, Param}; +use linkerd_stack::{ + layer::Layer, service_fn, ExtractParam, InsertParam, NewService, Param, ServiceExt, +}; use linkerd_tls as tls; use linkerd_tls_test_util as test_util; use std::{future::Future, net::SocketAddr, sync::mpsc, time::Duration}; use tokio::net::TcpStream; -use tower::{ - layer::Layer, - util::{service_fn, ServiceExt}, -}; -use tracing::instrument::Instrument; - -type ServerConn = ( - (tls::ConditionalServerTls, T), - io::EitherIo>, tls::server::DetectIo>, -); - -fn load(ent: &test_util::Entity) -> (meshtls::creds::Store, meshtls::NewClient, meshtls::Server) { - let roots_pem = std::str::from_utf8(ent.trust_anchors).expect("valid PEM"); - let (mut store, rx) = meshtls::creds::watch( - ent.name.parse().unwrap(), - roots_pem, - ent.key, - b"fake CSR data", - ) - .expect("credentials must be readable"); - - let expiry = std::time::SystemTime::now() + Duration::from_secs(600); - store - .set_certificate(DerX509(ent.crt.to_vec()), vec![], expiry) - .expect("certificate must be valid"); - - (store, rx.new_client(), rx.server()) -} +use tracing::Instrument; -#[tokio::test(flavor = "current_thread")] -async fn plaintext() { - let (_foo, _, server_tls) = load(&test_util::FOO_NS1); - let (_bar, client_tls, _) = load(&test_util::BAR_NS1); +pub async fn plaintext(mode: meshtls::Mode) { + let (_foo, _, server_tls) = load(mode, &test_util::FOO_NS1); + let (_bar, client_tls, _) = load(mode, &test_util::BAR_NS1); let (client_result, server_result) = run_test( client_tls, Conditional::None(tls::NoClientTls::NotProvidedByServiceDiscovery), @@ -77,10 +43,9 @@ async fn plaintext() { assert_eq!(&server_result.result.expect("ping")[..], PING); } -#[tokio::test(flavor = "current_thread")] -async fn proxy_to_proxy_tls_works() { - let (_foo, _, server_tls) = load(&test_util::FOO_NS1); - let (_bar, client_tls, _) = load(&test_util::BAR_NS1); +pub async fn proxy_to_proxy_tls_works(mode: meshtls::Mode) { + let (_foo, _, server_tls) = load(mode, &test_util::FOO_NS1); + let (_bar, client_tls, _) = load(mode, &test_util::BAR_NS1); let server_id = tls::ServerId(test_util::FOO_NS1.name.parse().unwrap()); let (client_result, server_result) = run_test( client_tls.clone(), @@ -108,13 +73,12 @@ async fn proxy_to_proxy_tls_works() { assert_eq!(&server_result.result.expect("ping")[..], PING); } -#[tokio::test(flavor = "current_thread")] -async fn proxy_to_proxy_tls_pass_through_when_identity_does_not_match() { - let (_foo, _, server_tls) = load(&test_util::FOO_NS1); +pub async fn proxy_to_proxy_tls_pass_through_when_identity_does_not_match(mode: meshtls::Mode) { + let (_foo, _, server_tls) = load(mode, &test_util::FOO_NS1); // Misuse the client's identity instead of the server's identity. Any // identity other than `server_tls.server_identity` would work. - let (_bar, client_tls, _) = load(&test_util::BAR_NS1); + let (_bar, client_tls, _) = load(mode, &test_util::BAR_NS1); let sni = test_util::BAR_NS1.name.parse::().unwrap(); let (client_result, server_result) = run_test( @@ -139,6 +103,33 @@ async fn proxy_to_proxy_tls_pass_through_when_identity_does_not_match() { assert_eq!(&server_result.result.unwrap()[..], START_OF_TLS); } +type ServerConn = ( + (tls::ConditionalServerTls, T), + io::EitherIo>, tls::server::DetectIo>, +); + +fn load( + mode: meshtls::Mode, + ent: &test_util::Entity, +) -> (meshtls::creds::Store, meshtls::NewClient, meshtls::Server) { + let roots_pem = std::str::from_utf8(ent.trust_anchors).expect("valid PEM"); + let (mut store, rx) = mode + .watch( + ent.name.parse().unwrap(), + roots_pem, + ent.key, + b"fake CSR data", + ) + .expect("credentials must be readable"); + + let expiry = std::time::SystemTime::now() + Duration::from_secs(600); + store + .set_certificate(DerX509(ent.crt.to_vec()), vec![], expiry) + .expect("certificate must be valid"); + + (store, rx.new_client(), rx.server()) +} + struct Transported { tls: Option, @@ -151,7 +142,8 @@ struct ServerParams { identity: meshtls::Server, } -type ClientIo = io::EitherIo, meshtls::ClientIo>>; +type ClientIo = + io::EitherIo, linkerd_meshtls::ClientIo>>; /// Runs a test for a single TCP connection. `client` processes the connection /// on the client side and `server` processes the connection on the server @@ -160,7 +152,7 @@ async fn run_test( client_tls: meshtls::NewClient, client_server_id: Conditional, client: C, - server_id: meshtls::Server, + server_tls: meshtls::Server, server: S, ) -> ( Transported, @@ -185,7 +177,7 @@ where let detect = tls::NewDetectTls::::new( ServerParams { - identity: server_id, + identity: server_tls, }, move |meta: (tls::ConditionalServerTls, Addrs)| { let server = server.clone(); From 21e002a9b6767b7466b9c411ceb9c8f7bc83f333 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 01:56:11 +0000 Subject: [PATCH 57/85] Split out tests even more --- .github/workflows/boring.yml | 6 +-- .github/workflows/test.yml | 72 ++++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml index 45853750f9..25149bbde6 100644 --- a/.github/workflows/boring.yml +++ b/.github/workflows/boring.yml @@ -15,7 +15,7 @@ permissions: jobs: # Linting - boring-clippy: + check-boring: timeout-minutes: 10 runs-on: ubuntu-latest container: @@ -25,9 +25,9 @@ jobs: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - working-directory: linkerd/meshtls - run: cargo clippy --no-default-features --features=boring + run: cargo clippy --no-default-features --features=boring --all-targets - boring-test: + test-boring: timeout-minutes: 15 runs-on: ubuntu-latest container: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a65da0fe66..57e8cccb9b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,21 +14,79 @@ permissions: jobs: - # Run all non-integration tests. + # Run non-integration tests. This should be quick. test-unit: timeout-minutes: 15 runs-on: ubuntu-latest + container: + image: docker://rust:1.56.0-buster steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cargo test --all --exclude=linkerd-app-integration --exclude=linkerd-meshtls-boring --no-run - - run: cargo test --all --exclude=linkerd-app-integration --exclude=linkerd-meshtls-boring + - run: | + cargo test --all --no-run \ + --exclude=linkerd-app \ + --exclude=linkerd-app-admin \ + --exclude=linkerd-app-core \ + --exclude=linkerd-app-gateway \ + --exclude=linkerd-app-inbound \ + --exclude=linkerd-app-integration \ + --exclude=linkerd-app-outbound \ + --exclude=linkerd-meshtls \ + --exclude=linkerd-meshtls-boring \ + --exclude=linkerd-meshtls-rustls \ + --exclude=linkerd2-proxy + - run: | + cargo test --all \ + --exclude=linkerd-app \ + --exclude=linkerd-app-admin \ + --exclude=linkerd-app-core \ + --exclude=linkerd-app-gateway \ + --exclude=linkerd-app-inbound \ + --exclude=linkerd-app-integration \ + --exclude=linkerd-app-outbound \ + --exclude=linkerd-meshtls \ + --exclude=linkerd-meshtls-boring \ + --exclude=linkerd-meshtls-rustls \ + --exclude=linkerd2-proxy - # Run only the integration tests. These have the potential to be flakey as they depend on opening - # sockets and may have timing sensitivity. + # Test the default meshtls backend. + test-rustls: + timeout-minutes: 15 + runs-on: ubuntu-latest + container: + image: docker://rust:1.56.0-buster + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - working-directory: ./linkerd/meshtls + run: cargo test --features=rustls --no-run + - working-directory: ./linkerd/meshtls + run: cargo test --features=rustls + + # Run only the app-level tests. These may take longer to compile (usually due to very large stack + # types) and have the potential to be flakey as they depend on opening sockets and may have timing + # sensitivity. test-integration: timeout-minutes: 15 runs-on: ubuntu-latest + container: + image: docker://rust:1.56.0-buster steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cargo test --package=linkerd-app-integration --no-run - - run: cargo test --package=linkerd-app-integration + - run: | + cargo test --all --no-run \ + --exclude=linkerd-app \ + --exclude=linkerd-app-admin \ + --exclude=linkerd-app-core \ + --exclude=linkerd-app-gateway \ + --exclude=linkerd-app-inbound \ + --exclude=linkerd-app-integration \ + --exclude=linkerd-app-outbound + - run: | + cargo test --all \ + --exclude=linkerd-app \ + --exclude=linkerd-app-admin \ + --exclude=linkerd-app-core \ + --exclude=linkerd-app-gateway \ + --exclude=linkerd-app-inbound \ + --exclude=linkerd-app-integration \ + --exclude=linkerd-app-outbound From b444a1e8aab17b33133ce9e0486402bf3808a18f Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 02:20:22 +0000 Subject: [PATCH 58/85] fix docs --- linkerd/meshtls/boring/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index 8063cd90fe..9117ee77f5 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -2,7 +2,7 @@ #![forbid(unsafe_code)] //! This crate provides an implementation of _meshtls_ backed by `boringssl` (as provided by -//! https://github.com/cloudflare/boring). +//! ). //! //! There are several caveats with the current implementation: //! From 3b02e069602687fc5574d95eac1d861c8d19d983 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 02:21:38 +0000 Subject: [PATCH 59/85] fixup! Split out tests even more --- .github/workflows/test.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 57e8cccb9b..d097e29878 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -74,19 +74,19 @@ jobs: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: | cargo test --all --no-run \ - --exclude=linkerd-app \ - --exclude=linkerd-app-admin \ - --exclude=linkerd-app-core \ - --exclude=linkerd-app-gateway \ - --exclude=linkerd-app-inbound \ - --exclude=linkerd-app-integration \ - --exclude=linkerd-app-outbound + --package=linkerd-app \ + --package=linkerd-app-admin \ + --package=linkerd-app-core \ + --package=linkerd-app-gateway \ + --package=linkerd-app-inbound \ + --package=linkerd-app-integration \ + --package=linkerd-app-outbound - run: | cargo test --all \ - --exclude=linkerd-app \ - --exclude=linkerd-app-admin \ - --exclude=linkerd-app-core \ - --exclude=linkerd-app-gateway \ - --exclude=linkerd-app-inbound \ - --exclude=linkerd-app-integration \ - --exclude=linkerd-app-outbound + --package=linkerd-app \ + --package=linkerd-app-admin \ + --package=linkerd-app-core \ + --package=linkerd-app-gateway \ + --package=linkerd-app-inbound \ + --package=linkerd-app-integration \ + --package=linkerd-app-outbound From c7c8f606484cb1da4446fd3b57ee83a294f0eb8e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 02:32:35 +0000 Subject: [PATCH 60/85] -all --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d097e29878..941530dda5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,7 +73,7 @@ jobs: steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: | - cargo test --all --no-run \ + cargo test --no-run \ --package=linkerd-app \ --package=linkerd-app-admin \ --package=linkerd-app-core \ @@ -82,7 +82,7 @@ jobs: --package=linkerd-app-integration \ --package=linkerd-app-outbound - run: | - cargo test --all \ + cargo test \ --package=linkerd-app \ --package=linkerd-app-admin \ --package=linkerd-app-core \ From 87f1cdabb48931439775a274b1da125867219102 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 02:55:57 +0000 Subject: [PATCH 61/85] move boring tests into test-meshtls --- .github/workflows/boring.yml | 39 ------------------------------------ .github/workflows/test.yml | 11 +++++++--- 2 files changed, 8 insertions(+), 42 deletions(-) delete mode 100644 .github/workflows/boring.yml diff --git a/.github/workflows/boring.yml b/.github/workflows/boring.yml deleted file mode 100644 index 25149bbde6..0000000000 --- a/.github/workflows/boring.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: boring - -on: - pull_request: {} - -env: - CARGO_INCREMENTAL: 0 - CARGO_NET_RETRY: 10 - RUST_BACKTRACE: short - RUSTUP_MAX_RETRIES: 10 - -permissions: - contents: read - -jobs: - - # Linting - check-boring: - timeout-minutes: 10 - runs-on: ubuntu-latest - container: - image: docker://rust:1.56.0-buster - steps: - - run: apt update && apt install -y cmake clang golang - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: rustup component add clippy - - working-directory: linkerd/meshtls - run: cargo clippy --no-default-features --features=boring --all-targets - - test-boring: - timeout-minutes: 15 - runs-on: ubuntu-latest - container: - image: docker://rust:1.56.0-buster - steps: - - run: apt update && apt install -y cmake clang golang - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - working-directory: linkerd/meshtls/boring - run: cargo test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 941530dda5..9ee5879622 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,17 +50,22 @@ jobs: --exclude=linkerd2-proxy # Test the default meshtls backend. - test-rustls: + test-meshtls: timeout-minutes: 15 runs-on: ubuntu-latest container: image: docker://rust:1.56.0-buster steps: + - run: apt update && apt install -y cmake clang golang # for boring - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - working-directory: ./linkerd/meshtls - run: cargo test --features=rustls --no-run + run: cargo test --all-features --no-run - working-directory: ./linkerd/meshtls - run: cargo test --features=rustls + run: cargo test --all-features + # Run clippy on the boring components while we have the dependencies instaled. + - run: rustup component add clippy + - working-directory: linkerd/meshtls + run: cargo clippy --no-default-features --features=boring --all-targets # Run only the app-level tests. These may take longer to compile (usually due to very large stack # types) and have the potential to be flakey as they depend on opening sockets and may have timing From f431aa09292e730a828772c153f4cd4e19e12323 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 03:11:23 +0000 Subject: [PATCH 62/85] test boring + rustls crates as well --- .github/workflows/test.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ee5879622..5fb8a5120f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,16 @@ jobs: run: cargo test --all-features --no-run - working-directory: ./linkerd/meshtls run: cargo test --all-features + - working-directory: ./linkerd/meshtls + run: | + cargo test --no-run \ + --package=linkerd-meshtls-boring \ + --package=linkerd-meshtls-rustls + - working-directory: ./linkerd/meshtls + run: | + cargo test \ + --package=linkerd-meshtls-boring \ + --package=linkerd-meshtls-rustls # Run clippy on the boring components while we have the dependencies instaled. - run: rustup component add clippy - working-directory: linkerd/meshtls From 7cd131afbf00d0f1f8891fd4f837e5749533e503 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 03:20:03 +0000 Subject: [PATCH 63/85] Fixup meshtls test clippy --- .github/workflows/test.yml | 2 +- linkerd/meshtls/tests/boring.rs | 2 ++ linkerd/meshtls/tests/rustls.rs | 2 ++ linkerd/meshtls/tests/util.rs | 4 +++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5fb8a5120f..b5c4444715 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: # Run clippy on the boring components while we have the dependencies instaled. - run: rustup component add clippy - working-directory: linkerd/meshtls - run: cargo clippy --no-default-features --features=boring --all-targets + run: cargo clippy --features=boring --all-targets # Run only the app-level tests. These may take longer to compile (usually due to very large stack # types) and have the potential to be flakey as they depend on opening sockets and may have timing diff --git a/linkerd/meshtls/tests/boring.rs b/linkerd/meshtls/tests/boring.rs index 853a3b24c5..17f3abda7d 100644 --- a/linkerd/meshtls/tests/boring.rs +++ b/linkerd/meshtls/tests/boring.rs @@ -1,4 +1,6 @@ #![cfg(feature = "boring")] +#![deny(warnings, rust_2018_idioms)] +#![forbid(unsafe_code)] mod util; diff --git a/linkerd/meshtls/tests/rustls.rs b/linkerd/meshtls/tests/rustls.rs index f6385c86b1..54f5945e83 100644 --- a/linkerd/meshtls/tests/rustls.rs +++ b/linkerd/meshtls/tests/rustls.rs @@ -1,4 +1,6 @@ #![cfg(feature = "rustls")] +#![deny(warnings, rust_2018_idioms)] +#![forbid(unsafe_code)] mod util; diff --git a/linkerd/meshtls/tests/util.rs b/linkerd/meshtls/tests/util.rs index 1598f454a9..fdd8a7a1d7 100644 --- a/linkerd/meshtls/tests/util.rs +++ b/linkerd/meshtls/tests/util.rs @@ -1,3 +1,6 @@ +#![deny(warnings)] +#![forbid(unsafe_code)] + use futures::prelude::*; use linkerd_conditional::Conditional; use linkerd_error::Infallible; @@ -226,7 +229,6 @@ where // type, e.g. `Arc`, but using a channel simplifies the code and // parallels the server side. let (sender, receiver) = mpsc::channel::>(); - let sender = sender.clone(); let tls = Some(client_server_id.clone().map(Into::into)); let client = async move { From da122a593cc258ff9355eee5a646548cf62238df Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 04:34:38 +0000 Subject: [PATCH 64/85] split workflows into slow and fast --- .github/workflows/check-release.yml | 25 ------ .github/workflows/{test.yml => fast.yml} | 103 +++++++++++----------- .github/workflows/{check.yml => slow.yml} | 52 ++++++----- 3 files changed, 81 insertions(+), 99 deletions(-) delete mode 100644 .github/workflows/check-release.yml rename .github/workflows/{test.yml => fast.yml} (74%) rename .github/workflows/{check.yml => slow.yml} (53%) diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml deleted file mode 100644 index bbe9919225..0000000000 --- a/.github/workflows/check-release.yml +++ /dev/null @@ -1,25 +0,0 @@ -# It's easy to make changes that are innocuous in dev builds that end up ballooning resources needed -# for release builds. This workflow builds the proxy in release-mode to ensure the build isn't -# OOM-killed. -name: check-release - -on: - pull_request: {} - -env: - CARGO_INCREMENTAL: 0 - CARGO_NET_RETRY: 10 - RUST_BACKTRACE: short - RUSTUP_MAX_RETRIES: 10 - CARGO_RELEASE: true - -permissions: - contents: read - -jobs: - check-release: - timeout-minutes: 20 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: make build diff --git a/.github/workflows/test.yml b/.github/workflows/fast.yml similarity index 74% rename from .github/workflows/test.yml rename to .github/workflows/fast.yml index b5c4444715..e666a60e58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/fast.yml @@ -1,4 +1,4 @@ -name: test +name: fast on: pull_request: {} @@ -14,40 +14,37 @@ permissions: jobs: - # Run non-integration tests. This should be quick. - test-unit: - timeout-minutes: 15 + # Linting + check-clippy: + timeout-minutes: 10 runs-on: ubuntu-latest container: image: docker://rust:1.56.0-buster steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: | - cargo test --all --no-run \ - --exclude=linkerd-app \ - --exclude=linkerd-app-admin \ - --exclude=linkerd-app-core \ - --exclude=linkerd-app-gateway \ - --exclude=linkerd-app-inbound \ - --exclude=linkerd-app-integration \ - --exclude=linkerd-app-outbound \ - --exclude=linkerd-meshtls \ - --exclude=linkerd-meshtls-boring \ - --exclude=linkerd-meshtls-rustls \ - --exclude=linkerd2-proxy - - run: | - cargo test --all \ - --exclude=linkerd-app \ - --exclude=linkerd-app-admin \ - --exclude=linkerd-app-core \ - --exclude=linkerd-app-gateway \ - --exclude=linkerd-app-inbound \ - --exclude=linkerd-app-integration \ - --exclude=linkerd-app-outbound \ - --exclude=linkerd-meshtls \ - --exclude=linkerd-meshtls-boring \ - --exclude=linkerd-meshtls-rustls \ - --exclude=linkerd2-proxy + - run: rustup component add clippy + - run: cargo clippy --all --exclude linkerd-meshtls-boring + + # Enforce automated formatting. + check-fmt: + timeout-minutes: 10 + runs-on: ubuntu-latest + container: + image: docker://rust:1.56.0-buster + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - run: rustup component add rustfmt + - run: make check-fmt + + # Generate docs + check-docs: + timeout-minutes: 15 + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - run: cargo doc # Test the default meshtls backend. test-meshtls: @@ -77,10 +74,8 @@ jobs: - working-directory: linkerd/meshtls run: cargo clippy --features=boring --all-targets - # Run only the app-level tests. These may take longer to compile (usually due to very large stack - # types) and have the potential to be flakey as they depend on opening sockets and may have timing - # sensitivity. - test-integration: + # Run non-integration tests. This should be quick. + test-unit: timeout-minutes: 15 runs-on: ubuntu-latest container: @@ -88,20 +83,28 @@ jobs: steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: | - cargo test --no-run \ - --package=linkerd-app \ - --package=linkerd-app-admin \ - --package=linkerd-app-core \ - --package=linkerd-app-gateway \ - --package=linkerd-app-inbound \ - --package=linkerd-app-integration \ - --package=linkerd-app-outbound + cargo test --all --no-run \ + --exclude=linkerd-app \ + --exclude=linkerd-app-admin \ + --exclude=linkerd-app-core \ + --exclude=linkerd-app-gateway \ + --exclude=linkerd-app-inbound \ + --exclude=linkerd-app-integration \ + --exclude=linkerd-app-outbound \ + --exclude=linkerd-meshtls \ + --exclude=linkerd-meshtls-boring \ + --exclude=linkerd-meshtls-rustls \ + --exclude=linkerd2-proxy - run: | - cargo test \ - --package=linkerd-app \ - --package=linkerd-app-admin \ - --package=linkerd-app-core \ - --package=linkerd-app-gateway \ - --package=linkerd-app-inbound \ - --package=linkerd-app-integration \ - --package=linkerd-app-outbound + cargo test --all \ + --exclude=linkerd-app \ + --exclude=linkerd-app-admin \ + --exclude=linkerd-app-core \ + --exclude=linkerd-app-gateway \ + --exclude=linkerd-app-inbound \ + --exclude=linkerd-app-integration \ + --exclude=linkerd-app-outbound \ + --exclude=linkerd-meshtls \ + --exclude=linkerd-meshtls-boring \ + --exclude=linkerd-meshtls-rustls \ + --exclude=linkerd2-proxy diff --git a/.github/workflows/check.yml b/.github/workflows/slow.yml similarity index 53% rename from .github/workflows/check.yml rename to .github/workflows/slow.yml index 44b4f12a6c..1c2eca0e03 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/slow.yml @@ -1,4 +1,4 @@ -name: check +name: slow on: pull_request: {} @@ -14,17 +14,6 @@ permissions: jobs: - # Linting - check-clippy: - timeout-minutes: 10 - runs-on: ubuntu-latest - container: - image: docker://rust:1.56.0-buster - steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: rustup component add clippy - - run: cargo clippy --all --exclude linkerd-meshtls-boring - # Iterate through all (non-fuzzer) sub-crates to ensure each compiles independently. check-each-crate: timeout-minutes: 20 @@ -45,23 +34,38 @@ jobs: (cd $d ; cargo check --all-targets) done - # Enforce automated formatting. - check-fmt: - timeout-minutes: 10 + check-release: + timeout-minutes: 20 runs-on: ubuntu-latest - container: - image: docker://rust:1.56.0-buster steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: rustup component add rustfmt - - run: make check-fmt + - run: make build - # Generate docs - check-docs: + # Run only the app-level tests. These may take longer to compile (usually due to very large stack + # types) and have the potential to be flakey as they depend on opening sockets and may have timing + # sensitivity. + test-integration: timeout-minutes: 15 runs-on: ubuntu-latest - permissions: - contents: read + container: + image: docker://rust:1.56.0-buster steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cargo doc + - run: | + cargo test --no-run \ + --package=linkerd-app \ + --package=linkerd-app-admin \ + --package=linkerd-app-core \ + --package=linkerd-app-gateway \ + --package=linkerd-app-inbound \ + --package=linkerd-app-integration \ + --package=linkerd-app-outbound + - run: | + cargo test \ + --package=linkerd-app \ + --package=linkerd-app-admin \ + --package=linkerd-app-core \ + --package=linkerd-app-gateway \ + --package=linkerd-app-inbound \ + --package=linkerd-app-integration \ + --package=linkerd-app-outbound From 34ab67a24756b4794b9b94596e3e47faa4f16226 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 06:01:18 +0000 Subject: [PATCH 65/85] omit deps in docs --- .github/workflows/fast.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index e666a60e58..370475adc4 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -44,7 +44,7 @@ jobs: contents: read steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cargo doc + - run: cargo doc --all --no-deps # Test the default meshtls backend. test-meshtls: From 316f20d58f5e6468e06728f827f1267ba7313019 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 06:07:50 +0000 Subject: [PATCH 66/85] combine docs with clippy --- .github/workflows/fast.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index 370475adc4..5158240fc6 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -24,6 +24,7 @@ jobs: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - run: cargo clippy --all --exclude linkerd-meshtls-boring + - run: cargo doc --all --no-deps # Enforce automated formatting. check-fmt: @@ -36,16 +37,6 @@ jobs: - run: rustup component add rustfmt - run: make check-fmt - # Generate docs - check-docs: - timeout-minutes: 15 - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cargo doc --all --no-deps - # Test the default meshtls backend. test-meshtls: timeout-minutes: 15 From 30fbe6bb76238413f01e47249dc38bc42f2df8ac Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 06:16:11 +0000 Subject: [PATCH 67/85] fixup! combine docs with clippy --- .github/workflows/fast.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index 5158240fc6..80c03c879e 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -24,7 +24,6 @@ jobs: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - run: cargo clippy --all --exclude linkerd-meshtls-boring - - run: cargo doc --all --no-deps # Enforce automated formatting. check-fmt: @@ -37,6 +36,16 @@ jobs: - run: rustup component add rustfmt - run: make check-fmt + # Generate docs + check-docs: + timeout-minutes: 15 + runs-on: ubuntu-latest + container: + image: docker://rust:1.56.0-buster + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - run: cargo doc --no-deps + # Test the default meshtls backend. test-meshtls: timeout-minutes: 15 From e5058031419a362d294418f5f4e2fc4d06819f7e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 15:28:26 +0000 Subject: [PATCH 68/85] split docs check --- .github/workflows/fast.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index 80c03c879e..22860829c8 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -44,7 +44,11 @@ jobs: image: docker://rust:1.56.0-buster steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: cargo doc --no-deps + - run: | + cargo doc --no-deps \ + --exclude=linkerd-meshtls \ + --exclude=linkerd-meshtls-boring \ + --exclude=linkerd-meshtls-rustls \ # Test the default meshtls backend. test-meshtls: @@ -73,6 +77,13 @@ jobs: - run: rustup component add clippy - working-directory: linkerd/meshtls run: cargo clippy --features=boring --all-targets + - working-directory: linkerd/meshtls + run: | + cargo doc --all-features --no-deps \ + --package=linkerd-meshtls \ + --package=linkerd-meshtls-boring \ + --package=linkerd-meshtls-rustls + # Run non-integration tests. This should be quick. test-unit: From 1c481c4ee59e21d203a8fd8049b3f0b2264e379c Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 15:31:00 +0000 Subject: [PATCH 69/85] build docs with tests --- .github/workflows/fast.yml | 29 ++++++++++++++--------------- .github/workflows/slow.yml | 10 ++++++++++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index 22860829c8..5818374804 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -36,20 +36,6 @@ jobs: - run: rustup component add rustfmt - run: make check-fmt - # Generate docs - check-docs: - timeout-minutes: 15 - runs-on: ubuntu-latest - container: - image: docker://rust:1.56.0-buster - steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: | - cargo doc --no-deps \ - --exclude=linkerd-meshtls \ - --exclude=linkerd-meshtls-boring \ - --exclude=linkerd-meshtls-rustls \ - # Test the default meshtls backend. test-meshtls: timeout-minutes: 15 @@ -84,7 +70,6 @@ jobs: --package=linkerd-meshtls-boring \ --package=linkerd-meshtls-rustls - # Run non-integration tests. This should be quick. test-unit: timeout-minutes: 15 @@ -119,3 +104,17 @@ jobs: --exclude=linkerd-meshtls-boring \ --exclude=linkerd-meshtls-rustls \ --exclude=linkerd2-proxy + - run: | + cargo doc --all --no-deps \ + --exclude=linkerd-app \ + --exclude=linkerd-app-admin \ + --exclude=linkerd-app-core \ + --exclude=linkerd-app-gateway \ + --exclude=linkerd-app-inbound \ + --exclude=linkerd-app-integration \ + --exclude=linkerd-app-outbound \ + --exclude=linkerd-meshtls \ + --exclude=linkerd-meshtls-boring \ + --exclude=linkerd-meshtls-rustls \ + --exclude=linkerd2-proxy + diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index 1c2eca0e03..ebac688e34 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -69,3 +69,13 @@ jobs: --package=linkerd-app-inbound \ --package=linkerd-app-integration \ --package=linkerd-app-outbound + - run: | + cargo doc --no-deps \ + --package=linkerd-app \ + --package=linkerd-app-admin \ + --package=linkerd-app-core \ + --package=linkerd-app-gateway \ + --package=linkerd-app-inbound \ + --package=linkerd-app-integration \ + --package=linkerd-app-outbound + From c44790bab1829476aa1d6a99391199ed7713d463 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 15:39:33 +0000 Subject: [PATCH 70/85] avoid building app-test in fast build --- .github/workflows/fast.yml | 3 +++ .github/workflows/slow.yml | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index 5818374804..10dde6b13a 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -87,6 +87,7 @@ jobs: --exclude=linkerd-app-inbound \ --exclude=linkerd-app-integration \ --exclude=linkerd-app-outbound \ + --exclude=linkerd-app-test \ --exclude=linkerd-meshtls \ --exclude=linkerd-meshtls-boring \ --exclude=linkerd-meshtls-rustls \ @@ -100,6 +101,7 @@ jobs: --exclude=linkerd-app-inbound \ --exclude=linkerd-app-integration \ --exclude=linkerd-app-outbound \ + --exclude=linkerd-app-test \ --exclude=linkerd-meshtls \ --exclude=linkerd-meshtls-boring \ --exclude=linkerd-meshtls-rustls \ @@ -113,6 +115,7 @@ jobs: --exclude=linkerd-app-inbound \ --exclude=linkerd-app-integration \ --exclude=linkerd-app-outbound \ + --exclude=linkerd-app-test \ --exclude=linkerd-meshtls \ --exclude=linkerd-meshtls-boring \ --exclude=linkerd-meshtls-rustls \ diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index ebac688e34..265669ad18 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -59,7 +59,8 @@ jobs: --package=linkerd-app-gateway \ --package=linkerd-app-inbound \ --package=linkerd-app-integration \ - --package=linkerd-app-outbound + --package=linkerd-app-outbound \ + --package=linkerd-app-test - run: | cargo test \ --package=linkerd-app \ @@ -68,7 +69,8 @@ jobs: --package=linkerd-app-gateway \ --package=linkerd-app-inbound \ --package=linkerd-app-integration \ - --package=linkerd-app-outbound + --package=linkerd-app-outbound \ + --package=linkerd-app-test - run: | cargo doc --no-deps \ --package=linkerd-app \ @@ -77,5 +79,6 @@ jobs: --package=linkerd-app-gateway \ --package=linkerd-app-inbound \ --package=linkerd-app-integration \ - --package=linkerd-app-outbound + --package=linkerd-app-outbound \ + --package=linkerd-app-test From db9210d117afa73695a3a8cd22b260ed199b62b2 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 15:50:32 +0000 Subject: [PATCH 71/85] go back to separate docs job --- .github/workflows/fast.yml | 28 ++++++++++++++-------------- .github/workflows/slow.yml | 10 ---------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index 10dde6b13a..ebc8978735 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -36,6 +36,20 @@ jobs: - run: rustup component add rustfmt - run: make check-fmt + # Generate docs + check-docs: + timeout-minutes: 15 + runs-on: ubuntu-latest + container: + image: docker://rust:1.56.0-buster + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - run: | + cargo doc --all --no-deps \ + --exclude=linkerd-meshtls \ + --exclude=linkerd-meshtls-boring \ + --exclude=linkerd-meshtls-rustls + # Test the default meshtls backend. test-meshtls: timeout-minutes: 15 @@ -106,18 +120,4 @@ jobs: --exclude=linkerd-meshtls-boring \ --exclude=linkerd-meshtls-rustls \ --exclude=linkerd2-proxy - - run: | - cargo doc --all --no-deps \ - --exclude=linkerd-app \ - --exclude=linkerd-app-admin \ - --exclude=linkerd-app-core \ - --exclude=linkerd-app-gateway \ - --exclude=linkerd-app-inbound \ - --exclude=linkerd-app-integration \ - --exclude=linkerd-app-outbound \ - --exclude=linkerd-app-test \ - --exclude=linkerd-meshtls \ - --exclude=linkerd-meshtls-boring \ - --exclude=linkerd-meshtls-rustls \ - --exclude=linkerd2-proxy diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index 265669ad18..6f5c924707 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -71,14 +71,4 @@ jobs: --package=linkerd-app-integration \ --package=linkerd-app-outbound \ --package=linkerd-app-test - - run: | - cargo doc --no-deps \ - --package=linkerd-app \ - --package=linkerd-app-admin \ - --package=linkerd-app-core \ - --package=linkerd-app-gateway \ - --package=linkerd-app-inbound \ - --package=linkerd-app-integration \ - --package=linkerd-app-outbound \ - --package=linkerd-app-test From 5028ebbde2f688d4e284173f3989968bb476305c Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 16:01:13 +0000 Subject: [PATCH 72/85] release build should actually set --release --- .github/workflows/slow.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index 6f5c924707..d474c146e7 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -39,7 +39,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - run: make build + - env: + CARGO_RELEASE: "1" + run: make build # Run only the app-level tests. These may take longer to compile (usually due to very large stack # types) and have the potential to be flakey as they depend on opening sockets and may have timing From 1445c0c7174d7839c45046e7bc760c8c488d8821 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:09:26 +0000 Subject: [PATCH 73/85] restore deps tests --- .github/workflows/fast.yml | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index ebc8978735..cc3a8bbd6d 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -73,16 +73,16 @@ jobs: cargo test \ --package=linkerd-meshtls-boring \ --package=linkerd-meshtls-rustls - # Run clippy on the boring components while we have the dependencies instaled. - - run: rustup component add clippy - - working-directory: linkerd/meshtls - run: cargo clippy --features=boring --all-targets - working-directory: linkerd/meshtls run: | cargo doc --all-features --no-deps \ --package=linkerd-meshtls \ --package=linkerd-meshtls-boring \ --package=linkerd-meshtls-rustls + # Run clippy on the boring components while we have the dependencies installed. + - run: rustup component add clippy + - working-directory: linkerd/meshtls + run: cargo clippy --features=boring --all-targets # Run non-integration tests. This should be quick. test-unit: @@ -121,3 +121,26 @@ jobs: --exclude=linkerd-meshtls-rustls \ --exclude=linkerd2-proxy + # Check for security advisories. + # + # TODO(ver): This should open issues against the linkerd2 repo (and be run in a cron). + deps-advisories: + timeout-minutes: 10 + runs-on: ubuntu-latest + # Prevent sudden announcement of a new advisory from failing Ci. + continue-on-error: true + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - uses: EmbarkStudios/cargo-deny-action@0ca727bbae7b7b578b9a5f98186caac35aa2a00d + with: + command: check advisories + + # Audit licenses, unreleased crates, and unexpected duplicate versions. + deps-bans: + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - uses: EmbarkStudios/cargo-deny-action@0ca727bbae7b7b578b9a5f98186caac35aa2a00d + with: + command: check bans licenses sources From fdad3e7fbd70e821454c90a1db44760d9686f74a Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:09:44 +0000 Subject: [PATCH 74/85] ws --- .github/workflows/fast.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index cc3a8bbd6d..e31d72950b 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - run: rustup component add clippy - - run: cargo clippy --all --exclude linkerd-meshtls-boring + - run: cargo clippy --all --exclude=linkerd-meshtls-boring # Enforce automated formatting. check-fmt: From 2ecfdf9389cb919815ae12c6c80b096b2a26683c Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:10:12 +0000 Subject: [PATCH 75/85] test timeouts --- .github/workflows/fast.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index e31d72950b..4d4d66ca00 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -38,7 +38,7 @@ jobs: # Generate docs check-docs: - timeout-minutes: 15 + timeout-minutes: 10 runs-on: ubuntu-latest container: image: docker://rust:1.56.0-buster @@ -50,9 +50,9 @@ jobs: --exclude=linkerd-meshtls-boring \ --exclude=linkerd-meshtls-rustls - # Test the default meshtls backend. + # Test the meshtls backends. test-meshtls: - timeout-minutes: 15 + timeout-minutes: 10 runs-on: ubuntu-latest container: image: docker://rust:1.56.0-buster From 9b5e53b666878a84fcff006239765a4d3e488c3a Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:10:45 +0000 Subject: [PATCH 76/85] undo needless changes --- .github/workflows/fast.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index 4d4d66ca00..c6c6b2bb1b 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -36,7 +36,7 @@ jobs: - run: rustup component add rustfmt - run: make check-fmt - # Generate docs + # Generate docs. check-docs: timeout-minutes: 10 runs-on: ubuntu-latest @@ -86,7 +86,7 @@ jobs: # Run non-integration tests. This should be quick. test-unit: - timeout-minutes: 15 + timeout-minutes: 10 runs-on: ubuntu-latest container: image: docker://rust:1.56.0-buster From 3dbc316ee5e1adf87a93c602827d137287f38709 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:16:21 +0000 Subject: [PATCH 77/85] fixup boring dev deps --- Cargo.lock | 3 --- linkerd/meshtls/boring/Cargo.toml | 7 +------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dce9b045ec..c32a4ce8fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,16 +1211,13 @@ dependencies = [ "boring", "futures", "hex", - "linkerd-conditional", "linkerd-dns-name", "linkerd-error", "linkerd-identity", "linkerd-io", - "linkerd-proxy-transport", "linkerd-stack", "linkerd-tls", "linkerd-tls-test-util", - "linkerd-tracing", "tokio", "tokio-boring", "tracing", diff --git a/linkerd/meshtls/boring/Cargo.toml b/linkerd/meshtls/boring/Cargo.toml index 6f4e3f938b..fb1002d89c 100644 --- a/linkerd/meshtls/boring/Cargo.toml +++ b/linkerd/meshtls/boring/Cargo.toml @@ -9,6 +9,7 @@ publish = false [dependencies] boring = "1" futures = { version = "0.3", default-features = false } +hex = "0.4" # used for debug logging linkerd-error = { path = "../../error" } linkerd-dns-name = { path = "../../dns/name" } linkerd-identity = { path = "../../identity" } @@ -19,11 +20,5 @@ tokio = { version = "1", features = ["macros", "sync"] } tokio-boring = "2" tracing = "0.1" -hex = "0.4" - [dev-dependencies] -linkerd-conditional = { path = "../../conditional" } -linkerd-proxy-transport = { path = "../../proxy/transport" } linkerd-tls-test-util = { path = "../../tls/test-util" } -linkerd-tracing = { path = "../../tracing" } -tokio = { version = "1", features = ["rt-multi-thread"] } From cc50fa18e91bd393830db973b2a0b5c7f0a5d05e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:17:23 +0000 Subject: [PATCH 78/85] back-out needless change to slow.yml --- .github/workflows/slow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index d474c146e7..159423e9c8 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -47,7 +47,7 @@ jobs: # types) and have the potential to be flakey as they depend on opening sockets and may have timing # sensitivity. test-integration: - timeout-minutes: 15 + timeout-minutes: 20 runs-on: ubuntu-latest container: image: docker://rust:1.56.0-buster From 53dfd0609331d25b2b903d0f04e03c33d251a3a5 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:18:09 +0000 Subject: [PATCH 79/85] back-out needless change to fast.yml --- .github/workflows/fast.yml | 1 + .github/workflows/slow.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index c6c6b2bb1b..5565010c07 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -1,3 +1,4 @@ +# Each job should typically run in under 5 minutes. name: fast on: diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index 159423e9c8..ed7e77b782 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -1,3 +1,4 @@ +# Each job typically runs for more than 5 minutes. name: slow on: From 92d4eb9622d11d29c1ce521ce641dbff496a43fe Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:20:34 +0000 Subject: [PATCH 80/85] wrap comments more tightly --- linkerd/meshtls/boring/src/lib.rs | 27 ++++++++++++++------------- linkerd/meshtls/src/lib.rs | 14 ++++++++------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/linkerd/meshtls/boring/src/lib.rs b/linkerd/meshtls/boring/src/lib.rs index 9117ee77f5..d9f01312ac 100644 --- a/linkerd/meshtls/boring/src/lib.rs +++ b/linkerd/meshtls/boring/src/lib.rs @@ -1,25 +1,26 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -//! This crate provides an implementation of _meshtls_ backed by `boringssl` (as provided by -//! ). +//! This crate provides an implementation of _meshtls_ backed by `boringssl` (as +//! provided by ). //! //! There are several caveats with the current implementation: //! -//! In its current form, this crate is compatible with the `meshtls-rustls` implementation, which -//! requires of ECDSA-P256-SHA256 keys & signature algorithms. This crate doesn't actually constrain -//! the algorithms beyond the Mozilla's 'intermediate' (v5) [defaults][defaults]. But, the goal for -//! supporting `boring` is to provide a FIPS 140-2 compliant mode. There's a [PR][fips-pr] that -//! implements this, but code changes will likely be required to enable this once it's -//! merged/released. +//! In its current form, this crate is compatible with the `meshtls-rustls` +//! implementation, which requires of ECDSA-P256-SHA256 keys & signature +//! algorithms. This crate doesn't actually constrain the algorithms beyond the +//! Mozilla's 'intermediate' (v5) [defaults][defaults]. But, the goal for +//! supporting `boring` is to provide a FIPS 140-2 compliant mode. There's a +//! [PR][fips-pr] that implements this, but code changes will likely be required +//! to enable this once it's merged/released. //! -//! A new SSL context is created for each connection. This is probably unnecessary, but it's simpler -//! for now. We can revisit this if needed. +//! A new SSL context is created for each connection. This is probably +//! unnecessary, but it's simpler for now. We can revisit this if needed. //! -//! This module is not enabled by default. See the `linkerd-meshtls` and `linkerd2-proxy` crates for -//! more information. +//! This module is not enabled by default. See the `linkerd-meshtls` and +//! `linkerd2-proxy` crates for more information. //! -//! [defaults]: https://wiki.mozilla.org/Security/Server_Side_TLS. +//! [defaults]: https://wiki.mozilla.org/Security/Server_Side_TLS //! [fips-pr]: https://github.com/cloudflare/boring/pull/52 mod client; diff --git a/linkerd/meshtls/src/lib.rs b/linkerd/meshtls/src/lib.rs index 0c39827496..fbdc4b68ed 100644 --- a/linkerd/meshtls/src/lib.rs +++ b/linkerd/meshtls/src/lib.rs @@ -1,13 +1,15 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -//! This crate provides a static interface for the proxy's x509 certificate provisioning and -//! creation of client/server services. It supports the `boring` and `rustls` TLS backends. +//! This crate provides a static interface for the proxy's x509 certificate +//! provisioning and creation of client/server services. It supports the +//! `boring` and `rustls` TLS backends. //! -//! This crate may be compiled without either implementation, in which case it will fail at runtime. -//! This enables an implementation to be chosen by the proxy's frontend, so that other crates can -//! depend on this crate without having to pin a TLS implementation. Furthermore, this crate -//! supports both backends simultaneously so it can be compiled with `--all-features`. +//! This crate may be compiled without either implementation, in which case it +//! will fail at runtime. This enables an implementation to be chosen by the +//! proxy's frontend, so that other crates can depend on this crate without +//! having to pin a TLS implementation. Furthermore, this crate supports both +//! backends simultaneously so it can be compiled with `--all-features`. mod client; pub mod creds; From eb77991142b86fe73f0bf09918d5ad64360c6844 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:25:07 +0000 Subject: [PATCH 81/85] back-out rustls changes in favor of #1367 --- linkerd/meshtls/rustls/src/client.rs | 25 ++++++++++++++++--------- linkerd/meshtls/rustls/src/server.rs | 22 +++++++++++++--------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/linkerd/meshtls/rustls/src/client.rs b/linkerd/meshtls/rustls/src/client.rs index 87420b4009..052f445020 100644 --- a/linkerd/meshtls/rustls/src/client.rs +++ b/linkerd/meshtls/rustls/src/client.rs @@ -2,7 +2,7 @@ use futures::prelude::*; use linkerd_io as io; use linkerd_stack::{NewService, Service}; use linkerd_tls::{client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef}; -use std::{pin::Pin, sync::Arc, task::Context}; +use std::{pin::Pin, sync::Arc}; use tokio::sync::watch; use tokio_rustls::rustls::{ClientConfig, Session}; @@ -84,8 +84,11 @@ where type Error = io::Error; type Future = ConnectFuture; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> io::Poll<()> { - io::Poll::Ready(Ok(())) + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) } fn call(&mut self, io: I) -> Self::Future { @@ -101,7 +104,7 @@ impl io::AsyncRead for ClientIo { #[inline] fn poll_read( mut self: Pin<&mut Self>, - cx: &mut Context<'_>, + cx: &mut std::task::Context<'_>, buf: &mut io::ReadBuf<'_>, ) -> io::Poll<()> { Pin::new(&mut self.0).poll_read(cx, buf) @@ -110,26 +113,30 @@ impl io::AsyncRead for ClientIo { impl io::AsyncWrite for ClientIo { #[inline] - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_flush(cx) } #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_shutdown(cx) } #[inline] - fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> io::Poll { Pin::new(&mut self.0).poll_write(cx, buf) } #[inline] fn poll_write_vectored( mut self: Pin<&mut Self>, - cx: &mut Context<'_>, + cx: &mut std::task::Context<'_>, bufs: &[io::IoSlice<'_>], - ) -> io::Poll { + ) -> std::task::Poll> { Pin::new(&mut self.0).poll_write_vectored(cx, bufs) } diff --git a/linkerd/meshtls/rustls/src/server.rs b/linkerd/meshtls/rustls/src/server.rs index e57e0feb47..6fd5806761 100644 --- a/linkerd/meshtls/rustls/src/server.rs +++ b/linkerd/meshtls/rustls/src/server.rs @@ -5,7 +5,7 @@ use linkerd_stack::{Param, Service}; use linkerd_tls::{ ClientId, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, ServerTls, }; -use std::{pin::Pin, sync::Arc, task::Context}; +use std::{pin::Pin, sync::Arc, task}; use thiserror::Error; use tokio::sync::watch; use tokio_rustls::rustls::{Certificate, ServerConfig, Session}; @@ -99,8 +99,8 @@ where type Future = TerminateFuture; #[inline] - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> io::Poll<()> { - io::Poll::Ready(Ok(())) + fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { + task::Poll::Ready(Ok(())) } #[inline] @@ -152,7 +152,7 @@ impl io::AsyncRead for ServerIo { #[inline] fn poll_read( mut self: Pin<&mut Self>, - cx: &mut Context<'_>, + cx: &mut std::task::Context<'_>, buf: &mut io::ReadBuf<'_>, ) -> io::Poll<()> { Pin::new(&mut self.0).poll_read(cx, buf) @@ -161,26 +161,30 @@ impl io::AsyncRead for ServerIo { impl io::AsyncWrite for ServerIo { #[inline] - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_flush(cx) } #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> io::Poll<()> { + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { Pin::new(&mut self.0).poll_shutdown(cx) } #[inline] - fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> io::Poll { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> io::Poll { Pin::new(&mut self.0).poll_write(cx, buf) } #[inline] fn poll_write_vectored( mut self: Pin<&mut Self>, - cx: &mut Context<'_>, + cx: &mut std::task::Context<'_>, bufs: &[io::IoSlice<'_>], - ) -> io::Poll { + ) -> std::task::Poll> { Pin::new(&mut self.0).poll_write_vectored(cx, bufs) } From 5716f5cf1b265d38c2b56eb12554d426c962ca04 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:25:55 +0000 Subject: [PATCH 82/85] -dead_code --- linkerd/meshtls/boring/src/creds.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/linkerd/meshtls/boring/src/creds.rs b/linkerd/meshtls/boring/src/creds.rs index 37a599c764..79f2651440 100644 --- a/linkerd/meshtls/boring/src/creds.rs +++ b/linkerd/meshtls/boring/src/creds.rs @@ -165,7 +165,6 @@ impl Creds { /// Encodes a list of ALPN protocols into a slice of bytes. /// /// `boring` requires that the list of protocols be encoded in the wire format. -#[allow(dead_code)] fn serialize_alpn(protocols: &[Vec]) -> Result> { // Allocate a buffer to hold the encoded protocols. let mut bytes = { From 466fd6d304af0af212e0351fa6b34ec936fd11ef Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 18:26:52 +0000 Subject: [PATCH 83/85] meshtls: Add a `boring` backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change adds a `meshtls-boring` proxy feature that can be used to compile the proxy with an alternate TLS implementation. The `meshtls-rustls` feature should be disabled to take advantage of this alternate backend. In its current mode, the boring backend is compatible with the existing identity credentials and algorithms (specifically TLSv1.3 and ECDSA-P256-SHA256 with CHACHA20-POLY1305-SHA256). In future changes--once `boring` has been updated--we can: - Improve error handling, especially for SSL errors - Relax deny.toml changes needed by bindgen features - Add a FIPS mode Co-authored-by: Arnar Páll From 845bd7fc3f87397ffc9c4129a673826c65e000b1 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 21:19:30 +0000 Subject: [PATCH 84/85] needless match --- linkerd/meshtls/boring/src/client.rs | 8 ++++---- linkerd/meshtls/boring/src/server.rs | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/linkerd/meshtls/boring/src/client.rs b/linkerd/meshtls/boring/src/client.rs index 8b1da23f35..723836c4c8 100644 --- a/linkerd/meshtls/boring/src/client.rs +++ b/linkerd/meshtls/boring/src/client.rs @@ -73,10 +73,10 @@ where fn call(&mut self, io: I) -> Self::Future { let id = self.server_id.clone(); - let connector = match &self.alpn { - None => self.rx.borrow().connector(&[]), - Some(alpn) => self.rx.borrow().connector(alpn), - }; + let connector = self + .rx + .borrow() + .connector(self.alpn.as_deref().unwrap_or(&[])); Box::pin(async move { let conn = connector.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let config = conn diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index 224a3bed66..6f52fc3a22 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -60,11 +60,10 @@ where fn call(&mut self, io: I) -> Self::Future { // TODO(ver) we should avoid creating a new context for each connection. - let acceptor = match &self.alpn { - Some(alpn) => self.rx.borrow().acceptor(alpn), - None => self.rx.borrow().acceptor(&[]), - }; - + let acceptor = self + .rx + .borrow() + .acceptor(self.alpn.as_deref().unwrap_or(&[])); Box::pin(async move { let acc = acceptor.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let io = tokio_boring::accept(&acc, io) From 1d3227fa636e7fd3981aa7bd01d8a21e331dda3e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 8 Nov 2021 21:25:03 +0000 Subject: [PATCH 85/85] let with_alpn clear alpn protocols as well --- linkerd/meshtls/boring/src/server.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/linkerd/meshtls/boring/src/server.rs b/linkerd/meshtls/boring/src/server.rs index 6f52fc3a22..9d512471de 100644 --- a/linkerd/meshtls/boring/src/server.rs +++ b/linkerd/meshtls/boring/src/server.rs @@ -31,9 +31,11 @@ impl Server { } pub fn with_alpn(mut self, alpn_protocols: Vec>) -> Self { - if !alpn_protocols.is_empty() { - self.alpn = Some(alpn_protocols.into()); - } + self.alpn = if alpn_protocols.is_empty() { + None + } else { + Some(alpn_protocols.into()) + }; self }