From bc679c1a29f8111880999eabed28e331eda42efc Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 8 Oct 2021 18:20:37 +0000 Subject: [PATCH 01/31] wip --- Cargo.lock | 1 + linkerd/app/core/src/control.rs | 4 +- linkerd/proxy/identity/Cargo.toml | 1 + linkerd/proxy/identity/src/certify.rs | 22 +++---- linkerd/tls/src/client.rs | 87 ++++++++++----------------- 5 files changed, 46 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 773dacb59b..007a498488 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1155,6 +1155,7 @@ dependencies = [ "pin-project", "thiserror", "tokio", + "tokio-rustls", "tonic", "tracing", ] diff --git a/linkerd/app/core/src/control.rs b/linkerd/app/core/src/control.rs index 232e0284cf..2c2ad70f3e 100644 --- a/linkerd/app/core/src/control.rs +++ b/linkerd/app/core/src/control.rs @@ -43,7 +43,7 @@ impl Config { self, dns: dns::Resolver, metrics: metrics::ControlHttp, - identity: Option, + identity: L, ) -> svc::ArcNewService< (), impl svc::Service< @@ -54,7 +54,7 @@ impl Config { > + Clone, > where - L: Clone + svc::Param + Send + Sync + 'static, + L: Clone + svc::NewService + Send + Sync + 'static, { let addr = self.addr; diff --git a/linkerd/proxy/identity/Cargo.toml b/linkerd/proxy/identity/Cargo.toml index c077b9418f..72f37bb0cd 100644 --- a/linkerd/proxy/identity/Cargo.toml +++ b/linkerd/proxy/identity/Cargo.toml @@ -19,6 +19,7 @@ linkerd-stack = { path = "../../stack" } linkerd-tls = { path = "../../tls" } thiserror = "1" tokio = { version = "1", features = ["time", "sync"] } +tokio-rustls = "0.22" tonic = { version = "0.5", default-features = false } tracing = "0.1.29" http-body = "0.4" diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index ae814b6cee..3dc44a42ab 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -6,9 +6,11 @@ use linkerd_metrics::Counter; use linkerd_stack::{NewService, Param}; use linkerd_tls as tls; use pin_project::pin_project; -use std::convert::TryFrom; -use std::sync::Arc; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{ + convert::TryFrom, + sync::Arc, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; use thiserror::Error; use tokio::sync::watch; use tokio::time::{self, Sleep}; @@ -207,7 +209,7 @@ impl LocalCrtKey { self.id.as_ref() } - pub fn client_config(&self) -> tls::client::Config { + pub fn client_config(&self) -> Arc { if let Some(ref c) = *self.crt_key.borrow() { return c.client_config(); } @@ -215,7 +217,7 @@ impl LocalCrtKey { self.trust_anchors.client_config() } - pub fn server_config(&self) -> tls::server::Config { + pub fn server_config(&self) -> Arc { if let Some(ref c) = *self.crt_key.borrow() { return c.server_config(); } @@ -224,14 +226,8 @@ impl LocalCrtKey { } } -impl Param for LocalCrtKey { - fn param(&self) -> tls::client::Config { - self.client_config() - } -} - -impl Param for LocalCrtKey { - fn param(&self) -> tls::server::Config { +impl Param> for LocalCrtKey { + fn param(&self) -> Arc { self.server_config() } } diff --git a/linkerd/tls/src/client.rs b/linkerd/tls/src/client.rs index ba051a4cb0..23b4d104be 100644 --- a/linkerd/tls/src/client.rs +++ b/linkerd/tls/src/client.rs @@ -1,3 +1,4 @@ +use crate::{HasNegotiatedProtocol, NegotiatedProtocolRef}; use futures::{ future::{Either, MapOk}, prelude::*, @@ -5,24 +6,22 @@ use futures::{ use linkerd_conditional::Conditional; use linkerd_identity as id; use linkerd_io as io; -use linkerd_stack::{layer, Param}; +use linkerd_stack::{layer, NewService, Param, Service, ServiceExt}; use std::{ fmt, future::Future, pin::Pin, str::FromStr, - sync::Arc, task::{Context, Poll}, }; pub use tokio_rustls::client::TlsStream; -use tokio_rustls::rustls::{self, Session}; -use tracing::{debug, trace}; +use tracing::debug; /// A newtype for target server identities. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ServerId(pub id::Name); -/// A stack paramter that configures a `Client` to establish a TLS connection. +/// A stack parameter that configures a `Client` to establish a TLS connection. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ClientTls { pub server_id: ServerId, @@ -54,20 +53,12 @@ pub enum NoClientTls { /// known TLS identity. pub type ConditionalClientTls = Conditional; -pub type Config = Arc; - #[derive(Clone, Debug)] pub struct Client { - local: Option, + identity: L, inner: C, } -type Connect = MapOk io::EitherIo>>; -type Handshake = - Pin>>> + Send + 'static>>; - -pub type Io = io::EitherIo>; - // === impl ClientTls === impl From for ClientTls { @@ -82,25 +73,37 @@ impl From for ClientTls { // === impl Client === impl Client { - pub fn layer(local: Option) -> impl layer::Layer + Clone { + pub fn layer(identity: L) -> impl layer::Layer + Clone { layer::mk(move |inner| Self { inner, - local: local.clone(), + identity: identity.clone(), }) } } -impl tower::Service for Client +impl Service for Client where - L: Clone + Param, T: Param, - C: tower::Service, + L: NewService, + C: Service, C::Response: io::AsyncRead + io::AsyncWrite + Send + Unpin, C::Future: Send + 'static, + H: Service + Send + 'static, + H::Response: io::AsyncRead + io::AsyncWrite + Send + Unpin + HasNegotiatedProtocol, + H::Future: Send + 'static, { - type Response = Io; + type Response = io::EitherIo; type Error = io::Error; - type Future = Either, Handshake>; + type Future = Either< + MapOk io::EitherIo>, + Pin< + Box< + dyn Future>> + + Send + + 'static, + >, + >, + >; #[inline] fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { @@ -108,48 +111,24 @@ where } fn call(&mut self, target: T) -> Self::Future { - let ClientTls { server_id, alpn } = match target.param() { - Conditional::Some(tls) => tls, + let handshake = match target.param() { + Conditional::Some(tls) => self.identity.new_service(tls), Conditional::None(reason) => { debug!(%reason, "Peer does not support TLS"); return Either::Left(self.inner.call(target).map_ok(io::EitherIo::Left)); } }; - let handshake = match self.local.as_ref() { - Some(local) => { - // Build a rustls ClientConfig for this connection. - // - // 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. - match alpn { - None => tokio_rustls::TlsConnector::from(local.param()), - Some(AlpnProtocols(protocols)) => { - let mut config: rustls::ClientConfig = local.param().as_ref().clone(); - config.alpn_protocols = protocols; - tokio_rustls::TlsConnector::from(Arc::new(config)) - } - } - } - None => { - trace!("Local identity disabled"); - return Either::Left(self.inner.call(target).map_ok(io::EitherIo::Left)); - } - }; - - debug!(server.id = %server_id, "Initiating TLS connection"); let connect = self.inner.call(target); Either::Right(Box::pin(async move { let io = connect.await?; - let io = handshake.connect((&server_id.0).into(), io).await?; - if let Some(alpn) = io.get_ref().1.get_alpn_protocol() { - debug!(alpn = ?std::str::from_utf8(alpn)); - } + let io = handshake.oneshot(io).await?; + debug!( + alpn = io + .negotiated_protocol() + .and_then(|NegotiatedProtocolRef(p)| std::str::from_utf8(p).ok()) + .map(tracing::field::display) + ); Ok(io::EitherIo::Right(io)) })) } From 9baee40cd13cae9ad0253ac65e08ce61e6faf98b Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 10 Oct 2021 18:47:22 +0000 Subject: [PATCH 02/31] Decouple TLS client from rustls --- Cargo.lock | 1 + linkerd/app/core/src/control.rs | 5 +- .../outbound/src/http/require_id_header.rs | 11 ++- linkerd/dns/name/src/name.rs | 5 ++ linkerd/identity/src/lib.rs | 19 ++--- linkerd/proxy/identity/src/certify.rs | 11 ++- linkerd/tls/Cargo.toml | 1 + linkerd/tls/src/client.rs | 80 ++++++++++++------- linkerd/tls/src/lib.rs | 11 +-- linkerd/tls/src/rustls.rs | 40 ++++++++++ linkerd/tls/src/server/mod.rs | 7 +- linkerd/tls/tests/tls_accept.rs | 25 ++++-- 12 files changed, 153 insertions(+), 63 deletions(-) create mode 100644 linkerd/tls/src/rustls.rs diff --git a/Cargo.lock b/Cargo.lock index 7ce1aa88d1..fd37b487c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1367,6 +1367,7 @@ dependencies = [ "linkerd-proxy-transport", "linkerd-stack", "linkerd-tracing", + "pin-project", "thiserror", "tokio", "tokio-rustls", diff --git a/linkerd/app/core/src/control.rs b/linkerd/app/core/src/control.rs index 2c2ad70f3e..92a912864b 100644 --- a/linkerd/app/core/src/control.rs +++ b/linkerd/app/core/src/control.rs @@ -54,7 +54,8 @@ impl Config { > + Clone, > where - L: Clone + svc::NewService + Send + Sync + 'static, + L: svc::NewService, + L: Clone + Send + Sync + 'static, { let addr = self.addr; @@ -84,7 +85,9 @@ impl Config { }; svc::stack(ConnectTcp::new(self.connect.keepalive)) + .check_service::() .push(tls::Client::layer(identity)) + .check_service::() .push_connect_timeout(self.connect.timeout) .push(self::client::layer()) .push_on_service(svc::MapErr::layer(Into::into)) diff --git a/linkerd/app/outbound/src/http/require_id_header.rs b/linkerd/app/outbound/src/http/require_id_header.rs index a25c2f9609..b317b1ae60 100644 --- a/linkerd/app/outbound/src/http/require_id_header.rs +++ b/linkerd/app/outbound/src/http/require_id_header.rs @@ -85,16 +85,19 @@ where // In either case, we clear the header so it is not passed on outbound requests. if let Some(require_id) = Self::extract_id(&mut request) { match self.tls.as_ref() { - Conditional::Some(tls::ClientTls { server_id, .. }) => { - if require_id != *server_id.as_ref() { + Conditional::Some(tls::ClientTls { + server_id: tls::ServerId(sni), + .. + }) => { + if require_id != *sni { debug!( required = %require_id, - found = %server_id, + found = %sni, "Identity required by header not satisfied" ); let e = IdentityRequired { required: require_id.into(), - found: Some(server_id.clone()), + found: Some(tls::ServerId(sni.clone())), }; return future::Either::Left(future::err(e.into())); } else { diff --git a/linkerd/dns/name/src/name.rs b/linkerd/dns/name/src/name.rs index add757da31..99c41b284e 100644 --- a/linkerd/dns/name/src/name.rs +++ b/linkerd/dns/name/src/name.rs @@ -22,6 +22,11 @@ impl Name { pub fn without_trailing_dot(&self) -> &str { self.as_ref().trim_end_matches('.') } + + #[inline] + pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { + self.0.as_ref() + } } impl fmt::Debug for Name { diff --git a/linkerd/identity/src/lib.rs b/linkerd/identity/src/lib.rs index 5c9d9649cf..7acc9895d2 100644 --- a/linkerd/identity/src/lib.rs +++ b/linkerd/identity/src/lib.rs @@ -134,9 +134,10 @@ impl From for Name { } } -impl<'t> From<&'t LocalId> for webpki::DNSNameRef<'t> { - fn from(LocalId(ref name): &'t LocalId) -> webpki::DNSNameRef<'t> { - name.into() +impl Name { + #[inline] + pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { + self.0.as_webpki() } } @@ -261,7 +262,7 @@ impl TrustAnchors { static NO_OCSP: &[u8] = &[]; client .get_verifier() - .verify_server_cert(&client.root_store, &crt.chain, (&crt.id).into(), NO_OCSP) + .verify_server_cert(&client.root_store, &crt.chain, crt.id.as_webpki(), NO_OCSP) .map_err(InvalidCrt)?; debug!("certified {}", crt.id); @@ -323,7 +324,7 @@ impl Crt { } pub fn name(&self) -> &Name { - self.id.as_ref() + &self.id.0 } } @@ -337,7 +338,7 @@ impl From<&'_ Crt> for LocalId { impl CrtKey { pub fn name(&self) -> &Name { - self.id.as_ref() + &self.id.0 } pub fn expiry(&self) -> SystemTime { @@ -439,9 +440,9 @@ impl From for Name { } } -impl AsRef for LocalId { - fn as_ref(&self) -> &Name { - &self.0 +impl LocalId { + pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { + self.0.as_webpki() } } diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index 5193048778..b127ae3b81 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -227,7 +227,7 @@ impl LocalCrtKey { } pub fn name(&self) -> &id::Name { - self.id.as_ref() + &self.id.0 } pub fn client_config(&self) -> Arc { @@ -247,6 +247,15 @@ impl LocalCrtKey { } } +impl NewService for LocalCrtKey { + type Service = tls::rustls::Connect; + + fn new_service(&self, target: tls::ClientTls) -> Self::Service { + // TODO: ALPN + tls::rustls::Connect::new(target, self.client_config()) + } +} + impl Param> for LocalCrtKey { fn param(&self) -> Arc { self.server_config() diff --git a/linkerd/tls/Cargo.toml b/linkerd/tls/Cargo.toml index 4021592ff6..050931f520 100644 --- a/linkerd/tls/Cargo.toml +++ b/linkerd/tls/Cargo.toml @@ -16,6 +16,7 @@ linkerd-error = { path = "../error" } linkerd-identity = { path = "../identity" } linkerd-io = { path = "../io" } linkerd-stack = { path = "../stack" } +pin-project = "1" thiserror = "1.0" tokio = { version = "1", features = ["macros", "time"] } tokio-rustls = "0.22" diff --git a/linkerd/tls/src/client.rs b/linkerd/tls/src/client.rs index 23b4d104be..ec070cec52 100644 --- a/linkerd/tls/src/client.rs +++ b/linkerd/tls/src/client.rs @@ -1,12 +1,9 @@ use crate::{HasNegotiatedProtocol, NegotiatedProtocolRef}; -use futures::{ - future::{Either, MapOk}, - prelude::*, -}; +use futures::prelude::*; use linkerd_conditional::Conditional; use linkerd_identity as id; use linkerd_io as io; -use linkerd_stack::{layer, NewService, Param, Service, ServiceExt}; +use linkerd_stack::{layer, NewService, Oneshot, Param, Service, ServiceExt}; use std::{ fmt, future::Future, @@ -59,6 +56,13 @@ pub struct Client { inner: C, } +#[pin_project::pin_project(project = ConnectProj)] +#[derive(Debug)] +pub enum Connect> { + Connect(#[pin] F, Option), + Handshake(#[pin] Oneshot), +} + // === impl ClientTls === impl From for ClientTls { @@ -94,16 +98,7 @@ where { type Response = io::EitherIo; type Error = io::Error; - type Future = Either< - MapOk io::EitherIo>, - Pin< - Box< - dyn Future>> - + Send - + 'static, - >, - >, - >; + type Future = Connect; #[inline] fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { @@ -112,25 +107,48 @@ where fn call(&mut self, target: T) -> Self::Future { let handshake = match target.param() { - Conditional::Some(tls) => self.identity.new_service(tls), + Conditional::Some(tls) => Some(self.identity.new_service(tls)), Conditional::None(reason) => { debug!(%reason, "Peer does not support TLS"); - return Either::Left(self.inner.call(target).map_ok(io::EitherIo::Left)); + None } }; let connect = self.inner.call(target); - Either::Right(Box::pin(async move { - let io = connect.await?; - let io = handshake.oneshot(io).await?; - debug!( - alpn = io - .negotiated_protocol() - .and_then(|NegotiatedProtocolRef(p)| std::str::from_utf8(p).ok()) - .map(tracing::field::display) - ); - Ok(io::EitherIo::Right(io)) - })) + Connect::Connect(connect, handshake) + } +} + +impl Future for Connect +where + F: TryFuture, + H: Service, + H::Response: HasNegotiatedProtocol, +{ + type Output = io::Result>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + match self.as_mut().project() { + ConnectProj::Connect(fut, tls) => { + let io = futures::ready!(fut.try_poll(cx))?; + match tls.take() { + None => return Poll::Ready(Ok(io::EitherIo::Left(io))), + Some(tls) => self.set(Connect::Handshake(tls.oneshot(io))), + } + } + ConnectProj::Handshake(fut) => { + let io = futures::ready!(fut.try_poll(cx))?; + debug!( + alpn = io + .negotiated_protocol() + .and_then(|NegotiatedProtocolRef(p)| std::str::from_utf8(p).ok()) + .map(tracing::field::display) + ); + return Poll::Ready(Ok(io::EitherIo::Right(io))); + } + } + } } } @@ -148,9 +166,9 @@ impl From for id::Name { } } -impl AsRef for ServerId { - fn as_ref(&self) -> &id::Name { - &self.0 +impl ServerId { + pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { + self.0.as_webpki() } } diff --git a/linkerd/tls/src/lib.rs b/linkerd/tls/src/lib.rs index b9e17d7251..6445ab1c06 100755 --- a/linkerd/tls/src/lib.rs +++ b/linkerd/tls/src/lib.rs @@ -1,19 +1,20 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -pub use linkerd_identity::LocalId; -use linkerd_io as io; -pub use tokio_rustls::rustls::Session; - pub mod client; +pub mod rustls; pub mod server; +pub use self::rustls::Session; +pub use linkerd_identity::LocalId; +use linkerd_io as io; + pub use self::{ client::{Client, ClientTls, ConditionalClientTls, NoClientTls, ServerId}, server::{ClientId, ConditionalServerTls, NewDetectTls, NoServerTls, ServerTls}, }; -/// A trait implented by transport streams to indicate its negotiated protocol. +/// A trait implemented by transport streams to indicate its negotiated protocol. pub trait HasNegotiatedProtocol { fn negotiated_protocol(&self) -> Option>; } diff --git a/linkerd/tls/src/rustls.rs b/linkerd/tls/src/rustls.rs new file mode 100644 index 0000000000..b7f80c3e30 --- /dev/null +++ b/linkerd/tls/src/rustls.rs @@ -0,0 +1,40 @@ +use crate::ClientTls; +use linkerd_io as io; +use linkerd_stack::Service; +use std::sync::Arc; +pub use tokio_rustls::rustls::*; + +#[derive(Clone)] +pub struct Connect { + client_tls: ClientTls, + config: Arc, +} + +pub type ConnectFuture = tokio_rustls::Connect; + +impl Connect { + pub fn new(client_tls: ClientTls, config: Arc) -> Self { + Self { client_tls, config } + } +} + +impl Service for Connect +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + type Response = tokio_rustls::client::TlsStream; + 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.client_tls.server_id.as_webpki(), io) + } +} diff --git a/linkerd/tls/src/server/mod.rs b/linkerd/tls/src/server/mod.rs index 3740c41e36..0f5c505c82 100644 --- a/linkerd/tls/src/server/mod.rs +++ b/linkerd/tls/src/server/mod.rs @@ -8,7 +8,7 @@ use linkerd_dns_name as dns; use linkerd_error::Error; use linkerd_identity as id; use linkerd_io::{self as io, AsyncReadExt, EitherIo, PrefixedIo}; -use linkerd_stack::{layer, ExtractParam, InsertParam, NewService, Param}; +use linkerd_stack::{layer, ExtractParam, InsertParam, NewService, Param, Service, ServiceExt}; use std::{ fmt, pin::Pin, @@ -20,7 +20,6 @@ use thiserror::Error; use tokio::time::{self, Duration}; use tokio_rustls::rustls::{self, Session}; pub use tokio_rustls::server::TlsStream; -use tower::util::ServiceExt; use tracing::{debug, trace, warn}; pub type Config = Arc; @@ -139,7 +138,7 @@ where } } -impl tower::Service for DetectTls +impl Service for DetectTls where I: io::Peek + io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, T: Clone + Send + 'static, @@ -147,7 +146,7 @@ where P::Target: Send + 'static, L: Param + Param, N: NewService + Clone + Send + 'static, - NSvc: tower::Service, Response = ()> + Send + 'static, + NSvc: Service, Response = ()> + Send + 'static, NSvc::Error: Into, NSvc::Future: Send, { diff --git a/linkerd/tls/tests/tls_accept.rs b/linkerd/tls/tests/tls_accept.rs index 2440ae3972..155ca65103 100644 --- a/linkerd/tls/tests/tls_accept.rs +++ b/linkerd/tls/tests/tls_accept.rs @@ -17,8 +17,13 @@ use linkerd_proxy_transport::{ }; use linkerd_stack::{ExtractParam, InsertParam, NewService, Param}; use linkerd_tls as tls; -use std::{future::Future, time::Duration}; -use std::{net::SocketAddr, sync::mpsc}; +use std::{ + future::Future, + net::SocketAddr, + sync::{mpsc, Arc}, + time::Duration, +}; +use tls::client::TlsStream; use tokio::net::TcpStream; use tower::{ layer::Layer, @@ -130,6 +135,8 @@ struct ServerParams { identity: id::CrtKey, } +type ClientIo = io::EitherIo, TlsStream>>; + /// 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. @@ -145,7 +152,7 @@ async fn run_test( ) where // Client - C: FnOnce(tls::client::Io>) -> CF + Clone + Send + 'static, + C: FnOnce(ClientIo) -> CF + Clone + Send + 'static, CF: Future> + Send + 'static, CR: Send + 'static, // Server @@ -328,14 +335,16 @@ impl Param for Target { // === impl Tls === -impl Param for Tls { - fn param(&self) -> tls::client::Config { - self.0.client_config() +impl NewService for Tls { + type Service = tls::rustls::Connect; + + fn new_service(&self, target: tls::ClientTls) -> Self::Service { + tls::rustls::Connect::new(target, self.0.client_config()) } } -impl Param for Tls { - fn param(&self) -> tls::server::Config { +impl Param> for Tls { + fn param(&self) -> Arc { self.0.server_config() } } From 075ce6df6bb61495c64d48559df8b85a20522377 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 10 Oct 2021 19:41:34 +0000 Subject: [PATCH 03/31] wip:server --- linkerd/proxy/identity/src/certify.rs | 11 +-- linkerd/proxy/tap/src/accept.rs | 7 +- linkerd/tls/src/client.rs | 1 - linkerd/tls/src/lib.rs | 20 ------ linkerd/tls/src/rustls.rs | 100 +++++++++++++++++++++++++- linkerd/tls/src/server/mod.rs | 91 ++++------------------- linkerd/tls/tests/tls_accept.rs | 9 ++- 7 files changed, 129 insertions(+), 110 deletions(-) diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index b127ae3b81..fb161c7857 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -230,7 +230,7 @@ impl LocalCrtKey { &self.id.0 } - pub fn client_config(&self) -> Arc { + pub fn client_config(&self) -> Arc { if let Some(ref c) = *self.crt_key.borrow() { return c.client_config(); } @@ -238,12 +238,13 @@ impl LocalCrtKey { self.trust_anchors.client_config() } - pub fn server_config(&self) -> Arc { + pub fn server_config(&self) -> Arc { if let Some(ref c) = *self.crt_key.borrow() { return c.server_config(); } - tls::server::empty_config() + let verifier = tls::rustls::NoClientAuth::new(); + Arc::new(tls::rustls::ServerConfig::new(verifier)) } } @@ -256,8 +257,8 @@ impl NewService for LocalCrtKey { } } -impl Param> for LocalCrtKey { - fn param(&self) -> Arc { +impl Param> for LocalCrtKey { + fn param(&self) -> Arc { self.server_config() } } diff --git a/linkerd/proxy/tap/src/accept.rs b/linkerd/proxy/tap/src/accept.rs index aa1514ef5d..333f9386de 100644 --- a/linkerd/proxy/tap/src/accept.rs +++ b/linkerd/proxy/tap/src/accept.rs @@ -5,7 +5,7 @@ use linkerd_conditional::Conditional; use linkerd_error::Error; use linkerd_io as io; use linkerd_proxy_http::{trace, HyperServerSvc}; -use linkerd_tls::{self as tls}; +use linkerd_tls as tls; use std::{ collections::HashSet, future::Future, @@ -21,7 +21,10 @@ pub struct AcceptPermittedClients { server: Server, } -type Connection = ((tls::ConditionalServerTls, T), tls::server::Io); +type Connection = ( + (tls::ConditionalServerTls, T), + io::EitherIo>, tls::server::DetectIo>, +); pub type ServeFuture = Pin> + Send + 'static>>; diff --git a/linkerd/tls/src/client.rs b/linkerd/tls/src/client.rs index ec070cec52..951548e05b 100644 --- a/linkerd/tls/src/client.rs +++ b/linkerd/tls/src/client.rs @@ -11,7 +11,6 @@ use std::{ str::FromStr, task::{Context, Poll}, }; -pub use tokio_rustls::client::TlsStream; use tracing::debug; /// A newtype for target server identities. diff --git a/linkerd/tls/src/lib.rs b/linkerd/tls/src/lib.rs index 6445ab1c06..2205a446a0 100755 --- a/linkerd/tls/src/lib.rs +++ b/linkerd/tls/src/lib.rs @@ -59,26 +59,6 @@ impl std::fmt::Debug for NegotiatedProtocolRef<'_> { } } -impl HasNegotiatedProtocol for self::client::TlsStream { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) - } -} - -impl HasNegotiatedProtocol for self::server::TlsStream { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) - } -} - impl HasNegotiatedProtocol for tokio::net::TcpStream { #[inline] fn negotiated_protocol(&self) -> Option> { diff --git a/linkerd/tls/src/rustls.rs b/linkerd/tls/src/rustls.rs index b7f80c3e30..3eaddea45f 100644 --- a/linkerd/tls/src/rustls.rs +++ b/linkerd/tls/src/rustls.rs @@ -1,8 +1,14 @@ -use crate::ClientTls; +use crate::{ + ClientId, ClientTls, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, + ServerTls, +}; +use futures::prelude::*; use linkerd_io as io; use linkerd_stack::Service; use std::sync::Arc; pub use tokio_rustls::rustls::*; +pub use tokio_rustls::{client::TlsStream as ClientStream, server::TlsStream as ServerStream}; +use tracing::debug; #[derive(Clone)] pub struct Connect { @@ -10,8 +16,20 @@ pub struct Connect { config: Arc, } +#[derive(Clone)] +pub struct Terminate { + config: Arc, +} + pub type ConnectFuture = tokio_rustls::Connect; +pub type TerminateFuture = futures::future::MapOk< + tokio_rustls::Accept, + fn(ServerStream) -> (ServerTls, ServerStream), +>; + +// === impl Connect === + impl Connect { pub fn new(client_tls: ClientTls, config: Arc) -> Self { Self { client_tls, config } @@ -22,7 +40,7 @@ impl Service for Connect where I: io::AsyncRead + io::AsyncWrite + Send + Unpin, { - type Response = tokio_rustls::client::TlsStream; + type Response = ClientStream; type Error = io::Error; type Future = ConnectFuture; @@ -38,3 +56,81 @@ where .connect(self.client_tls.server_id.as_webpki(), io) } } + +// === impl Terminate === + +impl Service for Terminate +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + type Response = (ServerTls, ServerStream); + type Error = io::Error; + type Future = TerminateFuture; + + 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::TlsAcceptor::from(self.config.clone()).accept(io).map_ok(|io| { + // Determine the peer's identity, if it exist. + let client_id = Self::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, io) + }) + } +} + +impl Terminate { + fn client_identity(tls: &ServerStream) -> 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) => Some(ClientId(linkerd_identity::Name::from( + linkerd_dns_name::Name::from(n.to_owned()), + ))), + webpki::GeneralDNSNameRef::Wildcard(_) => { + // Wildcards can perhaps be handled in a future path... + None + } + } + } +} + +impl HasNegotiatedProtocol for ClientStream { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.get_ref() + .1 + .get_alpn_protocol() + .map(NegotiatedProtocolRef) + } +} + +impl HasNegotiatedProtocol for ServerStream { + #[inline] + fn negotiated_protocol(&self) -> Option> { + self.get_ref() + .1 + .get_alpn_protocol() + .map(NegotiatedProtocolRef) + } +} diff --git a/linkerd/tls/src/server/mod.rs b/linkerd/tls/src/server/mod.rs index 0f5c505c82..a99d33964e 100644 --- a/linkerd/tls/src/server/mod.rs +++ b/linkerd/tls/src/server/mod.rs @@ -1,35 +1,23 @@ mod client_hello; -use crate::{LocalId, NegotiatedProtocol, ServerId}; +use crate::{NegotiatedProtocol, ServerId}; use bytes::BytesMut; use futures::prelude::*; use linkerd_conditional::Conditional; -use linkerd_dns_name as dns; use linkerd_error::Error; use linkerd_identity as id; use linkerd_io::{self as io, AsyncReadExt, EitherIo, PrefixedIo}; -use linkerd_stack::{layer, ExtractParam, InsertParam, NewService, Param, Service, ServiceExt}; +use linkerd_stack::{layer, ExtractParam, InsertParam, NewService, Service, ServiceExt}; use std::{ fmt, pin::Pin, str::FromStr, - sync::Arc, task::{Context, Poll}, }; use thiserror::Error; use tokio::time::{self, Duration}; -use tokio_rustls::rustls::{self, Session}; -pub use tokio_rustls::server::TlsStream; use tracing::{debug, trace, warn}; -pub type Config = Arc; - -/// Produces a server config that fails to handshake all connections. -pub fn empty_config() -> Config { - let verifier = rustls::NoClientAuth::new(); - Arc::new(rustls::ServerConfig::new(verifier)) -} - /// A newtype for remote client idenities. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ClientId(pub id::Name); @@ -66,9 +54,9 @@ pub enum NoServerTls { /// Indicates whether TLS was established on an accepted connection. pub type ConditionalServerTls = Conditional; -type DetectIo = EitherIo>; +pub type DetectIo = EitherIo>; -pub type Io = EitherIo>, DetectIo>; +pub type Io = EitherIo>; #[derive(Clone, Debug)] pub struct NewDetectTls { @@ -138,15 +126,18 @@ where } } -impl Service for DetectTls +impl Service for DetectTls where I: io::Peek + io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, T: Clone + Send + 'static, P: InsertParam + Clone + Send + Sync + 'static, P::Target: Send + 'static, - L: Param + Param, + L: NewService + Clone + Send + 'static, + LSvc: Service, Response = (ServerTls, LIo), Error = io::Error> + Send + 'static, + LSvc::Future: Send, + LIo: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, N: NewService + Clone + Send + 'static, - NSvc: Service, Response = ()> + Send + 'static, + NSvc: Service>, Response = ()> + Send + 'static, NSvc::Error: Into, NSvc::Future: Send, { @@ -163,8 +154,7 @@ where let params = self.params.clone(); let new_accept = self.inner.clone(); - let config: Config = self.local_identity.param(); - let LocalId(local_id) = self.local_identity.param(); + let tls = self.local_identity.clone(); // Detect the SNI from a ClientHello (or timeout). let Timeout(timeout) = self.timeout; @@ -174,19 +164,11 @@ where let (peer, io) = match sni { // If we detected an SNI matching this proxy, terminate TLS. - Some(ServerId(id)) if id == local_id => { - trace!("Identified local SNI"); - let (peer, io) = handshake(config, io).await?; - (Conditional::Some(peer), EitherIo::Left(io)) - } - // If we detected another SNI, continue proxying the - // opaque stream. Some(sni) => { - debug!(%sni, "Identified foreign SNI"); - let peer = ServerTls::Passthru { sni }; - (Conditional::Some(peer), EitherIo::Right(io)) + let tls = tls.new_service(sni); + let (peer, io) = tls.oneshot(io).await?; + (Conditional::Some(peer), EitherIo::Left(io)) } - // If no TLS was detected, continue proxying the stream. None => ( Conditional::None(NoServerTls::NoClientHello), EitherIo::Right(io), @@ -254,51 +236,6 @@ where Ok((None, io)) } -async fn handshake(tls_config: Config, io: T) -> io::Result<(ServerTls, TlsStream)> -where - T: io::AsyncRead + io::AsyncWrite + Unpin, -{ - let io = tokio_rustls::TlsAcceptor::from(tls_config) - .accept(io) - .await?; - - // 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, - }; - Ok((tls, io)) -} - -fn client_identity(tls: &TlsStream) -> Option { - use webpki::GeneralDNSNameRef; - - let (_io, session) = tls.get_ref(); - let certs = session.get_peer_certificates()?; - let c = certs.first().map(rustls::Certificate::as_ref)?; - let end_cert = webpki::EndEntityCert::from(c).ok()?; - let dns_names = end_cert.dns_names().ok()?; - - match dns_names.first()? { - GeneralDNSNameRef::DNSName(n) => { - Some(ClientId(id::Name::from(dns::Name::from(n.to_owned())))) - } - GeneralDNSNameRef::Wildcard(_) => { - // Wildcards can perhaps be handled in a future path... - None - } - } -} - // === impl ClientId === impl From for ClientId { diff --git a/linkerd/tls/tests/tls_accept.rs b/linkerd/tls/tests/tls_accept.rs index 155ca65103..2dfd4a6434 100644 --- a/linkerd/tls/tests/tls_accept.rs +++ b/linkerd/tls/tests/tls_accept.rs @@ -23,7 +23,6 @@ use std::{ sync::{mpsc, Arc}, time::Duration, }; -use tls::client::TlsStream; use tokio::net::TcpStream; use tower::{ layer::Layer, @@ -31,7 +30,10 @@ use tower::{ }; use tracing::instrument::Instrument; -type ServerConn = ((tls::ConditionalServerTls, T), tls::server::Io); +type ServerConn = ( + (tls::ConditionalServerTls, T), + io::EitherIo>, tls::server::DetectIo>, +); #[tokio::test(flavor = "current_thread")] async fn plaintext() { @@ -135,7 +137,8 @@ struct ServerParams { identity: id::CrtKey, } -type ClientIo = io::EitherIo, TlsStream>>; +type ClientIo = + io::EitherIo, tls::rustls::ClientStream>>; /// Runs a test for a single TCP connection. `client` processes the connection /// on the client side and `server` processes the connection on the server From e6b36c0282b665e884b1d0527df988ce3857f34d Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 10 Oct 2021 23:09:16 +0000 Subject: [PATCH 04/31] factor out server TLS as well --- linkerd/app/inbound/src/detect.rs | 9 +++- linkerd/app/inbound/src/direct.rs | 66 +++++++++++++++++---------- linkerd/proxy/identity/src/certify.rs | 29 +++++++++--- linkerd/proxy/tap/src/accept.rs | 2 +- linkerd/tls/src/rustls.rs | 66 ++++++++++----------------- linkerd/tls/src/server/mod.rs | 20 ++++---- linkerd/tls/tests/tls_accept.rs | 32 ++++++++----- 7 files changed, 132 insertions(+), 92 deletions(-) diff --git a/linkerd/app/inbound/src/detect.rs b/linkerd/app/inbound/src/detect.rs index 43c551af7f..8e2ad854ee 100644 --- a/linkerd/app/inbound/src/detect.rs +++ b/linkerd/app/inbound/src/detect.rs @@ -53,6 +53,8 @@ struct TlsParams { identity: LocalCrtKey, } +type TlsIo = tls::server::Io>>; + // === impl Inbound === impl Inbound { @@ -92,7 +94,7 @@ impl Inbound { I: Debug + Send + Sync + Unpin + 'static, N: svc::NewService, N: Clone + Send + Sync + Unpin + 'static, - NSvc: svc::Service, Response = ()>, + NSvc: svc::Service, Response = ()>, NSvc: Send + Unpin + 'static, NSvc::Error: Into, NSvc::Future: Send, @@ -107,6 +109,7 @@ impl Inbound { let detect_timeout = cfg.proxy.detect_protocol_timeout; detect + .check_new_service::>() .push_switch( // Ensure that the connection is authorized before proceeding with protocol // detection. @@ -133,12 +136,15 @@ impl Inbound { forward .clone() .push_on_service(svc::MapTargetLayer::new(io::BoxedIo::new)) + .check_new_service::>() .into_inner(), ) + .check_new_service::<(_, T), TlsIo>() .push(tls::NewDetectTls::::layer(TlsParams { timeout: tls::server::Timeout(detect_timeout), identity: rt.identity.clone(), })) + .check_new_service::() .push_switch( // If this port's policy indicates that authentication is not required and // detection should be skipped, use the TCP stack directly. @@ -160,6 +166,7 @@ impl Inbound { .push_on_service(svc::MapTargetLayer::new(io::BoxedIo::new)) .into_inner(), ) + .check_new_service::() .push_on_service(svc::BoxService::layer()) .push(svc::ArcNewService::layer()) }) diff --git a/linkerd/app/inbound/src/direct.rs b/linkerd/app/inbound/src/direct.rs index 0a88b31449..77f0768c79 100644 --- a/linkerd/app/inbound/src/direct.rs +++ b/linkerd/app/inbound/src/direct.rs @@ -8,7 +8,7 @@ use linkerd_app_core::{ transport_header::{self, NewTransportHeaderServer, SessionProtocol, TransportHeader}, Conditional, Error, NameAddr, Result, }; -use std::{convert::TryFrom, fmt::Debug}; +use std::{convert::TryFrom, fmt::Debug, task}; use thiserror::Error; use tracing::{debug_span, info_span}; @@ -52,8 +52,9 @@ pub struct ClientInfo { pub local_addr: OrigDstAddr, } -type FwdIo = SensorIo>>; -pub type GatewayIo = io::EitherIo, SensorIo>>; +type TlsIo = tls::server::Io>>; +type FwdIo = SensorIo>>; +pub type GatewayIo = io::EitherIo, SensorIo>>; #[derive(Clone)] struct TlsParams { @@ -129,8 +130,13 @@ impl Inbound { negotiated_protocol: client.alpn, }, ); - let permit = allow.check_authorized(client.client_addr, &tls)?; - Ok(svc::Either::A(Local { addr: Remote(ServerAddr(addr)), permit, client_id: client.client_id, })) + let permit = + allow.check_authorized(client.client_addr, &tls)?; + Ok(svc::Either::A(Local { + addr: Remote(ServerAddr(addr)), + permit, + client_id: client.client_id, + })) } TransportHeader { port, @@ -167,29 +173,30 @@ impl Inbound { .instrument( |g: &GatewayTransportHeader| info_span!("gateway", dst = %g.target), ) - .check_new_service::>>() + .check_new_service::>>() .into_inner(), ) // Use ALPN to determine whether a transport header should be read. .push(NewTransportHeaderServer::layer(detect_timeout)) - .push_request_filter( - |client: ClientInfo| -> Result<_> { - if client.header_negotiated() { - Ok(client) - } else { - Err(RefusedNoTarget.into()) - } - }, - ) - .check_new_service::>() + .push_request_filter(|client: ClientInfo| -> Result<_> { + if client.header_negotiated() { + Ok(client) + } else { + Err(RefusedNoTarget.into()) + } + }) + .check_new_service::>() // Build a ClientInfo target for each accepted connection. Refuse the // connection if it doesn't include an mTLS identity. .push_request_filter(ClientInfo::try_from) .push(svc::ArcNewService::layer()) - .push(tls::NewDetectTls::::layer(TlsParams { - timeout: tls::server::Timeout(detect_timeout), - identity: WithTransportHeaderAlpn(rt.identity.clone()), - })) + .check_new_service::<(_, T), TlsIo>() + .push(tls::NewDetectTls::::layer( + TlsParams { + timeout: tls::server::Timeout(detect_timeout), + identity: WithTransportHeaderAlpn(rt.identity.clone()), + }, + )) .check_new_service::() .push_on_service(svc::BoxService::layer()) .push(svc::ArcNewService::layer()) @@ -293,8 +300,21 @@ impl Param for GatewayTransportHeader { // === impl WithTransportHeaderAlpn === -impl svc::Param for WithTransportHeaderAlpn { - fn param(&self) -> tls::server::Config { +impl svc::Service for WithTransportHeaderAlpn +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + type Response = (tls::ServerTls, tls::rustls::ServerIo); + type Error = io::Error; + type Future = tls::rustls::TerminateFuture; + + #[inline] + fn poll_ready(&mut self, _: &mut task::Context<'_>) -> task::Poll> { + task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { // Copy the underlying TLS config and set an ALPN value. // // TODO: Avoid cloning the server config for every connection. It would @@ -304,7 +324,7 @@ impl svc::Param for WithTransportHeaderAlpn { config .alpn_protocols .push(transport_header::PROTOCOL.into()); - config.into() + tls::rustls::terminate(config.into(), io) } } diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index fb161c7857..e018746ffc 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -3,17 +3,21 @@ use linkerd2_proxy_api::identity::{self as api, identity_client::IdentityClient} use linkerd_error::Error; use linkerd_identity as id; use linkerd_metrics::Counter; -use linkerd_stack::{NewService, Param}; +use linkerd_stack::{NewService, Param, Service}; use linkerd_tls as tls; use pin_project::pin_project; use std::{ convert::TryFrom, sync::Arc, + task, time::{Duration, SystemTime, UNIX_EPOCH}, }; use thiserror::Error; -use tokio::sync::watch; -use tokio::time::{self, Sleep}; +use tokio::{ + io, + sync::watch, + time::{self, Sleep}, +}; use tonic::{self as grpc, body::BoxBody, client::GrpcService}; use tracing::{debug, error, trace}; @@ -257,9 +261,22 @@ impl NewService for LocalCrtKey { } } -impl Param> for LocalCrtKey { - fn param(&self) -> Arc { - self.server_config() +impl Service for LocalCrtKey +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + type Response = (tls::ServerTls, tls::rustls::ServerIo); + type Error = io::Error; + type Future = tls::rustls::TerminateFuture; + + #[inline] + fn poll_ready(&mut self, _: &mut task::Context<'_>) -> task::Poll> { + task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + tls::rustls::terminate(self.server_config(), io) } } diff --git a/linkerd/proxy/tap/src/accept.rs b/linkerd/proxy/tap/src/accept.rs index 333f9386de..862a032609 100644 --- a/linkerd/proxy/tap/src/accept.rs +++ b/linkerd/proxy/tap/src/accept.rs @@ -23,7 +23,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>>; diff --git a/linkerd/tls/src/rustls.rs b/linkerd/tls/src/rustls.rs index 3eaddea45f..065bd5ae70 100644 --- a/linkerd/tls/src/rustls.rs +++ b/linkerd/tls/src/rustls.rs @@ -6,8 +6,7 @@ use futures::prelude::*; use linkerd_io as io; use linkerd_stack::Service; use std::sync::Arc; -pub use tokio_rustls::rustls::*; -pub use tokio_rustls::{client::TlsStream as ClientStream, server::TlsStream as ServerStream}; +pub use tokio_rustls::{client::TlsStream as ClientIo, rustls::*, server::TlsStream as ServerIo}; use tracing::debug; #[derive(Clone)] @@ -23,10 +22,8 @@ pub struct Terminate { pub type ConnectFuture = tokio_rustls::Connect; -pub type TerminateFuture = futures::future::MapOk< - tokio_rustls::Accept, - fn(ServerStream) -> (ServerTls, ServerStream), ->; +pub type TerminateFuture = + futures::future::MapOk, fn(ServerIo) -> (ServerTls, ServerIo)>; // === impl Connect === @@ -40,7 +37,7 @@ impl Service for Connect where I: io::AsyncRead + io::AsyncWrite + Send + Unpin, { - type Response = ClientStream; + type Response = ClientIo; type Error = io::Error; type Future = ConnectFuture; @@ -59,25 +56,15 @@ where // === impl Terminate === -impl Service for Terminate +pub fn terminate(config: Arc, io: I) -> TerminateFuture where I: io::AsyncRead + io::AsyncWrite + Send + Unpin, { - type Response = (ServerTls, ServerStream); - type Error = io::Error; - type Future = TerminateFuture; - - 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::TlsAcceptor::from(self.config.clone()).accept(io).map_ok(|io| { + tokio_rustls::TlsAcceptor::from(config) + .accept(io) + .map_ok(|io| { // Determine the peer's identity, if it exist. - let client_id = Self::client_identity(&io); + let client_id = client_identity(&io); let negotiated_protocol = io .get_ref() @@ -92,30 +79,27 @@ where }; (tls, io) }) - } } -impl Terminate { - fn client_identity(tls: &ServerStream) -> 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) => Some(ClientId(linkerd_identity::Name::from( - linkerd_dns_name::Name::from(n.to_owned()), - ))), - webpki::GeneralDNSNameRef::Wildcard(_) => { - // Wildcards can perhaps be handled in a future path... - None - } +fn client_identity(tls: &ServerIo) -> 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) => Some(ClientId(linkerd_identity::Name::from( + linkerd_dns_name::Name::from(n.to_owned()), + ))), + webpki::GeneralDNSNameRef::Wildcard(_) => { + // Wildcards can perhaps be handled in a future path... + None } } } -impl HasNegotiatedProtocol for ClientStream { +impl HasNegotiatedProtocol for ClientIo { #[inline] fn negotiated_protocol(&self) -> Option> { self.get_ref() @@ -125,7 +109,7 @@ impl HasNegotiatedProtocol for ClientStream { } } -impl HasNegotiatedProtocol for ServerStream { +impl HasNegotiatedProtocol for ServerIo { #[inline] fn negotiated_protocol(&self) -> Option> { self.get_ref() diff --git a/linkerd/tls/src/server/mod.rs b/linkerd/tls/src/server/mod.rs index a99d33964e..5ac0dde78a 100644 --- a/linkerd/tls/src/server/mod.rs +++ b/linkerd/tls/src/server/mod.rs @@ -7,7 +7,7 @@ use linkerd_conditional::Conditional; use linkerd_error::Error; use linkerd_identity as id; use linkerd_io::{self as io, AsyncReadExt, EitherIo, PrefixedIo}; -use linkerd_stack::{layer, ExtractParam, InsertParam, NewService, Service, ServiceExt}; +use linkerd_stack::{layer, ExtractParam, InsertParam, NewService, Param, Service, ServiceExt}; use std::{ fmt, pin::Pin, @@ -126,18 +126,18 @@ where } } -impl Service for DetectTls +impl Service for DetectTls where I: io::Peek + io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, T: Clone + Send + 'static, P: InsertParam + Clone + Send + Sync + 'static, P::Target: Send + 'static, - L: NewService + Clone + Send + 'static, - LSvc: Service, Response = (ServerTls, LIo), Error = io::Error> + Send + 'static, - LSvc::Future: Send, + L: Param + Clone + Send + 'static, + L: Service, Response = (ServerTls, LIo), Error = io::Error>, + L::Future: Send, LIo: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, N: NewService + Clone + Send + 'static, - NSvc: Service>, Response = ()> + Send + 'static, + NSvc: Service, Response = ()> + Send + 'static, NSvc::Error: Into, NSvc::Future: Send, { @@ -162,13 +162,17 @@ where Box::pin(async move { let (sni, io) = detect.await.map_err(|_| ServerTlsTimeoutError(()))??; + let id::LocalId(id) = tls.param(); let (peer, io) = match sni { // If we detected an SNI matching this proxy, terminate TLS. - Some(sni) => { - let tls = tls.new_service(sni); + Some(ServerId(sni)) if sni == id => { let (peer, io) = tls.oneshot(io).await?; (Conditional::Some(peer), EitherIo::Left(io)) } + Some(sni) => { + let peer = ServerTls::Passthru { sni }; + (Conditional::Some(peer), EitherIo::Right(io)) + } None => ( Conditional::None(NoServerTls::NoClientHello), EitherIo::Right(io), diff --git a/linkerd/tls/tests/tls_accept.rs b/linkerd/tls/tests/tls_accept.rs index 2dfd4a6434..103ca00c72 100644 --- a/linkerd/tls/tests/tls_accept.rs +++ b/linkerd/tls/tests/tls_accept.rs @@ -15,14 +15,9 @@ use linkerd_proxy_transport::{ listen::{Addrs, Bind, BindTcp}, ConnectTcp, Keepalive, ListenAddr, }; -use linkerd_stack::{ExtractParam, InsertParam, NewService, Param}; +use linkerd_stack::{ExtractParam, InsertParam, NewService, Param, Service}; use linkerd_tls as tls; -use std::{ - future::Future, - net::SocketAddr, - sync::{mpsc, Arc}, - time::Duration, -}; +use std::{future::Future, net::SocketAddr, sync::mpsc, task, time::Duration}; use tokio::net::TcpStream; use tower::{ layer::Layer, @@ -32,7 +27,7 @@ use tracing::instrument::Instrument; type ServerConn = ( (tls::ConditionalServerTls, T), - io::EitherIo>, tls::server::DetectIo>, + io::EitherIo>, tls::server::DetectIo>, ); #[tokio::test(flavor = "current_thread")] @@ -138,7 +133,7 @@ struct ServerParams { } type ClientIo = - io::EitherIo, tls::rustls::ClientStream>>; + io::EitherIo, tls::rustls::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 @@ -346,9 +341,22 @@ impl NewService for Tls { } } -impl Param> for Tls { - fn param(&self) -> Arc { - self.0.server_config() +impl Service for Tls +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + type Response = (tls::ServerTls, tls::rustls::ServerIo); + type Error = io::Error; + type Future = tls::rustls::TerminateFuture; + + #[inline] + fn poll_ready(&mut self, _: &mut task::Context<'_>) -> task::Poll> { + task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, io: I) -> Self::Future { + tls::rustls::terminate(self.0.server_config(), io) } } From 77345e5812cecb960843917373ce7b3ce27f754a Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 10 Oct 2021 23:31:48 +0000 Subject: [PATCH 05/31] Split rustls into its own module --- Cargo.lock | 23 ++- Cargo.toml | 1 + linkerd/app/core/Cargo.toml | 1 + linkerd/app/core/src/control.rs | 2 +- linkerd/app/core/src/lib.rs | 1 + linkerd/app/inbound/src/detect.rs | 4 +- linkerd/app/inbound/src/direct.rs | 9 +- linkerd/identity/Cargo.toml | 1 + linkerd/proxy/identity/Cargo.toml | 2 +- linkerd/proxy/identity/src/certify.rs | 21 ++- linkerd/proxy/tap/Cargo.toml | 1 + linkerd/proxy/tap/src/accept.rs | 2 +- linkerd/tls/Cargo.toml | 2 +- linkerd/tls/rustls/Cargo.toml | 19 ++ linkerd/tls/rustls/src/lib.rs | 247 ++++++++++++++++++++++++++ linkerd/tls/src/lib.rs | 2 - linkerd/tls/src/rustls.rs | 120 ------------- linkerd/tls/tests/tls_accept.rs | 16 +- 18 files changed, 321 insertions(+), 153 deletions(-) create mode 100644 linkerd/tls/rustls/Cargo.toml create mode 100644 linkerd/tls/rustls/src/lib.rs delete mode 100644 linkerd/tls/src/rustls.rs diff --git a/Cargo.lock b/Cargo.lock index fd37b487c5..618b071cce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,6 +689,7 @@ dependencies = [ "linkerd-stack-tracing", "linkerd-system", "linkerd-tls", + "linkerd-tls-rustls", "linkerd-trace-context", "linkerd-tracing", "linkerd-transport-header", @@ -992,6 +993,7 @@ name = "linkerd-identity" version = "0.1.0" dependencies = [ "linkerd-dns-name", + "pin-project", "ring", "thiserror", "tokio-rustls", @@ -1153,11 +1155,11 @@ dependencies = [ "linkerd-metrics", "linkerd-stack", "linkerd-tls", + "linkerd-tls-rustls", "linkerd2-proxy-api", "pin-project", "thiserror", "tokio", - "tokio-rustls", "tonic", "tracing", ] @@ -1191,6 +1193,7 @@ dependencies = [ "linkerd-proxy-transport", "linkerd-stack", "linkerd-tls", + "linkerd-tls-rustls", "linkerd2-proxy-api", "parking_lot", "pin-project", @@ -1366,17 +1369,33 @@ dependencies = [ "linkerd-io", "linkerd-proxy-transport", "linkerd-stack", + "linkerd-tls-rustls", "linkerd-tracing", "pin-project", "thiserror", "tokio", - "tokio-rustls", "tower", "tracing", "untrusted", "webpki", ] +[[package]] +name = "linkerd-tls-rustls" +version = "0.1.0" +dependencies = [ + "futures", + "linkerd-dns-name", + "linkerd-identity", + "linkerd-io", + "linkerd-stack", + "linkerd-tls", + "pin-project", + "tokio-rustls", + "tracing", + "webpki", +] + [[package]] name = "linkerd-tonic-watch" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 1ce77e888b..3599140009 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ members = [ "linkerd/system", "linkerd/tonic-watch", "linkerd/tls", + "linkerd/tls/rustls", "linkerd/tracing", "linkerd/transport-header", "linkerd/transport-metrics", diff --git a/linkerd/app/core/Cargo.toml b/linkerd/app/core/Cargo.toml index 654b192d97..6d219c1c5e 100644 --- a/linkerd/app/core/Cargo.toml +++ b/linkerd/app/core/Cargo.toml @@ -58,6 +58,7 @@ linkerd-tracing = { path = "../../tracing" } linkerd-transport-header = { path = "../../transport-header" } linkerd-transport-metrics = { path = "../../transport-metrics" } linkerd-tls = { path = "../../tls" } +linkerd-tls-rustls = { path = "../../tls/rustls" } linkerd-trace-context = { path = "../../trace-context" } regex = "1.5.4" serde_json = "1" diff --git a/linkerd/app/core/src/control.rs b/linkerd/app/core/src/control.rs index 92a912864b..caa678ccfb 100644 --- a/linkerd/app/core/src/control.rs +++ b/linkerd/app/core/src/control.rs @@ -54,7 +54,7 @@ impl Config { > + Clone, > where - L: svc::NewService, + L: svc::NewService, L: Clone + Send + Sync + 'static, { let addr = self.addr; diff --git a/linkerd/app/core/src/lib.rs b/linkerd/app/core/src/lib.rs index 866e0cf255..e004e730a2 100644 --- a/linkerd/app/core/src/lib.rs +++ b/linkerd/app/core/src/lib.rs @@ -27,6 +27,7 @@ pub use linkerd_service_profiles as profiles; pub use linkerd_stack_metrics as stack_metrics; pub use linkerd_stack_tracing as stack_tracing; pub use linkerd_tls as tls; +pub use linkerd_tls_rustls as rustls; pub use linkerd_tracing as trace; pub use linkerd_transport_header as transport_header; diff --git a/linkerd/app/inbound/src/detect.rs b/linkerd/app/inbound/src/detect.rs index 8e2ad854ee..dee3dfafdd 100644 --- a/linkerd/app/inbound/src/detect.rs +++ b/linkerd/app/inbound/src/detect.rs @@ -5,7 +5,7 @@ use crate::{ use linkerd_app_core::{ detect, identity, io, proxy::{http, identity::LocalCrtKey}, - svc, tls, + rustls, svc, tls, transport::{ self, addrs::{ClientAddr, OrigDstAddr, Remote}, @@ -53,7 +53,7 @@ struct TlsParams { identity: LocalCrtKey, } -type TlsIo = tls::server::Io>>; +type TlsIo = tls::server::Io>>; // === impl Inbound === diff --git a/linkerd/app/inbound/src/direct.rs b/linkerd/app/inbound/src/direct.rs index 77f0768c79..c429ec5e8a 100644 --- a/linkerd/app/inbound/src/direct.rs +++ b/linkerd/app/inbound/src/direct.rs @@ -2,6 +2,7 @@ use crate::{policy, Inbound}; use linkerd_app_core::{ io, proxy::identity::LocalCrtKey, + rustls, svc::{self, ExtractParam, InsertParam, Param}, tls, transport::{self, metrics::SensorIo, ClientAddr, OrigDstAddr, Remote, ServerAddr}, @@ -52,7 +53,7 @@ pub struct ClientInfo { pub local_addr: OrigDstAddr, } -type TlsIo = tls::server::Io>>; +type TlsIo = tls::server::Io>>; type FwdIo = SensorIo>>; pub type GatewayIo = io::EitherIo, SensorIo>>; @@ -304,9 +305,9 @@ impl svc::Service for WithTransportHeaderAlpn where I: io::AsyncRead + io::AsyncWrite + Send + Unpin, { - type Response = (tls::ServerTls, tls::rustls::ServerIo); + type Response = (tls::ServerTls, rustls::ServerIo); type Error = io::Error; - type Future = tls::rustls::TerminateFuture; + type Future = rustls::TerminateFuture; #[inline] fn poll_ready(&mut self, _: &mut task::Context<'_>) -> task::Poll> { @@ -324,7 +325,7 @@ where config .alpn_protocols .push(transport_header::PROTOCOL.into()); - tls::rustls::terminate(config.into(), io) + rustls::terminate(config.into(), io) } } diff --git a/linkerd/identity/Cargo.toml b/linkerd/identity/Cargo.toml index b3e92e5bf3..258e49248e 100644 --- a/linkerd/identity/Cargo.toml +++ b/linkerd/identity/Cargo.toml @@ -13,6 +13,7 @@ test-util = [] linkerd-dns-name = { path = "../dns/name" } ring = "0.16.19" thiserror = "1.0" +pin-project = "1" tokio-rustls = "0.22" tracing = "0.1.29" untrusted = "0.7" diff --git a/linkerd/proxy/identity/Cargo.toml b/linkerd/proxy/identity/Cargo.toml index 9782c68505..6711158910 100644 --- a/linkerd/proxy/identity/Cargo.toml +++ b/linkerd/proxy/identity/Cargo.toml @@ -18,9 +18,9 @@ linkerd-identity = { path = "../../identity" } linkerd-metrics = { path = "../../metrics" } linkerd-stack = { path = "../../stack" } linkerd-tls = { path = "../../tls" } +linkerd-tls-rustls = { path = "../../tls/rustls" } thiserror = "1" tokio = { version = "1", features = ["time", "sync"] } -tokio-rustls = "0.22" tonic = { version = "0.5", default-features = false } tracing = "0.1.29" http-body = "0.4" diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index e018746ffc..940a79e5f1 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -5,7 +5,7 @@ use linkerd_identity as id; use linkerd_metrics::Counter; use linkerd_stack::{NewService, Param, Service}; use linkerd_tls as tls; -use pin_project::pin_project; +use linkerd_tls_rustls as rustls; use std::{ convert::TryFrom, sync::Arc, @@ -36,7 +36,6 @@ pub struct Config { /// Holds the process's local TLS identity state. /// /// Updates dynamically as certificates are provisioned from the Identity service. -#[pin_project] #[derive(Clone, Debug)] pub struct LocalCrtKey { trust_anchors: id::TrustAnchors, @@ -234,7 +233,7 @@ impl LocalCrtKey { &self.id.0 } - pub fn client_config(&self) -> Arc { + fn client_config(&self) -> Arc { if let Some(ref c) = *self.crt_key.borrow() { return c.client_config(); } @@ -242,22 +241,22 @@ impl LocalCrtKey { self.trust_anchors.client_config() } - pub fn server_config(&self) -> Arc { + pub fn server_config(&self) -> Arc { if let Some(ref c) = *self.crt_key.borrow() { return c.server_config(); } - let verifier = tls::rustls::NoClientAuth::new(); - Arc::new(tls::rustls::ServerConfig::new(verifier)) + let verifier = rustls::NoClientAuth::new(); + Arc::new(rustls::ServerConfig::new(verifier)) } } impl NewService for LocalCrtKey { - type Service = tls::rustls::Connect; + type Service = rustls::Connect; fn new_service(&self, target: tls::ClientTls) -> Self::Service { // TODO: ALPN - tls::rustls::Connect::new(target, self.client_config()) + rustls::Connect::new(target, self.client_config()) } } @@ -265,9 +264,9 @@ impl Service for LocalCrtKey where I: io::AsyncRead + io::AsyncWrite + Send + Unpin, { - type Response = (tls::ServerTls, tls::rustls::ServerIo); + type Response = (tls::ServerTls, rustls::ServerIo); type Error = io::Error; - type Future = tls::rustls::TerminateFuture; + type Future = rustls::TerminateFuture; #[inline] fn poll_ready(&mut self, _: &mut task::Context<'_>) -> task::Poll> { @@ -276,7 +275,7 @@ where #[inline] fn call(&mut self, io: I) -> Self::Future { - tls::rustls::terminate(self.server_config(), io) + rustls::terminate(self.server_config(), io) } } diff --git a/linkerd/proxy/tap/Cargo.toml b/linkerd/proxy/tap/Cargo.toml index 646b324723..befab9eb11 100644 --- a/linkerd/proxy/tap/Cargo.toml +++ b/linkerd/proxy/tap/Cargo.toml @@ -23,6 +23,7 @@ linkerd-proxy-http = { path = "../http" } linkerd-proxy-transport = { path = "../transport" } linkerd-stack = { path = "../../stack" } linkerd-tls = { path = "../../tls" } +linkerd-tls-rustls = { path = "../../tls/rustls" } parking_lot = "0.11" rand = { version = "0.8" } thiserror = "1.0" diff --git a/linkerd/proxy/tap/src/accept.rs b/linkerd/proxy/tap/src/accept.rs index 862a032609..d8fcb64374 100644 --- a/linkerd/proxy/tap/src/accept.rs +++ b/linkerd/proxy/tap/src/accept.rs @@ -23,7 +23,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>>; diff --git a/linkerd/tls/Cargo.toml b/linkerd/tls/Cargo.toml index 050931f520..77c2f0f4f2 100644 --- a/linkerd/tls/Cargo.toml +++ b/linkerd/tls/Cargo.toml @@ -19,13 +19,13 @@ linkerd-stack = { path = "../stack" } pin-project = "1" thiserror = "1.0" tokio = { version = "1", features = ["macros", "time"] } -tokio-rustls = "0.22" tower = "0.4.8" tracing = "0.1.29" webpki = "0.21" untrusted = "0.7" [dev-dependencies] +linkerd-tls-rustls = { path = "rustls" } linkerd-identity = { path = "../identity", features = ["test-util"] } linkerd-proxy-transport = { path = "../proxy/transport" } linkerd-tracing = { path = "../tracing", features = ["ansi"] } diff --git a/linkerd/tls/rustls/Cargo.toml b/linkerd/tls/rustls/Cargo.toml new file mode 100644 index 0000000000..1b1bd1a974 --- /dev/null +++ b/linkerd/tls/rustls/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "linkerd-tls-rustls" +version = "0.1.0" +authors = ["Linkerd Developers "] +license = "Apache-2.0" +edition = "2018" +publish = false + +[dependencies] +futures = { version = "0.3", default-features = false } +linkerd-dns-name = { path = "../../dns/name" } +linkerd-identity = { path = "../../identity" } +linkerd-io = { path = "../../io" } +linkerd-stack = { path = "../../stack" } +linkerd-tls = { path = ".." } +pin-project = "1" +tokio-rustls = "0.22" +tracing = "0.1" +webpki = "0.21" diff --git a/linkerd/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs new file mode 100644 index 0000000000..32ae03cb3b --- /dev/null +++ b/linkerd/tls/rustls/src/lib.rs @@ -0,0 +1,247 @@ +#![deny(warnings, rust_2018_idioms)] +#![forbid(unsafe_code)] + +use futures::prelude::*; +use linkerd_io as io; +use linkerd_stack::Service; +use linkerd_tls::{ + ClientId, ClientTls, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, + ServerTls, +}; +use std::{pin::Pin, sync::Arc}; +pub use tokio_rustls::rustls::*; +use tracing::debug; + +#[derive(Clone)] +pub struct Connect { + client_tls: ClientTls, + config: Arc, +} + +#[derive(Clone)] +pub struct Terminate { + config: Arc, +} + +pub type ConnectFuture = futures::future::MapOk< + tokio_rustls::Connect, + fn(tokio_rustls::client::TlsStream) -> ClientIo, +>; + +pub type TerminateFuture = futures::future::MapOk< + tokio_rustls::Accept, + fn(tokio_rustls::server::TlsStream) -> (ServerTls, ServerIo), +>; + +#[derive(Debug)] +pub struct ClientIo(tokio_rustls::client::TlsStream); + +#[derive(Debug)] +pub struct ServerIo(tokio_rustls::server::TlsStream); + +// === impl Connect === + +impl Connect { + pub fn new(client_tls: ClientTls, config: Arc) -> Self { + Self { client_tls, 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.client_tls.server_id.as_webpki(), io) + .map_ok(ClientIo) + } +} + +// === impl Terminate === + +pub fn terminate(config: Arc, io: I) -> TerminateFuture +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + tokio_rustls::TlsAcceptor::from(config) + .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) => Some(ClientId(linkerd_identity::Name::from( + linkerd_dns_name::Name::from(n.to_owned()), + ))), + webpki::GeneralDNSNameRef::Wildcard(_) => { + // Wildcards can perhaps be handled in a future path... + None + } + } +} + +// === 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.peer_addr() + } +} + +// === 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.peer_addr() + } +} diff --git a/linkerd/tls/src/lib.rs b/linkerd/tls/src/lib.rs index 2205a446a0..baa1c8b805 100755 --- a/linkerd/tls/src/lib.rs +++ b/linkerd/tls/src/lib.rs @@ -2,10 +2,8 @@ #![forbid(unsafe_code)] pub mod client; -pub mod rustls; pub mod server; -pub use self::rustls::Session; pub use linkerd_identity::LocalId; use linkerd_io as io; diff --git a/linkerd/tls/src/rustls.rs b/linkerd/tls/src/rustls.rs deleted file mode 100644 index 065bd5ae70..0000000000 --- a/linkerd/tls/src/rustls.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::{ - ClientId, ClientTls, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, - ServerTls, -}; -use futures::prelude::*; -use linkerd_io as io; -use linkerd_stack::Service; -use std::sync::Arc; -pub use tokio_rustls::{client::TlsStream as ClientIo, rustls::*, server::TlsStream as ServerIo}; -use tracing::debug; - -#[derive(Clone)] -pub struct Connect { - client_tls: ClientTls, - config: Arc, -} - -#[derive(Clone)] -pub struct Terminate { - config: Arc, -} - -pub type ConnectFuture = tokio_rustls::Connect; - -pub type TerminateFuture = - futures::future::MapOk, fn(ServerIo) -> (ServerTls, ServerIo)>; - -// === impl Connect === - -impl Connect { - pub fn new(client_tls: ClientTls, config: Arc) -> Self { - Self { client_tls, 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.client_tls.server_id.as_webpki(), io) - } -} - -// === impl Terminate === - -pub fn terminate(config: Arc, io: I) -> TerminateFuture -where - I: io::AsyncRead + io::AsyncWrite + Send + Unpin, -{ - tokio_rustls::TlsAcceptor::from(config) - .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, io) - }) -} - -fn client_identity(tls: &ServerIo) -> 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) => Some(ClientId(linkerd_identity::Name::from( - linkerd_dns_name::Name::from(n.to_owned()), - ))), - webpki::GeneralDNSNameRef::Wildcard(_) => { - // Wildcards can perhaps be handled in a future path... - None - } - } -} - -impl HasNegotiatedProtocol for ClientIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) - } -} - -impl HasNegotiatedProtocol for ServerIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) - } -} diff --git a/linkerd/tls/tests/tls_accept.rs b/linkerd/tls/tests/tls_accept.rs index 103ca00c72..35c442a9bd 100644 --- a/linkerd/tls/tests/tls_accept.rs +++ b/linkerd/tls/tests/tls_accept.rs @@ -17,6 +17,7 @@ use linkerd_proxy_transport::{ }; use linkerd_stack::{ExtractParam, InsertParam, NewService, Param, Service}; use linkerd_tls as tls; +use linkerd_tls_rustls as rustls; use std::{future::Future, net::SocketAddr, sync::mpsc, task, time::Duration}; use tokio::net::TcpStream; use tower::{ @@ -27,7 +28,7 @@ use tracing::instrument::Instrument; type ServerConn = ( (tls::ConditionalServerTls, T), - io::EitherIo>, tls::server::DetectIo>, + io::EitherIo>, tls::server::DetectIo>, ); #[tokio::test(flavor = "current_thread")] @@ -132,8 +133,7 @@ struct ServerParams { identity: id::CrtKey, } -type ClientIo = - io::EitherIo, tls::rustls::ClientIo>>; +type ClientIo = io::EitherIo, rustls::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 @@ -334,10 +334,10 @@ impl Param for Target { // === impl Tls === impl NewService for Tls { - type Service = tls::rustls::Connect; + type Service = rustls::Connect; fn new_service(&self, target: tls::ClientTls) -> Self::Service { - tls::rustls::Connect::new(target, self.0.client_config()) + rustls::Connect::new(target, self.0.client_config()) } } @@ -345,9 +345,9 @@ impl Service for Tls where I: io::AsyncRead + io::AsyncWrite + Send + Unpin, { - type Response = (tls::ServerTls, tls::rustls::ServerIo); + type Response = (tls::ServerTls, rustls::ServerIo); type Error = io::Error; - type Future = tls::rustls::TerminateFuture; + type Future = rustls::TerminateFuture; #[inline] fn poll_ready(&mut self, _: &mut task::Context<'_>) -> task::Poll> { @@ -356,7 +356,7 @@ where #[inline] fn call(&mut self, io: I) -> Self::Future { - tls::rustls::terminate(self.0.server_config(), io) + rustls::terminate(self.0.server_config(), io) } } From 3ae896edd2d3763a593e0af45342425b13b505d9 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Tue, 12 Oct 2021 15:24:59 +0000 Subject: [PATCH 06/31] fix client ALPN --- linkerd/proxy/identity/src/certify.rs | 3 ++- linkerd/tls/rustls/src/lib.rs | 29 ++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index 940a79e5f1..a6ad2b2f2a 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -254,8 +254,8 @@ impl LocalCrtKey { impl NewService for LocalCrtKey { type Service = rustls::Connect; + /// Creates a new TLS client service. fn new_service(&self, target: tls::ClientTls) -> Self::Service { - // TODO: ALPN rustls::Connect::new(target, self.client_config()) } } @@ -273,6 +273,7 @@ where task::Poll::Ready(Ok(())) } + /// Terminates a server-side TLS connection. #[inline] fn call(&mut self, io: I) -> Self::Future { rustls::terminate(self.server_config(), io) diff --git a/linkerd/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs index 32ae03cb3b..544a489873 100644 --- a/linkerd/tls/rustls/src/lib.rs +++ b/linkerd/tls/rustls/src/lib.rs @@ -5,8 +5,8 @@ use futures::prelude::*; use linkerd_io as io; use linkerd_stack::Service; use linkerd_tls::{ - ClientId, ClientTls, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, - ServerTls, + client::AlpnProtocols, ClientId, ClientTls, HasNegotiatedProtocol, NegotiatedProtocol, + NegotiatedProtocolRef, ServerId, ServerTls, }; use std::{pin::Pin, sync::Arc}; pub use tokio_rustls::rustls::*; @@ -14,7 +14,7 @@ use tracing::debug; #[derive(Clone)] pub struct Connect { - client_tls: ClientTls, + server_id: ServerId, config: Arc, } @@ -43,7 +43,26 @@ pub struct ServerIo(tokio_rustls::server::TlsStream); impl Connect { pub fn new(client_tls: ClientTls, config: Arc) -> Self { - Self { client_tls, config } + // 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. + let config = match client_tls.alpn { + None => config, + Some(AlpnProtocols(protocols)) => { + let mut c: ClientConfig = config.as_ref().clone(); + c.alpn_protocols = protocols; + Arc::new(c) + } + }; + + Self { + server_id: client_tls.server_id, + config, + } } } @@ -64,7 +83,7 @@ where fn call(&mut self, io: I) -> Self::Future { tokio_rustls::TlsConnector::from(self.config.clone()) - .connect(self.client_tls.server_id.as_webpki(), io) + .connect(self.server_id.as_webpki(), io) .map_ok(ClientIo) } } From 7926c08ade63289a4e1af19c3b0c51c8ad51671c Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 16 Oct 2021 19:52:37 +0000 Subject: [PATCH 07/31] -needless checks --- linkerd/app/core/src/control.rs | 2 -- linkerd/app/inbound/src/detect.rs | 5 ----- linkerd/app/inbound/src/direct.rs | 5 ----- 3 files changed, 12 deletions(-) diff --git a/linkerd/app/core/src/control.rs b/linkerd/app/core/src/control.rs index caa678ccfb..7f6f31a3bf 100644 --- a/linkerd/app/core/src/control.rs +++ b/linkerd/app/core/src/control.rs @@ -85,9 +85,7 @@ impl Config { }; svc::stack(ConnectTcp::new(self.connect.keepalive)) - .check_service::() .push(tls::Client::layer(identity)) - .check_service::() .push_connect_timeout(self.connect.timeout) .push(self::client::layer()) .push_on_service(svc::MapErr::layer(Into::into)) diff --git a/linkerd/app/inbound/src/detect.rs b/linkerd/app/inbound/src/detect.rs index dee3dfafdd..734217c6e8 100644 --- a/linkerd/app/inbound/src/detect.rs +++ b/linkerd/app/inbound/src/detect.rs @@ -109,7 +109,6 @@ impl Inbound { let detect_timeout = cfg.proxy.detect_protocol_timeout; detect - .check_new_service::>() .push_switch( // Ensure that the connection is authorized before proceeding with protocol // detection. @@ -136,15 +135,12 @@ impl Inbound { forward .clone() .push_on_service(svc::MapTargetLayer::new(io::BoxedIo::new)) - .check_new_service::>() .into_inner(), ) - .check_new_service::<(_, T), TlsIo>() .push(tls::NewDetectTls::::layer(TlsParams { timeout: tls::server::Timeout(detect_timeout), identity: rt.identity.clone(), })) - .check_new_service::() .push_switch( // If this port's policy indicates that authentication is not required and // detection should be skipped, use the TCP stack directly. @@ -166,7 +162,6 @@ impl Inbound { .push_on_service(svc::MapTargetLayer::new(io::BoxedIo::new)) .into_inner(), ) - .check_new_service::() .push_on_service(svc::BoxService::layer()) .push(svc::ArcNewService::layer()) }) diff --git a/linkerd/app/inbound/src/direct.rs b/linkerd/app/inbound/src/direct.rs index c429ec5e8a..ac8e76f7c2 100644 --- a/linkerd/app/inbound/src/direct.rs +++ b/linkerd/app/inbound/src/direct.rs @@ -104,7 +104,6 @@ impl Inbound { rt.metrics.proxy.transport.clone(), )) .instrument(|_: &_| debug_span!("opaque")) - .check_new_service::() // When the transport header is present, it may be used for either local TCP // forwarding, or we may be processing an HTTP gateway connection. HTTP gateway // connections that have a transport header must provide a target name as a part of @@ -174,7 +173,6 @@ impl Inbound { .instrument( |g: &GatewayTransportHeader| info_span!("gateway", dst = %g.target), ) - .check_new_service::>>() .into_inner(), ) // Use ALPN to determine whether a transport header should be read. @@ -186,19 +184,16 @@ impl Inbound { Err(RefusedNoTarget.into()) } }) - .check_new_service::>() // Build a ClientInfo target for each accepted connection. Refuse the // connection if it doesn't include an mTLS identity. .push_request_filter(ClientInfo::try_from) .push(svc::ArcNewService::layer()) - .check_new_service::<(_, T), TlsIo>() .push(tls::NewDetectTls::::layer( TlsParams { timeout: tls::server::Timeout(detect_timeout), identity: WithTransportHeaderAlpn(rt.identity.clone()), }, )) - .check_new_service::() .push_on_service(svc::BoxService::layer()) .push(svc::ArcNewService::layer()) }) From ae94c6ad411c126ec56248a4e988e8a2d33c68f0 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 16 Oct 2021 20:04:55 +0000 Subject: [PATCH 08/31] Drop rustls dependency from io --- Cargo.lock | 1 - linkerd/io/Cargo.toml | 1 - linkerd/io/src/lib.rs | 12 ------------ linkerd/tls/rustls/src/lib.rs | 4 ++-- 4 files changed, 2 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 618b071cce..34f643e68d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1012,7 +1012,6 @@ dependencies = [ "linkerd-errno", "pin-project", "tokio", - "tokio-rustls", "tokio-test", "tokio-util", ] diff --git a/linkerd/io/Cargo.toml b/linkerd/io/Cargo.toml index 3005a8e8a4..4e9ab36fa1 100644 --- a/linkerd/io/Cargo.toml +++ b/linkerd/io/Cargo.toml @@ -18,7 +18,6 @@ futures = { version = "0.3", default-features = false } bytes = "1" linkerd-errno = { path = "../errno" } tokio = { version = "1", features = ["io-util", "net"] } -tokio-rustls = "0.22" tokio-test = { version = "0.4", optional = true } tokio-util = { version = "0.6", features = ["io"] } pin-project = "1" diff --git a/linkerd/io/src/lib.rs b/linkerd/io/src/lib.rs index 8993c81217..6653e63c3a 100644 --- a/linkerd/io/src/lib.rs +++ b/linkerd/io/src/lib.rs @@ -64,18 +64,6 @@ impl PeerAddr for tokio::net::TcpStream { } } -impl PeerAddr for tokio_rustls::client::TlsStream { - fn peer_addr(&self) -> Result { - self.get_ref().0.peer_addr() - } -} - -impl PeerAddr for tokio_rustls::server::TlsStream { - fn peer_addr(&self) -> Result { - self.get_ref().0.peer_addr() - } -} - #[cfg(feature = "tokio-test")] impl PeerAddr for tokio_test::io::Mock { fn peer_addr(&self) -> Result { diff --git a/linkerd/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs index 544a489873..7398394b57 100644 --- a/linkerd/tls/rustls/src/lib.rs +++ b/linkerd/tls/rustls/src/lib.rs @@ -195,7 +195,7 @@ impl HasNegotiatedProtocol for ClientIo { impl io::PeerAddr for ClientIo { #[inline] fn peer_addr(&self) -> io::Result { - self.0.peer_addr() + self.0.get_ref().0.peer_addr() } } @@ -261,6 +261,6 @@ impl HasNegotiatedProtocol for ServerIo { impl io::PeerAddr for ServerIo { #[inline] fn peer_addr(&self) -> io::Result { - self.0.peer_addr() + self.0.get_ref().0.peer_addr() } } From 54d40d79b9dbc41422ce53f907a05f414f00dd0e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 16 Oct 2021 20:39:34 +0000 Subject: [PATCH 09/31] Implement deref for ID types --- .../outbound/src/http/require_id_header.rs | 34 +++++++++---------- linkerd/dns/name/src/name.rs | 6 ---- linkerd/identity/src/lib.rs | 14 ++++---- linkerd/tls/src/client.rs | 8 +++++ 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/linkerd/app/outbound/src/http/require_id_header.rs b/linkerd/app/outbound/src/http/require_id_header.rs index b317b1ae60..2495d11f27 100644 --- a/linkerd/app/outbound/src/http/require_id_header.rs +++ b/linkerd/app/outbound/src/http/require_id_header.rs @@ -85,24 +85,22 @@ where // In either case, we clear the header so it is not passed on outbound requests. if let Some(require_id) = Self::extract_id(&mut request) { match self.tls.as_ref() { - Conditional::Some(tls::ClientTls { - server_id: tls::ServerId(sni), - .. - }) => { - if require_id != *sni { - debug!( - required = %require_id, - found = %sni, - "Identity required by header not satisfied" - ); - let e = IdentityRequired { - required: require_id.into(), - found: Some(tls::ServerId(sni.clone())), - }; - return future::Either::Left(future::err(e.into())); - } else { - trace!(required = %require_id, "Identity required by header"); - } + Conditional::Some(tls::ClientTls { server_id, .. }) + if require_id == **server_id => + { + trace!(required = %require_id, "Identity required by header") + } + Conditional::Some(tls::ClientTls { server_id, .. }) => { + debug!( + required = %require_id, + found = %server_id, + "Identity required by header not satisfied" + ); + let e = IdentityRequired { + required: require_id.into(), + found: Some(server_id.clone()), + }; + return future::Either::Left(future::err(e.into())); } Conditional::None(_) => { debug!(required = %require_id, "Identity required by header not satisfied"); diff --git a/linkerd/dns/name/src/name.rs b/linkerd/dns/name/src/name.rs index 99c41b284e..9992b1b35d 100644 --- a/linkerd/dns/name/src/name.rs +++ b/linkerd/dns/name/src/name.rs @@ -71,12 +71,6 @@ impl From for webpki::DNSName { } } -impl<'t> From<&'t Name> for webpki::DNSNameRef<'t> { - fn from(Name(ref name): &'t Name) -> webpki::DNSNameRef<'t> { - name.as_ref() - } -} - impl AsRef for Name { #[inline] fn as_ref(&self) -> &str { diff --git a/linkerd/identity/src/lib.rs b/linkerd/identity/src/lib.rs index 7acc9895d2..fd9d9120c1 100644 --- a/linkerd/identity/src/lib.rs +++ b/linkerd/identity/src/lib.rs @@ -134,6 +134,14 @@ impl From for Name { } } +impl std::ops::Deref for Name { + type Target = linkerd_dns_name::Name; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl Name { #[inline] pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { @@ -165,12 +173,6 @@ impl TryFrom<&[u8]> for Name { } } -impl<'t> From<&'t Name> for webpki::DNSNameRef<'t> { - fn from(Name(ref name): &'t Name) -> webpki::DNSNameRef<'t> { - name.as_ref().into() - } -} - impl AsRef for Name { fn as_ref(&self) -> &str { (*self.0).as_ref() diff --git a/linkerd/tls/src/client.rs b/linkerd/tls/src/client.rs index 951548e05b..0fa7b2b1cb 100644 --- a/linkerd/tls/src/client.rs +++ b/linkerd/tls/src/client.rs @@ -7,6 +7,7 @@ use linkerd_stack::{layer, NewService, Oneshot, Param, Service, ServiceExt}; use std::{ fmt, future::Future, + ops::Deref, pin::Pin, str::FromStr, task::{Context, Poll}, @@ -165,6 +166,13 @@ impl From for id::Name { } } +impl Deref for ServerId { + type Target = id::Name; + fn deref(&self) -> &id::Name { + &self.0 + } +} + impl ServerId { pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { self.0.as_webpki() From 960ad0d167adab61c6bb5a09b780f4d7ad894ce1 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 16 Oct 2021 20:45:08 +0000 Subject: [PATCH 10/31] semicolon --- linkerd/app/outbound/src/http/require_id_header.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkerd/app/outbound/src/http/require_id_header.rs b/linkerd/app/outbound/src/http/require_id_header.rs index 2495d11f27..d3f1d963f2 100644 --- a/linkerd/app/outbound/src/http/require_id_header.rs +++ b/linkerd/app/outbound/src/http/require_id_header.rs @@ -88,7 +88,7 @@ where Conditional::Some(tls::ClientTls { server_id, .. }) if require_id == **server_id => { - trace!(required = %require_id, "Identity required by header") + trace!(required = %require_id, "Identity required by header"); } Conditional::Some(tls::ClientTls { server_id, .. }) => { debug!( From 3ef9c5cc1f09a54b9c6678c07938b8980c12581c Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 16 Oct 2021 21:29:59 +0000 Subject: [PATCH 11/31] move credential types to rustls crate --- Cargo.lock | 4 +- linkerd/app/admin/src/stack.rs | 3 +- linkerd/app/core/src/lib.rs | 4 +- linkerd/app/core/src/proxy/mod.rs | 1 - linkerd/app/inbound/src/detect.rs | 18 +- linkerd/app/inbound/src/direct.rs | 5 +- linkerd/app/inbound/src/lib.rs | 3 +- linkerd/app/inbound/src/policy/config.rs | 2 +- linkerd/app/outbound/src/lib.rs | 2 +- linkerd/app/src/dst.rs | 3 +- linkerd/app/src/identity.rs | 5 +- linkerd/app/src/tap.rs | 2 +- linkerd/identity/Cargo.toml | 7 - linkerd/identity/src/lib.rs | 370 +------------- linkerd/proxy/identity/Cargo.toml | 2 +- linkerd/proxy/identity/src/certify.rs | 20 +- linkerd/proxy/identity/src/lib.rs | 2 + linkerd/proxy/identity/src/metrics.rs | 2 +- linkerd/tls/Cargo.toml | 3 +- linkerd/tls/rustls/Cargo.toml | 6 + linkerd/tls/rustls/src/client.rs | 137 ++++++ linkerd/tls/rustls/src/lib.rs | 455 ++++++++++-------- linkerd/tls/rustls/src/server.rs | 132 +++++ .../{identity => tls/rustls}/src/test_util.rs | 3 +- .../rustls}/src/testdata/bar-ns1-ca1/crt.der | Bin .../rustls}/src/testdata/bar-ns1-ca1/csr.pem | 0 .../rustls}/src/testdata/bar-ns1-ca1/key.p8 | Bin .../rustls}/src/testdata/ca-config.json | 0 .../rustls}/src/testdata/ca1-key.pem | 0 .../rustls}/src/testdata/ca1.pem | 0 .../rustls}/src/testdata/ca2-key.pem | 0 .../rustls}/src/testdata/ca2.pem | 0 .../testdata/controller-linkerd-ca1/crt.der | Bin .../testdata/controller-linkerd-ca1/csr.pem | 0 .../testdata/controller-linkerd-ca1/key.p8 | Bin .../src/testdata/default-default-ca1/crt.der | Bin .../src/testdata/default-default-ca1/csr.pem | 0 .../src/testdata/default-default-ca1/key.p8 | Bin .../rustls}/src/testdata/foo-ns1-ca1/crt.der | Bin .../rustls}/src/testdata/foo-ns1-ca1/csr.pem | 0 .../rustls}/src/testdata/foo-ns1-ca1/key.p8 | Bin .../rustls}/src/testdata/foo-ns1-ca2/crt.der | Bin .../rustls}/src/testdata/foo-ns1-ca2/csr.pem | 0 .../rustls}/src/testdata/foo-ns1-ca2/key.p8 | Bin .../rustls}/src/testdata/gen-certs.sh | 0 linkerd/tls/rustls/src/tests.rs | 35 ++ linkerd/tls/tests/tls_accept.rs | 23 +- 47 files changed, 624 insertions(+), 625 deletions(-) create mode 100644 linkerd/tls/rustls/src/client.rs create mode 100644 linkerd/tls/rustls/src/server.rs rename linkerd/{identity => tls/rustls}/src/test_util.rs (94%) rename linkerd/{identity => tls/rustls}/src/testdata/bar-ns1-ca1/crt.der (100%) rename linkerd/{identity => tls/rustls}/src/testdata/bar-ns1-ca1/csr.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/bar-ns1-ca1/key.p8 (100%) rename linkerd/{identity => tls/rustls}/src/testdata/ca-config.json (100%) rename linkerd/{identity => tls/rustls}/src/testdata/ca1-key.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/ca1.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/ca2-key.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/ca2.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/controller-linkerd-ca1/crt.der (100%) rename linkerd/{identity => tls/rustls}/src/testdata/controller-linkerd-ca1/csr.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/controller-linkerd-ca1/key.p8 (100%) rename linkerd/{identity => tls/rustls}/src/testdata/default-default-ca1/crt.der (100%) rename linkerd/{identity => tls/rustls}/src/testdata/default-default-ca1/csr.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/default-default-ca1/key.p8 (100%) rename linkerd/{identity => tls/rustls}/src/testdata/foo-ns1-ca1/crt.der (100%) rename linkerd/{identity => tls/rustls}/src/testdata/foo-ns1-ca1/csr.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/foo-ns1-ca1/key.p8 (100%) rename linkerd/{identity => tls/rustls}/src/testdata/foo-ns1-ca2/crt.der (100%) rename linkerd/{identity => tls/rustls}/src/testdata/foo-ns1-ca2/csr.pem (100%) rename linkerd/{identity => tls/rustls}/src/testdata/foo-ns1-ca2/key.p8 (100%) rename linkerd/{identity => tls/rustls}/src/testdata/gen-certs.sh (100%) create mode 100644 linkerd/tls/rustls/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 34f643e68d..b3ff60cf09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -994,9 +994,7 @@ version = "0.1.0" dependencies = [ "linkerd-dns-name", "pin-project", - "ring", "thiserror", - "tokio-rustls", "tracing", "untrusted", "webpki", @@ -1390,6 +1388,8 @@ dependencies = [ "linkerd-stack", "linkerd-tls", "pin-project", + "ring", + "thiserror", "tokio-rustls", "tracing", "webpki", diff --git a/linkerd/app/admin/src/stack.rs b/linkerd/app/admin/src/stack.rs index d669ad38de..d97dc93cdb 100644 --- a/linkerd/app/admin/src/stack.rs +++ b/linkerd/app/admin/src/stack.rs @@ -2,8 +2,9 @@ use linkerd_app_core::{ classify, config::ServerConfig, detect, drain, errors, + identity::LocalCrtKey, metrics::{self, FmtMetrics}, - proxy::{http, identity::LocalCrtKey}, + proxy::http, serve, svc::{self, ExtractParam, InsertParam, Param}, tls, trace, diff --git a/linkerd/app/core/src/lib.rs b/linkerd/app/core/src/lib.rs index e004e730a2..0f8bdff984 100644 --- a/linkerd/app/core/src/lib.rs +++ b/linkerd/app/core/src/lib.rs @@ -20,9 +20,9 @@ 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 as identity; pub use linkerd_io as io; pub use linkerd_opencensus as opencensus; +pub use linkerd_proxy_identity as identity; pub use linkerd_service_profiles as profiles; pub use linkerd_stack_metrics as stack_metrics; pub use linkerd_stack_tracing as stack_tracing; @@ -57,7 +57,7 @@ const DEFAULT_PORT: u16 = 80; #[derive(Clone, Debug)] pub struct ProxyRuntime { - pub identity: proxy::identity::LocalCrtKey, + pub identity: identity::LocalCrtKey, pub metrics: metrics::Proxy, pub tap: proxy::tap::Registry, pub span_sink: http_tracing::OpenCensusSink, diff --git a/linkerd/app/core/src/proxy/mod.rs b/linkerd/app/core/src/proxy/mod.rs index ec37b9965d..2fda2f6f4b 100644 --- a/linkerd/app/core/src/proxy/mod.rs +++ b/linkerd/app/core/src/proxy/mod.rs @@ -5,7 +5,6 @@ pub use linkerd_proxy_core as core; pub use linkerd_proxy_discover as discover; pub use linkerd_proxy_dns_resolve as dns_resolve; pub use linkerd_proxy_http as http; -pub use linkerd_proxy_identity as identity; pub use linkerd_proxy_resolve as resolve; pub use linkerd_proxy_tap as tap; pub use linkerd_proxy_tcp as tcp; diff --git a/linkerd/app/inbound/src/detect.rs b/linkerd/app/inbound/src/detect.rs index 734217c6e8..3bb3a05d83 100644 --- a/linkerd/app/inbound/src/detect.rs +++ b/linkerd/app/inbound/src/detect.rs @@ -4,7 +4,7 @@ use crate::{ }; use linkerd_app_core::{ detect, identity, io, - proxy::{http, identity::LocalCrtKey}, + proxy::http, rustls, svc, tls, transport::{ self, @@ -50,7 +50,7 @@ struct ConfigureHttpDetect; #[derive(Clone)] struct TlsParams { timeout: tls::server::Timeout, - identity: LocalCrtKey, + identity: identity::LocalCrtKey, } type TlsIo = tls::server::Io>>; @@ -137,10 +137,12 @@ impl Inbound { .push_on_service(svc::MapTargetLayer::new(io::BoxedIo::new)) .into_inner(), ) - .push(tls::NewDetectTls::::layer(TlsParams { - timeout: tls::server::Timeout(detect_timeout), - identity: rt.identity.clone(), - })) + .push(tls::NewDetectTls::::layer( + TlsParams { + timeout: tls::server::Timeout(detect_timeout), + identity: rt.identity.clone(), + }, + )) .push_switch( // If this port's policy indicates that authentication is not required and // detection should be skipped, use the TCP stack directly. @@ -427,9 +429,9 @@ impl svc::ExtractParam for TlsParams { } } -impl svc::ExtractParam for TlsParams { +impl svc::ExtractParam for TlsParams { #[inline] - fn extract_param(&self, _: &T) -> LocalCrtKey { + fn extract_param(&self, _: &T) -> identity::LocalCrtKey { self.identity.clone() } } diff --git a/linkerd/app/inbound/src/direct.rs b/linkerd/app/inbound/src/direct.rs index ac8e76f7c2..dfe4250c12 100644 --- a/linkerd/app/inbound/src/direct.rs +++ b/linkerd/app/inbound/src/direct.rs @@ -1,8 +1,7 @@ use crate::{policy, Inbound}; use linkerd_app_core::{ - io, - proxy::identity::LocalCrtKey, - rustls, + identity::LocalCrtKey, + io, rustls, svc::{self, ExtractParam, InsertParam, Param}, tls, transport::{self, metrics::SensorIo, ClientAddr, OrigDstAddr, Remote, ServerAddr}, diff --git a/linkerd/app/inbound/src/lib.rs b/linkerd/app/inbound/src/lib.rs index b23ea3bb4b..bc51f7aa04 100644 --- a/linkerd/app/inbound/src/lib.rs +++ b/linkerd/app/inbound/src/lib.rs @@ -21,9 +21,10 @@ use linkerd_app_core::{ config::{ConnectConfig, ProxyConfig}, drain, http_tracing::OpenCensusSink, + identity::LocalCrtKey, io, + proxy::tap, proxy::tcp, - proxy::{identity::LocalCrtKey, tap}, svc, transport::{self, Remote, ServerAddr}, Error, NameMatch, ProxyRuntime, diff --git a/linkerd/app/inbound/src/policy/config.rs b/linkerd/app/inbound/src/policy/config.rs index a9168ac913..621b14aec1 100644 --- a/linkerd/app/inbound/src/policy/config.rs +++ b/linkerd/app/inbound/src/policy/config.rs @@ -1,5 +1,5 @@ use super::{discover::Discover, DefaultPolicy, ServerPolicy, Store}; -use linkerd_app_core::{control, dns, metrics, proxy::identity::LocalCrtKey, svc::NewService}; +use linkerd_app_core::{control, dns, identity::LocalCrtKey, metrics, svc::NewService}; use std::collections::{HashMap, HashSet}; /// Configures inbound policies. diff --git a/linkerd/app/outbound/src/lib.rs b/linkerd/app/outbound/src/lib.rs index efd053b9e9..6dc1a26534 100644 --- a/linkerd/app/outbound/src/lib.rs +++ b/linkerd/app/outbound/src/lib.rs @@ -23,11 +23,11 @@ use linkerd_app_core::{ config::ProxyConfig, drain, http_tracing::OpenCensusSink, + identity::LocalCrtKey, io, profiles, proxy::{ api_resolve::{ConcreteAddr, Metadata}, core::Resolve, - identity::LocalCrtKey, tap, }, serve, diff --git a/linkerd/app/src/dst.rs b/linkerd/app/src/dst.rs index 17aad2aec5..2f0b3693f6 100644 --- a/linkerd/app/src/dst.rs +++ b/linkerd/app/src/dst.rs @@ -1,9 +1,10 @@ use linkerd_app_core::{ control, dns, exp_backoff::{ExponentialBackoff, ExponentialBackoffStream}, + identity::LocalCrtKey, metrics, profiles::{self, DiscoveryRejected}, - proxy::{api_resolve as api, http, identity::LocalCrtKey, resolve::recover}, + proxy::{api_resolve as api, http, resolve::recover}, svc::{self, NewService}, Error, Recover, }; diff --git a/linkerd/app/src/identity.rs b/linkerd/app/src/identity.rs index 49c89fc211..83e397d601 100644 --- a/linkerd/app/src/identity.rs +++ b/linkerd/app/src/identity.rs @@ -1,7 +1,4 @@ -pub use linkerd_app_core::identity::{ - Crt, CrtKey, Csr, InvalidName, Key, Name, TokenSource, TrustAnchors, -}; -pub use linkerd_app_core::proxy::identity::{certify, metrics, LocalCrtKey}; +pub use linkerd_app_core::identity::*; use linkerd_app_core::{ control, dns, exp_backoff::{ExponentialBackoff, ExponentialBackoffStream}, diff --git a/linkerd/app/src/tap.rs b/linkerd/app/src/tap.rs index a173b83df7..fce6ff24cb 100644 --- a/linkerd/app/src/tap.rs +++ b/linkerd/app/src/tap.rs @@ -2,7 +2,7 @@ use futures::prelude::*; use linkerd_app_core::{ config::ServerConfig, drain, - proxy::identity::LocalCrtKey, + identity::LocalCrtKey, proxy::tap, serve, svc::{self, ExtractParam, InsertParam, Param}, diff --git a/linkerd/identity/Cargo.toml b/linkerd/identity/Cargo.toml index 258e49248e..b4a9bb74d3 100644 --- a/linkerd/identity/Cargo.toml +++ b/linkerd/identity/Cargo.toml @@ -5,17 +5,10 @@ authors = ["Linkerd Developers "] license = "Apache-2.0" edition = "2018" -[features] -default = [] -test-util = [] - [dependencies] linkerd-dns-name = { path = "../dns/name" } -ring = "0.16.19" thiserror = "1.0" pin-project = "1" -tokio-rustls = "0.22" tracing = "0.1.29" untrusted = "0.7" webpki = "=0.21.4" - diff --git a/linkerd/identity/src/lib.rs b/linkerd/identity/src/lib.rs index fd9d9120c1..9a1bb3ecca 100644 --- a/linkerd/identity/src/lib.rs +++ b/linkerd/identity/src/lib.rs @@ -1,131 +1,20 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -pub use ring::error::KeyRejected; -use ring::rand; -use ring::signature::EcdsaKeyPair; -use std::{convert::TryFrom, fmt, fs, io, str::FromStr, sync::Arc, time::SystemTime}; -use thiserror::Error; -use tokio_rustls::rustls; -use tracing::{debug, warn}; - -#[cfg(any(test, feature = "test-util"))] -pub mod test_util; - pub use linkerd_dns_name::InvalidName; - -/// A DER-encoded X.509 certificate signing request. -#[derive(Clone, Debug)] -pub struct Csr(Arc>); +use std::{convert::TryFrom, fmt, fs, io, str::FromStr, sync::Arc}; /// An endpoint's identity. #[derive(Clone, Eq, PartialEq, Hash)] pub struct Name(Arc); -#[derive(Clone, Debug)] -pub struct Key(Arc); - -struct SigningKey(Arc); -struct Signer(Arc); - -#[derive(Clone)] -pub struct TrustAnchors(Arc); - #[derive(Clone, Debug)] pub struct TokenSource(Arc); -#[derive(Clone, Debug)] -pub struct Crt { - id: LocalId, - expiry: SystemTime, - chain: Vec, -} - -#[derive(Clone)] -pub struct CrtKey { - id: LocalId, - expiry: SystemTime, - client_config: Arc, - server_config: Arc, -} - -struct CertResolver(rustls::sign::CertifiedKey); - -#[derive(Clone, Debug, Error)] -#[error(transparent)] -pub struct InvalidCrt(rustls::TLSError); - /// A newtype for local server identities. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct LocalId(pub Name); -// These must be kept in sync: -static SIGNATURE_ALG_RING_SIGNING: &ring::signature::EcdsaSigningAlgorithm = - &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING; -const SIGNATURE_ALG_RUSTLS_SCHEME: rustls::SignatureScheme = - rustls::SignatureScheme::ECDSA_NISTP256_SHA256; -const SIGNATURE_ALG_RUSTLS_ALGORITHM: rustls::internal::msgs::enums::SignatureAlgorithm = - rustls::internal::msgs::enums::SignatureAlgorithm::ECDSA; -const TLS_VERSIONS: &[rustls::ProtocolVersion] = &[rustls::ProtocolVersion::TLSv1_3]; - -// === impl Csr === - -impl Csr { - pub fn from_der(der: Vec) -> Option { - if der.is_empty() { - return None; - } - - Some(Csr(Arc::new(der))) - } - - pub fn to_vec(&self) -> Vec { - self.0.to_vec() - } -} - -// === impl Key === - -impl Key { - pub fn from_pkcs8(b: &[u8]) -> Result { - let k = EcdsaKeyPair::from_pkcs8(SIGNATURE_ALG_RING_SIGNING, b)?; - Ok(Key(Arc::new(k))) - } -} - -impl rustls::sign::SigningKey for SigningKey { - fn choose_scheme( - &self, - offered: &[rustls::SignatureScheme], - ) -> Option> { - if offered.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { - Some(Box::new(Signer(self.0.clone()))) - } else { - None - } - } - - fn algorithm(&self) -> rustls::internal::msgs::enums::SignatureAlgorithm { - SIGNATURE_ALG_RUSTLS_ALGORITHM - } -} - -impl rustls::sign::Signer for Signer { - 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 Name === impl From for Name { @@ -210,224 +99,6 @@ impl TokenSource { } } -// === impl TrustAnchors === - -impl TrustAnchors { - #[cfg(any(test, feature = "test-util"))] - fn empty() -> Self { - TrustAnchors(Arc::new(rustls::ClientConfig::new())) - } - - pub fn from_pem(s: &str) -> Option { - use std::io::Cursor; - - let mut roots = rustls::RootCertStore::empty(); - let (added, skipped) = roots.add_pem_file(&mut Cursor::new(s)).ok()?; - if skipped != 0 { - warn!("skipped {} trust anchors in trust anchors file", skipped); - } - if added == 0 { - return None; - } - - let mut c = rustls::ClientConfig::new(); - - // 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`. - c.root_store = roots; - - // Disable session resumption for the time-being until resumption is - // more tested. - c.enable_tickets = false; - - Some(TrustAnchors(Arc::new(c))) - } - - pub fn certify(&self, key: Key, crt: Crt) -> Result { - let mut client = self.0.as_ref().clone(); - - // Ensure 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. - // - // XXX: Rustls currently only provides access to a - // `ServerCertVerifier` through - // `rustls::ClientConfig::get_verifier()`. - // - // XXX: Once `rustls::ServerCertVerified` is exposed in Rustls's - // safe API, use it to pass proof to CertCertResolver::new.... - // - // TODO: Restrict accepted signatutre algorithms. - static NO_OCSP: &[u8] = &[]; - client - .get_verifier() - .verify_server_cert(&client.root_store, &crt.chain, crt.id.as_webpki(), NO_OCSP) - .map_err(InvalidCrt)?; - debug!("certified {}", crt.id); - - let k = SigningKey(key.0); - let key = rustls::sign::CertifiedKey::new(crt.chain, Arc::new(Box::new(k))); - let resolver = Arc::new(CertResolver(key)); - - // Enable client authentication. - client.client_auth_cert_resolver = resolver.clone(); - - // 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.0.root_store.clone()), - ); - server.versions = TLS_VERSIONS.to_vec(); - server.cert_resolver = resolver; - - Ok(CrtKey { - id: crt.id, - expiry: crt.expiry, - client_config: Arc::new(client), - server_config: Arc::new(server), - }) - } - - pub fn client_config(&self) -> Arc { - self.0.clone() - } -} - -impl fmt::Debug for TrustAnchors { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TrustAnchors").finish() - } -} - -// === Crt === - -impl Crt { - pub fn new( - id: LocalId, - leaf: Vec, - intermediates: Vec>, - expiry: SystemTime, - ) -> Self { - let mut chain = Vec::with_capacity(intermediates.len() + 1); - chain.push(rustls::Certificate(leaf)); - chain.extend(intermediates.into_iter().map(rustls::Certificate)); - - Self { id, chain, expiry } - } - - pub fn name(&self) -> &Name { - &self.id.0 - } -} - -impl From<&'_ Crt> for LocalId { - fn from(crt: &Crt) -> LocalId { - crt.id.clone() - } -} - -// === CrtKey === - -impl CrtKey { - pub fn name(&self) -> &Name { - &self.id.0 - } - - pub fn expiry(&self) -> SystemTime { - self.expiry - } - - pub fn id(&self) -> &LocalId { - &self.id - } - - pub fn client_config(&self) -> Arc { - self.client_config.clone() - } - - pub fn server_config(&self) -> Arc { - self.server_config.clone() - } -} - -impl fmt::Debug for CrtKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.debug_struct("CrtKey") - .field("id", &self.id) - .field("expiry", &self.expiry) - .finish() - } -} - -// === impl CertResolver === - -impl rustls::ResolvesClientCert for CertResolver { - fn resolve( - &self, - _acceptable_issuers: &[&[u8]], - sigschemes: &[rustls::SignatureScheme], - ) -> Option { - // The proxy's server-side doesn't send the list of acceptable issuers so - // don't bother looking at `_acceptable_issuers`. - self.resolve_(sigschemes) - } - - fn has_certs(&self) -> bool { - true - } -} - -impl CertResolver { - 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::ResolvesServerCert for CertResolver { - fn resolve(&self, hello: rustls::ClientHello<'_>) -> Option { - let server_name = if let Some(server_name) = hello.server_name() { - server_name - } else { - debug!("no SNI -> no certificate"); - return None; - }; - - // Verify that our certificate is valid for the given SNI name. - let c = (&self.0.cert) - .first() - .map(rustls::Certificate::as_ref) - .unwrap_or(&[]); // An empty input will fail to parse. - if let Err(err) = - webpki::EndEntityCert::from(c).and_then(|c| c.verify_is_valid_for_dns_name(server_name)) - { - debug!( - "our certificate is not valid for the SNI name -> no certificate: {:?}", - err - ); - return None; - } - - self.resolve_(hello.sigschemes()) - } -} - // === impl LocalId === impl From for LocalId { @@ -453,42 +124,3 @@ impl fmt::Display for LocalId { self.0.fmt(f) } } - -#[cfg(test)] -mod tests { - 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"); - } -} diff --git a/linkerd/proxy/identity/Cargo.toml b/linkerd/proxy/identity/Cargo.toml index 6711158910..92770b88bc 100644 --- a/linkerd/proxy/identity/Cargo.toml +++ b/linkerd/proxy/identity/Cargo.toml @@ -8,7 +8,7 @@ publish = false [features] rustfmt = ["linkerd2-proxy-api/rustfmt"] -test-util = ["linkerd-identity/test-util"] +test-util = ["linkerd-tls-rustls/test-util"] [dependencies] futures = { version = "0.3", default-features = false } diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index a6ad2b2f2a..2362830677 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -5,7 +5,7 @@ use linkerd_identity as id; use linkerd_metrics::Counter; use linkerd_stack::{NewService, Param, Service}; use linkerd_tls as tls; -use linkerd_tls_rustls as rustls; +use linkerd_tls_rustls::{self as rustls, Crt, CrtKey, Csr, Key, TrustAnchors}; use std::{ convert::TryFrom, sync::Arc, @@ -24,9 +24,9 @@ use tracing::{debug, error, trace}; /// Configures the Identity service and local identity. #[derive(Clone, Debug)] pub struct Config { - pub trust_anchors: id::TrustAnchors, - pub key: id::Key, - pub csr: id::Csr, + pub trust_anchors: TrustAnchors, + pub key: Key, + pub csr: Csr, pub token: id::TokenSource, pub local_id: id::LocalId, pub min_refresh: Duration, @@ -38,9 +38,9 @@ pub struct Config { /// Updates dynamically as certificates are provisioned from the Identity service. #[derive(Clone, Debug)] pub struct LocalCrtKey { - trust_anchors: id::TrustAnchors, + trust_anchors: TrustAnchors, id: id::LocalId, - crt_key: watch::Receiver>, + crt_key: watch::Receiver>, refreshes: Arc, } @@ -52,7 +52,7 @@ pub struct AwaitCrt(Option); #[error("identity initialization failed")] pub struct LostDaemon(()); -pub type CrtKeySender = watch::Sender>; +pub type CrtKeySender = watch::Sender>; #[derive(Debug)] pub struct Daemon { @@ -135,7 +135,7 @@ impl Daemon { ), Some(expiry) => { let key = config.key.clone(); - let crt = id::Crt::new( + let crt = Crt::new( config.local_id.clone(), leaf_certificate, intermediate_certificates, @@ -191,7 +191,7 @@ impl LocalCrtKey { } #[cfg(feature = "test-util")] - pub fn for_test(id: &id::test_util::Identity) -> Self { + pub fn for_test(id: &rustls::test_util::Identity) -> Self { let crt_key = id.validate().expect("Identity must be valid"); let (tx, rx) = watch::channel(Some(crt_key)); // Prevent the receiver stream from ending. @@ -208,7 +208,7 @@ impl LocalCrtKey { #[cfg(feature = "test-util")] pub fn default_for_test() -> Self { - Self::for_test(&id::test_util::DEFAULT_DEFAULT) + Self::for_test(&rustls::test_util::DEFAULT_DEFAULT) } pub async fn await_crt(mut self) -> Result { diff --git a/linkerd/proxy/identity/src/lib.rs b/linkerd/proxy/identity/src/lib.rs index 4cab0f2b13..dfbe0f1845 100644 --- a/linkerd/proxy/identity/src/lib.rs +++ b/linkerd/proxy/identity/src/lib.rs @@ -5,3 +5,5 @@ pub mod certify; pub mod metrics; pub use self::certify::{AwaitCrt, CrtKeySender, LocalCrtKey}; +pub use linkerd_identity::*; +pub use linkerd_tls_rustls::*; diff --git a/linkerd/proxy/identity/src/metrics.rs b/linkerd/proxy/identity/src/metrics.rs index 9e4f04aeca..d158da365c 100644 --- a/linkerd/proxy/identity/src/metrics.rs +++ b/linkerd/proxy/identity/src/metrics.rs @@ -1,5 +1,5 @@ -use linkerd_identity::CrtKey; use linkerd_metrics::{metrics, Counter, FmtMetrics, Gauge}; +use linkerd_tls_rustls::CrtKey; use std::{fmt, sync::Arc, time::UNIX_EPOCH}; use tokio::sync::watch; diff --git a/linkerd/tls/Cargo.toml b/linkerd/tls/Cargo.toml index 77c2f0f4f2..979d490918 100644 --- a/linkerd/tls/Cargo.toml +++ b/linkerd/tls/Cargo.toml @@ -25,8 +25,7 @@ webpki = "0.21" untrusted = "0.7" [dev-dependencies] -linkerd-tls-rustls = { path = "rustls" } -linkerd-identity = { path = "../identity", features = ["test-util"] } +linkerd-tls-rustls = { path = "rustls", features = ["test-util"] } linkerd-proxy-transport = { path = "../proxy/transport" } linkerd-tracing = { path = "../tracing", features = ["ansi"] } tokio = { version = "1", features = ["rt-multi-thread"] } diff --git a/linkerd/tls/rustls/Cargo.toml b/linkerd/tls/rustls/Cargo.toml index 1b1bd1a974..7547e5af7c 100644 --- a/linkerd/tls/rustls/Cargo.toml +++ b/linkerd/tls/rustls/Cargo.toml @@ -6,6 +6,10 @@ license = "Apache-2.0" edition = "2018" publish = false +[features] +default = [] +test-util = [] + [dependencies] futures = { version = "0.3", default-features = false } linkerd-dns-name = { path = "../../dns/name" } @@ -14,6 +18,8 @@ linkerd-io = { path = "../../io" } linkerd-stack = { path = "../../stack" } linkerd-tls = { path = ".." } pin-project = "1" +ring = "0.16.19" +thiserror = "1" tokio-rustls = "0.22" tracing = "0.1" webpki = "0.21" diff --git a/linkerd/tls/rustls/src/client.rs b/linkerd/tls/rustls/src/client.rs new file mode 100644 index 0000000000..489e822345 --- /dev/null +++ b/linkerd/tls/rustls/src/client.rs @@ -0,0 +1,137 @@ +use futures::prelude::*; +use linkerd_io as io; +use linkerd_stack::Service; +use linkerd_tls::{ + client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerId, +}; +use std::{pin::Pin, sync::Arc}; +use tokio_rustls::rustls::{ClientConfig, Session}; + +#[derive(Clone)] +pub struct Connect { + server_id: ServerId, + 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 Connect === + +impl Connect { + pub 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. + let config = match client_tls.alpn { + None => config, + Some(AlpnProtocols(protocols)) => { + let mut c: ClientConfig = config.as_ref().clone(); + c.alpn_protocols = protocols; + Arc::new(c) + } + }; + + Self { + server_id: client_tls.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_webpki(), 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/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs index 7398394b57..f9810b76dd 100644 --- a/linkerd/tls/rustls/src/lib.rs +++ b/linkerd/tls/rustls/src/lib.rs @@ -1,266 +1,329 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -use futures::prelude::*; -use linkerd_io as io; -use linkerd_stack::Service; -use linkerd_tls::{ - client::AlpnProtocols, ClientId, ClientTls, HasNegotiatedProtocol, NegotiatedProtocol, - NegotiatedProtocolRef, ServerId, ServerTls, +mod client; +mod server; +#[cfg(feature = "test-util")] +pub mod test_util; + +pub use self::{ + client::{ClientIo, Connect, ConnectFuture}, + server::{terminate, ServerIo, TerminateFuture}, }; -use std::{pin::Pin, sync::Arc}; +use linkerd_identity as id; +pub use ring::error::KeyRejected; +use ring::{rand, signature::EcdsaKeyPair}; +use std::{sync::Arc, time::SystemTime}; +use thiserror::Error; pub use tokio_rustls::rustls::*; -use tracing::debug; +use tracing::{debug, warn}; + +#[derive(Clone, Debug)] +pub struct Key(Arc); + +struct SigningKey(Arc); +struct Signer(Arc); + +/// A DER-encoded X.509 certificate signing request. +#[derive(Clone, Debug)] +pub struct Csr(Arc>); #[derive(Clone)] -pub struct Connect { - server_id: ServerId, - config: Arc, +pub struct TrustAnchors(Arc); + +#[derive(Clone, Debug)] +pub struct Crt { + id: id::LocalId, + expiry: SystemTime, + chain: Vec, } #[derive(Clone)] -pub struct Terminate { - config: Arc, +pub struct CrtKey { + id: id::LocalId, + expiry: SystemTime, + client_config: Arc, + server_config: Arc, } -pub type ConnectFuture = futures::future::MapOk< - tokio_rustls::Connect, - fn(tokio_rustls::client::TlsStream) -> ClientIo, ->; +struct CertResolver(sign::CertifiedKey); -pub type TerminateFuture = futures::future::MapOk< - tokio_rustls::Accept, - fn(tokio_rustls::server::TlsStream) -> (ServerTls, ServerIo), ->; +#[derive(Clone, Debug, Error)] +#[error(transparent)] +pub struct InvalidCrt(TLSError); -#[derive(Debug)] -pub struct ClientIo(tokio_rustls::client::TlsStream); +// These must be kept in sync: +static SIGNATURE_ALG_RING_SIGNING: &ring::signature::EcdsaSigningAlgorithm = + &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING; +const SIGNATURE_ALG_RUSTLS_SCHEME: SignatureScheme = SignatureScheme::ECDSA_NISTP256_SHA256; +const SIGNATURE_ALG_RUSTLS_ALGORITHM: internal::msgs::enums::SignatureAlgorithm = + internal::msgs::enums::SignatureAlgorithm::ECDSA; +const TLS_VERSIONS: &[ProtocolVersion] = &[ProtocolVersion::TLSv1_3]; -#[derive(Debug)] -pub struct ServerIo(tokio_rustls::server::TlsStream); +// === impl Csr === -// === impl Connect === - -impl Connect { - pub 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. - let config = match client_tls.alpn { - None => config, - Some(AlpnProtocols(protocols)) => { - let mut c: ClientConfig = config.as_ref().clone(); - c.alpn_protocols = protocols; - Arc::new(c) - } - }; - - Self { - server_id: client_tls.server_id, - config, +impl Csr { + pub fn from_der(der: Vec) -> Option { + if der.is_empty() { + return None; } - } -} -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(())) + Some(Csr(Arc::new(der))) } - fn call(&mut self, io: I) -> Self::Future { - tokio_rustls::TlsConnector::from(self.config.clone()) - .connect(self.server_id.as_webpki(), io) - .map_ok(ClientIo) + pub fn to_vec(&self) -> Vec { + self.0.to_vec() } } -// === impl Terminate === - -pub fn terminate(config: Arc, io: I) -> TerminateFuture -where - I: io::AsyncRead + io::AsyncWrite + Send + Unpin, -{ - tokio_rustls::TlsAcceptor::from(config) - .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)) - }) +// === impl Key === + +impl Key { + pub fn from_pkcs8(b: &[u8]) -> Result { + let k = EcdsaKeyPair::from_pkcs8(SIGNATURE_ALG_RING_SIGNING, b)?; + Ok(Key(Arc::new(k))) + } } -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) => Some(ClientId(linkerd_identity::Name::from( - linkerd_dns_name::Name::from(n.to_owned()), - ))), - webpki::GeneralDNSNameRef::Wildcard(_) => { - // Wildcards can perhaps be handled in a future path... +impl sign::SigningKey for SigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + if offered.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { + Some(Box::new(Signer(self.0.clone()))) + } else { None } } + + fn algorithm(&self) -> internal::msgs::enums::SignatureAlgorithm { + SIGNATURE_ALG_RUSTLS_ALGORITHM + } } -// === impl ClientIo === +impl sign::Signer for Signer { + fn sign(&self, message: &[u8]) -> Result, TLSError> { + let rng = rand::SystemRandom::new(); + self.0 + .sign(&rng, message) + .map(|signature| signature.as_ref().to_owned()) + .map_err(|ring::error::Unspecified| TLSError::General("Signing Failed".to_owned())) + } -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) + fn get_scheme(&self) -> SignatureScheme { + SIGNATURE_ALG_RUSTLS_SCHEME } } -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) +// === impl TrustAnchors === + +impl TrustAnchors { + #[cfg(any(test, feature = "test-util"))] + fn empty() -> Self { + TrustAnchors(Arc::new(ClientConfig::new())) } - #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { - Pin::new(&mut self.0).poll_shutdown(cx) + pub fn from_pem(s: &str) -> Option { + use std::io::Cursor; + + let mut roots = RootCertStore::empty(); + let (added, skipped) = roots.add_pem_file(&mut Cursor::new(s)).ok()?; + if skipped != 0 { + warn!("skipped {} trust anchors in trust anchors file", skipped); + } + if added == 0 { + return None; + } + + let mut c = ClientConfig::new(); + + // 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`. + c.root_store = roots; + + // Disable session resumption for the time-being until resumption is + // more tested. + c.enable_tickets = false; + + Some(TrustAnchors(Arc::new(c))) } - #[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) + pub fn certify(&self, key: Key, crt: Crt) -> Result { + let mut client = self.0.as_ref().clone(); + + // Ensure 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. + // + // 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. + static NO_OCSP: &[u8] = &[]; + client + .get_verifier() + .verify_server_cert(&client.root_store, &crt.chain, crt.id.as_webpki(), NO_OCSP) + .map_err(InvalidCrt)?; + debug!("certified {}", crt.id); + + let k = SigningKey(key.0); + let key = sign::CertifiedKey::new(crt.chain, Arc::new(Box::new(k))); + let resolver = Arc::new(CertResolver(key)); + + // Enable client authentication. + client.client_auth_cert_resolver = resolver.clone(); + + // 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 = ServerConfig::new(AllowAnyAnonymousOrAuthenticatedClient::new( + self.0.root_store.clone(), + )); + server.versions = TLS_VERSIONS.to_vec(); + server.cert_resolver = resolver; + + Ok(CrtKey { + id: crt.id, + expiry: crt.expiry, + client_config: Arc::new(client), + server_config: Arc::new(server), + }) } - #[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) + pub fn client_config(&self) -> Arc { + self.0.clone() } +} - #[inline] - fn is_write_vectored(&self) -> bool { - self.0.is_write_vectored() +impl std::fmt::Debug for TrustAnchors { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TrustAnchors").finish() } } -impl HasNegotiatedProtocol for ClientIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.0 - .get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) +// === Crt === + +impl Crt { + pub fn new( + id: id::LocalId, + leaf: Vec, + intermediates: Vec>, + expiry: SystemTime, + ) -> Self { + let mut chain = Vec::with_capacity(intermediates.len() + 1); + chain.push(Certificate(leaf)); + chain.extend(intermediates.into_iter().map(Certificate)); + + Self { id, chain, expiry } + } + + pub fn name(&self) -> &id::Name { + &self.id.0 } } -impl io::PeerAddr for ClientIo { - #[inline] - fn peer_addr(&self) -> io::Result { - self.0.get_ref().0.peer_addr() +impl From<&'_ Crt> for id::LocalId { + fn from(crt: &Crt) -> id::LocalId { + crt.id.clone() } } -// === impl ServerIo === +// === CrtKey === -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 CrtKey { + pub fn name(&self) -> &id::Name { + &self.id.0 } -} -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) + pub fn expiry(&self) -> SystemTime { + self.expiry } - #[inline] - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> io::Poll<()> { - Pin::new(&mut self.0).poll_shutdown(cx) + pub fn id(&self) -> &id::LocalId { + &self.id } - #[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) + pub fn client_config(&self) -> Arc { + self.client_config.clone() } - #[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) + pub fn server_config(&self) -> Arc { + self.server_config.clone() } +} - #[inline] - fn is_write_vectored(&self) -> bool { - self.0.is_write_vectored() +impl std::fmt::Debug for CrtKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_struct("CrtKey") + .field("id", &self.id) + .field("expiry", &self.expiry) + .finish() } } -impl HasNegotiatedProtocol for ServerIo { - #[inline] - fn negotiated_protocol(&self) -> Option> { - self.0 - .get_ref() - .1 - .get_alpn_protocol() - .map(NegotiatedProtocolRef) +// === impl CertResolver === + +impl ResolvesClientCert for CertResolver { + fn resolve( + &self, + _acceptable_issuers: &[&[u8]], + sigschemes: &[SignatureScheme], + ) -> Option { + // The proxy's server-side doesn't send the list of acceptable issuers so + // don't bother looking at `_acceptable_issuers`. + self.resolve_(sigschemes) + } + + fn has_certs(&self) -> bool { + true + } +} + +impl CertResolver { + fn resolve_(&self, sigschemes: &[SignatureScheme]) -> Option { + if !sigschemes.contains(&SIGNATURE_ALG_RUSTLS_SCHEME) { + debug!("signature scheme not supported -> no certificate"); + return None; + } + Some(self.0.clone()) } } -impl io::PeerAddr for ServerIo { - #[inline] - fn peer_addr(&self) -> io::Result { - self.0.get_ref().0.peer_addr() +impl ResolvesServerCert for CertResolver { + fn resolve(&self, hello: ClientHello<'_>) -> Option { + let server_name = if let Some(server_name) = hello.server_name() { + server_name + } else { + debug!("no SNI -> no certificate"); + return None; + }; + + // Verify that our certificate is valid for the given SNI name. + let c = (&self.0.cert) + .first() + .map(Certificate::as_ref) + .unwrap_or(&[]); // An empty input will fail to parse. + if let Err(err) = + webpki::EndEntityCert::from(c).and_then(|c| c.verify_is_valid_for_dns_name(server_name)) + { + debug!( + "our certificate is not valid for the SNI name -> no certificate: {:?}", + err + ); + return None; + } + + self.resolve_(hello.sigschemes()) } } diff --git a/linkerd/tls/rustls/src/server.rs b/linkerd/tls/rustls/src/server.rs new file mode 100644 index 0000000000..5e4bf4b54f --- /dev/null +++ b/linkerd/tls/rustls/src/server.rs @@ -0,0 +1,132 @@ +use futures::prelude::*; +use linkerd_io as io; +use linkerd_tls::{ + ClientId, HasNegotiatedProtocol, NegotiatedProtocol, NegotiatedProtocolRef, ServerTls, +}; +use std::{pin::Pin, sync::Arc}; +use tokio_rustls::rustls::{Certificate, ServerConfig, Session}; +use tracing::debug; + +#[derive(Clone)] +pub struct Terminate { + config: Arc, +} + +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); + +// === impl Terminate === + +pub fn terminate(config: Arc, io: I) -> TerminateFuture +where + I: io::AsyncRead + io::AsyncWrite + Send + Unpin, +{ + tokio_rustls::TlsAcceptor::from(config) + .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) => Some(ClientId(linkerd_identity::Name::from( + linkerd_dns_name::Name::from(n.to_owned()), + ))), + 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/src/test_util.rs b/linkerd/tls/rustls/src/test_util.rs similarity index 94% rename from linkerd/identity/src/test_util.rs rename to linkerd/tls/rustls/src/test_util.rs index bd24f4f315..eb8e23c4fc 100644 --- a/linkerd/identity/src/test_util.rs +++ b/linkerd/tls/rustls/src/test_util.rs @@ -1,4 +1,5 @@ use super::*; +use linkerd_identity::{LocalId, Name}; use std::time::{Duration, SystemTime}; pub struct Identity { @@ -46,7 +47,7 @@ impl Identity { pub fn crt(&self) -> Crt { const HOUR: Duration = Duration::from_secs(60 * 60); - let n = Name::from_str(self.name).expect("name must be valid"); + let n = self.name.parse::().expect("name must be valid"); let der = self.crt.iter().copied().collect(); Crt::new(LocalId(n), der, vec![], SystemTime::now() + HOUR) } diff --git a/linkerd/identity/src/testdata/bar-ns1-ca1/crt.der b/linkerd/tls/rustls/src/testdata/bar-ns1-ca1/crt.der similarity index 100% rename from linkerd/identity/src/testdata/bar-ns1-ca1/crt.der rename to linkerd/tls/rustls/src/testdata/bar-ns1-ca1/crt.der diff --git a/linkerd/identity/src/testdata/bar-ns1-ca1/csr.pem b/linkerd/tls/rustls/src/testdata/bar-ns1-ca1/csr.pem similarity index 100% rename from linkerd/identity/src/testdata/bar-ns1-ca1/csr.pem rename to linkerd/tls/rustls/src/testdata/bar-ns1-ca1/csr.pem diff --git a/linkerd/identity/src/testdata/bar-ns1-ca1/key.p8 b/linkerd/tls/rustls/src/testdata/bar-ns1-ca1/key.p8 similarity index 100% rename from linkerd/identity/src/testdata/bar-ns1-ca1/key.p8 rename to linkerd/tls/rustls/src/testdata/bar-ns1-ca1/key.p8 diff --git a/linkerd/identity/src/testdata/ca-config.json b/linkerd/tls/rustls/src/testdata/ca-config.json similarity index 100% rename from linkerd/identity/src/testdata/ca-config.json rename to linkerd/tls/rustls/src/testdata/ca-config.json diff --git a/linkerd/identity/src/testdata/ca1-key.pem b/linkerd/tls/rustls/src/testdata/ca1-key.pem similarity index 100% rename from linkerd/identity/src/testdata/ca1-key.pem rename to linkerd/tls/rustls/src/testdata/ca1-key.pem diff --git a/linkerd/identity/src/testdata/ca1.pem b/linkerd/tls/rustls/src/testdata/ca1.pem similarity index 100% rename from linkerd/identity/src/testdata/ca1.pem rename to linkerd/tls/rustls/src/testdata/ca1.pem diff --git a/linkerd/identity/src/testdata/ca2-key.pem b/linkerd/tls/rustls/src/testdata/ca2-key.pem similarity index 100% rename from linkerd/identity/src/testdata/ca2-key.pem rename to linkerd/tls/rustls/src/testdata/ca2-key.pem diff --git a/linkerd/identity/src/testdata/ca2.pem b/linkerd/tls/rustls/src/testdata/ca2.pem similarity index 100% rename from linkerd/identity/src/testdata/ca2.pem rename to linkerd/tls/rustls/src/testdata/ca2.pem diff --git a/linkerd/identity/src/testdata/controller-linkerd-ca1/crt.der b/linkerd/tls/rustls/src/testdata/controller-linkerd-ca1/crt.der similarity index 100% rename from linkerd/identity/src/testdata/controller-linkerd-ca1/crt.der rename to linkerd/tls/rustls/src/testdata/controller-linkerd-ca1/crt.der diff --git a/linkerd/identity/src/testdata/controller-linkerd-ca1/csr.pem b/linkerd/tls/rustls/src/testdata/controller-linkerd-ca1/csr.pem similarity index 100% rename from linkerd/identity/src/testdata/controller-linkerd-ca1/csr.pem rename to linkerd/tls/rustls/src/testdata/controller-linkerd-ca1/csr.pem diff --git a/linkerd/identity/src/testdata/controller-linkerd-ca1/key.p8 b/linkerd/tls/rustls/src/testdata/controller-linkerd-ca1/key.p8 similarity index 100% rename from linkerd/identity/src/testdata/controller-linkerd-ca1/key.p8 rename to linkerd/tls/rustls/src/testdata/controller-linkerd-ca1/key.p8 diff --git a/linkerd/identity/src/testdata/default-default-ca1/crt.der b/linkerd/tls/rustls/src/testdata/default-default-ca1/crt.der similarity index 100% rename from linkerd/identity/src/testdata/default-default-ca1/crt.der rename to linkerd/tls/rustls/src/testdata/default-default-ca1/crt.der diff --git a/linkerd/identity/src/testdata/default-default-ca1/csr.pem b/linkerd/tls/rustls/src/testdata/default-default-ca1/csr.pem similarity index 100% rename from linkerd/identity/src/testdata/default-default-ca1/csr.pem rename to linkerd/tls/rustls/src/testdata/default-default-ca1/csr.pem diff --git a/linkerd/identity/src/testdata/default-default-ca1/key.p8 b/linkerd/tls/rustls/src/testdata/default-default-ca1/key.p8 similarity index 100% rename from linkerd/identity/src/testdata/default-default-ca1/key.p8 rename to linkerd/tls/rustls/src/testdata/default-default-ca1/key.p8 diff --git a/linkerd/identity/src/testdata/foo-ns1-ca1/crt.der b/linkerd/tls/rustls/src/testdata/foo-ns1-ca1/crt.der similarity index 100% rename from linkerd/identity/src/testdata/foo-ns1-ca1/crt.der rename to linkerd/tls/rustls/src/testdata/foo-ns1-ca1/crt.der diff --git a/linkerd/identity/src/testdata/foo-ns1-ca1/csr.pem b/linkerd/tls/rustls/src/testdata/foo-ns1-ca1/csr.pem similarity index 100% rename from linkerd/identity/src/testdata/foo-ns1-ca1/csr.pem rename to linkerd/tls/rustls/src/testdata/foo-ns1-ca1/csr.pem diff --git a/linkerd/identity/src/testdata/foo-ns1-ca1/key.p8 b/linkerd/tls/rustls/src/testdata/foo-ns1-ca1/key.p8 similarity index 100% rename from linkerd/identity/src/testdata/foo-ns1-ca1/key.p8 rename to linkerd/tls/rustls/src/testdata/foo-ns1-ca1/key.p8 diff --git a/linkerd/identity/src/testdata/foo-ns1-ca2/crt.der b/linkerd/tls/rustls/src/testdata/foo-ns1-ca2/crt.der similarity index 100% rename from linkerd/identity/src/testdata/foo-ns1-ca2/crt.der rename to linkerd/tls/rustls/src/testdata/foo-ns1-ca2/crt.der diff --git a/linkerd/identity/src/testdata/foo-ns1-ca2/csr.pem b/linkerd/tls/rustls/src/testdata/foo-ns1-ca2/csr.pem similarity index 100% rename from linkerd/identity/src/testdata/foo-ns1-ca2/csr.pem rename to linkerd/tls/rustls/src/testdata/foo-ns1-ca2/csr.pem diff --git a/linkerd/identity/src/testdata/foo-ns1-ca2/key.p8 b/linkerd/tls/rustls/src/testdata/foo-ns1-ca2/key.p8 similarity index 100% rename from linkerd/identity/src/testdata/foo-ns1-ca2/key.p8 rename to linkerd/tls/rustls/src/testdata/foo-ns1-ca2/key.p8 diff --git a/linkerd/identity/src/testdata/gen-certs.sh b/linkerd/tls/rustls/src/testdata/gen-certs.sh similarity index 100% rename from linkerd/identity/src/testdata/gen-certs.sh rename to linkerd/tls/rustls/src/testdata/gen-certs.sh diff --git a/linkerd/tls/rustls/src/tests.rs b/linkerd/tls/rustls/src/tests.rs new file mode 100644 index 0000000000..abf27117d0 --- /dev/null +++ b/linkerd/tls/rustls/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"); +} diff --git a/linkerd/tls/tests/tls_accept.rs b/linkerd/tls/tests/tls_accept.rs index 35c442a9bd..e93aa098ae 100644 --- a/linkerd/tls/tests/tls_accept.rs +++ b/linkerd/tls/tests/tls_accept.rs @@ -8,7 +8,6 @@ use futures::prelude::*; use linkerd_conditional::Conditional; use linkerd_error::Infallible; -use linkerd_identity as id; use linkerd_io::{self as io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use linkerd_proxy_transport::{ addrs::*, @@ -33,8 +32,8 @@ type ServerConn = ( #[tokio::test(flavor = "current_thread")] async fn plaintext() { - let server_tls = id::test_util::FOO_NS1.validate().unwrap(); - let client_tls = id::test_util::BAR_NS1.validate().unwrap(); + let server_tls = rustls::test_util::FOO_NS1.validate().unwrap(); + let client_tls = rustls::test_util::BAR_NS1.validate().unwrap(); let (client_result, server_result) = run_test( client_tls, Conditional::None(tls::NoClientTls::NotProvidedByServiceDiscovery), @@ -59,8 +58,8 @@ async fn plaintext() { #[tokio::test(flavor = "current_thread")] async fn proxy_to_proxy_tls_works() { - let server_tls = id::test_util::FOO_NS1.validate().unwrap(); - let client_tls = id::test_util::BAR_NS1.validate().unwrap(); + let server_tls = rustls::test_util::FOO_NS1.validate().unwrap(); + let client_tls = rustls::test_util::BAR_NS1.validate().unwrap(); let server_id = tls::ServerId(server_tls.name().clone()); let (client_result, server_result) = run_test( client_tls.clone(), @@ -90,14 +89,14 @@ async fn proxy_to_proxy_tls_works() { #[tokio::test(flavor = "current_thread")] async fn proxy_to_proxy_tls_pass_through_when_identity_does_not_match() { - let server_tls = id::test_util::FOO_NS1.validate().unwrap(); + let server_tls = rustls::test_util::FOO_NS1.validate().unwrap(); // Misuse the client's identity instead of the server's identity. Any // identity other than `server_tls.server_identity` would work. - let client_tls = id::test_util::BAR_NS1 + let client_tls = rustls::test_util::BAR_NS1 .validate() .expect("valid client cert"); - let sni = id::test_util::BAR_NS1.crt().name().clone(); + let sni = rustls::test_util::BAR_NS1.crt().name().clone(); let (client_result, server_result) = run_test( client_tls, @@ -130,7 +129,7 @@ struct Transported { #[derive(Clone)] struct ServerParams { - identity: id::CrtKey, + identity: rustls::CrtKey, } type ClientIo = io::EitherIo, rustls::ClientIo>>; @@ -139,10 +138,10 @@ type ClientIo = io::EitherIo, rustls::ClientIo( - client_tls: id::CrtKey, + client_tls: rustls::CrtKey, client_server_id: Conditional, client: C, - server_id: id::CrtKey, + server_id: rustls::CrtKey, server: S, ) -> ( Transported, @@ -315,7 +314,7 @@ struct Server; struct Target(SocketAddr, tls::ConditionalClientTls); #[derive(Clone)] -struct Tls(id::CrtKey); +struct Tls(rustls::CrtKey); // === impl Target === From 003bf425c56a49e141faab3af5cb0c29a837291e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 16 Oct 2021 22:09:31 +0000 Subject: [PATCH 12/31] Move identity::TokenSource to proxy::identity --- linkerd/identity/src/lib.rs | 24 +----------------------- linkerd/proxy/identity/src/certify.rs | 3 ++- linkerd/proxy/identity/src/lib.rs | 6 +++++- linkerd/proxy/identity/src/token.rs | 24 ++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 25 deletions(-) create mode 100644 linkerd/proxy/identity/src/token.rs diff --git a/linkerd/identity/src/lib.rs b/linkerd/identity/src/lib.rs index 9a1bb3ecca..96bda44f39 100644 --- a/linkerd/identity/src/lib.rs +++ b/linkerd/identity/src/lib.rs @@ -2,15 +2,12 @@ #![forbid(unsafe_code)] pub use linkerd_dns_name::InvalidName; -use std::{convert::TryFrom, fmt, fs, io, str::FromStr, sync::Arc}; +use std::{convert::TryFrom, fmt, str::FromStr, sync::Arc}; /// An endpoint's identity. #[derive(Clone, Eq, PartialEq, Hash)] pub struct Name(Arc); -#[derive(Clone, Debug)] -pub struct TokenSource(Arc); - /// A newtype for local server identities. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct LocalId(pub Name); @@ -80,25 +77,6 @@ impl fmt::Display for Name { } } -// === impl TokenSource === - -impl TokenSource { - pub fn if_nonempty_file(p: String) -> io::Result { - let ts = TokenSource(Arc::new(p)); - ts.load().map(|_| ts) - } - - pub fn load(&self) -> io::Result> { - let t = fs::read(self.0.as_str())?; - - if t.is_empty() { - return Err(io::Error::new(io::ErrorKind::Other, "token is empty")); - } - - Ok(t) - } -} - // === impl LocalId === impl From for LocalId { diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index 2362830677..9f15e5a570 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -1,3 +1,4 @@ +use crate::TokenSource; use http_body::Body; use linkerd2_proxy_api::identity::{self as api, identity_client::IdentityClient}; use linkerd_error::Error; @@ -27,7 +28,7 @@ pub struct Config { pub trust_anchors: TrustAnchors, pub key: Key, pub csr: Csr, - pub token: id::TokenSource, + pub token: TokenSource, pub local_id: id::LocalId, pub min_refresh: Duration, pub max_refresh: Duration, diff --git a/linkerd/proxy/identity/src/lib.rs b/linkerd/proxy/identity/src/lib.rs index dfbe0f1845..774a53d9a4 100644 --- a/linkerd/proxy/identity/src/lib.rs +++ b/linkerd/proxy/identity/src/lib.rs @@ -3,7 +3,11 @@ pub mod certify; pub mod metrics; +mod token; -pub use self::certify::{AwaitCrt, CrtKeySender, LocalCrtKey}; +pub use self::{ + certify::{AwaitCrt, CrtKeySender, LocalCrtKey}, + token::TokenSource, +}; pub use linkerd_identity::*; pub use linkerd_tls_rustls::*; diff --git a/linkerd/proxy/identity/src/token.rs b/linkerd/proxy/identity/src/token.rs new file mode 100644 index 0000000000..41842f902b --- /dev/null +++ b/linkerd/proxy/identity/src/token.rs @@ -0,0 +1,24 @@ +use std::{io, path::PathBuf}; + +#[derive(Clone, Debug)] +pub struct TokenSource(PathBuf); + +// === impl TokenSource === + +impl TokenSource { + pub fn if_nonempty_file(p: impl Into) -> io::Result { + let ts = TokenSource(p.into()); + ts.load()?; + Ok(ts) + } + + pub fn load(&self) -> io::Result> { + let t = std::fs::read(&self.0)?; + + if t.is_empty() { + return Err(io::Error::new(io::ErrorKind::Other, "token is empty")); + } + + Ok(t) + } +} From f53671a2fc9580d981195939a45f949a8f511e1b Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 16 Oct 2021 23:24:14 +0000 Subject: [PATCH 13/31] Remove webpki/ring from dns::Name Copy webpki's DNS name types into our dns-name crate so that webpki and ring aren't needed for basic name types. --- Cargo.lock | 2 - linkerd/app/gateway/src/gateway.rs | 2 +- .../inbound/src/http/set_identity_header.rs | 2 +- linkerd/app/inbound/src/policy/mod.rs | 4 +- linkerd/app/src/lib.rs | 2 +- linkerd/dns/name/Cargo.toml | 1 - linkerd/dns/name/src/lib.rs | 2 +- linkerd/dns/name/src/name.rs | 233 +++++++++++++++--- linkerd/dns/src/lib.rs | 10 +- linkerd/identity/Cargo.toml | 1 - linkerd/identity/src/lib.rs | 31 +-- linkerd/tls/rustls/src/client.rs | 17 +- linkerd/tls/rustls/src/lib.rs | 4 +- linkerd/tls/rustls/src/server.rs | 7 +- linkerd/tls/src/client.rs | 6 - linkerd/tls/src/server/mod.rs | 7 +- 16 files changed, 240 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3ff60cf09..e55c65945a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -880,7 +880,6 @@ version = "0.1.0" dependencies = [ "thiserror", "untrusted", - "webpki", ] [[package]] @@ -997,7 +996,6 @@ dependencies = [ "thiserror", "tracing", "untrusted", - "webpki", ] [[package]] diff --git a/linkerd/app/gateway/src/gateway.rs b/linkerd/app/gateway/src/gateway.rs index c68c618f21..7d2da58d0a 100644 --- a/linkerd/app/gateway/src/gateway.rs +++ b/linkerd/app/gateway/src/gateway.rs @@ -155,7 +155,7 @@ where { if let Some(by) = fwd_by(forwarded) { tracing::info!(%forwarded); - if by == local_id.as_ref() { + if by == local_id.as_str() { return Box::pin(future::err(GatewayLoop.into())); } } diff --git a/linkerd/app/inbound/src/http/set_identity_header.rs b/linkerd/app/inbound/src/http/set_identity_header.rs index fcd5ca50ff..59cb9e3fb3 100644 --- a/linkerd/app/inbound/src/http/set_identity_header.rs +++ b/linkerd/app/inbound/src/http/set_identity_header.rs @@ -43,7 +43,7 @@ where .and_then(|tls| match tls { tls::ServerTls::Established { client_id, .. } => { client_id.as_ref().and_then(|id| { - match http::HeaderValue::from_str(id.as_ref().as_ref()) { + match http::HeaderValue::from_str((*id).as_str()) { Ok(v) => Some(v), Err(error) => { tracing::warn!(%error, "identity not a valid header value"); diff --git a/linkerd/app/inbound/src/policy/mod.rs b/linkerd/app/inbound/src/policy/mod.rs index c88bfe9b26..315020a359 100644 --- a/linkerd/app/inbound/src/policy/mod.rs +++ b/linkerd/app/inbound/src/policy/mod.rs @@ -144,8 +144,8 @@ impl AllowPolicy { .. }) = tls { - if identities.contains(id.as_ref()) - || suffixes.iter().any(|s| s.contains(id.as_ref())) + if identities.contains(id.as_str()) + || suffixes.iter().any(|s| s.contains(id.as_str())) { return Ok(Permit::new(self.dst, &*server, authz)); } diff --git a/linkerd/app/src/lib.rs b/linkerd/app/src/lib.rs index 2cb84ebd11..db1d190d1a 100644 --- a/linkerd/app/src/lib.rs +++ b/linkerd/app/src/lib.rs @@ -352,7 +352,7 @@ impl App { .await_crt() .map_ok(move |id| { latch.release(); - info!("Certified identity: {}", id.name().as_ref()); + info!("Certified identity: {}", id.name()); }) .map_err(|_| { // The daemon task was lost?! diff --git a/linkerd/dns/name/Cargo.toml b/linkerd/dns/name/Cargo.toml index d3215b1739..0a9bbb3c43 100644 --- a/linkerd/dns/name/Cargo.toml +++ b/linkerd/dns/name/Cargo.toml @@ -9,4 +9,3 @@ publish = false [dependencies] thiserror = "1.0" untrusted = "0.7" -webpki = "0.21" diff --git a/linkerd/dns/name/src/lib.rs b/linkerd/dns/name/src/lib.rs index 9aff437180..f0222e25cb 100644 --- a/linkerd/dns/name/src/lib.rs +++ b/linkerd/dns/name/src/lib.rs @@ -4,5 +4,5 @@ mod name; mod suffix; -pub use self::name::{InvalidName, Name}; +pub use self::name::{InvalidName, Name, NameRef}; pub use self::suffix::Suffix; diff --git a/linkerd/dns/name/src/name.rs b/linkerd/dns/name/src/name.rs index 9992b1b35d..ee31aa915a 100644 --- a/linkerd/dns/name/src/name.rs +++ b/linkerd/dns/name/src/name.rs @@ -1,60 +1,95 @@ -use std::convert::TryFrom; -use std::fmt; +// Based on https://github.com/briansmith/webpki/blob/18cda8a5e32dfc2723930018853a984bd634e667/src/name/dns_name.rs +// +// Copyright 2015-2020 Brian Smith. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use std::{convert::TryFrom, fmt, ops::Deref}; use thiserror::Error; -/// A `Name` is guaranteed to be syntactically valid. The validity rules +/// A DNS Name suitable for use in the TLS Server Name Indication (SNI) +/// extension and/or for use as the reference hostname for which to verify a +/// certificate. +/// +/// A `Name` is guaranteed to be syntactically valid. The validity rules are +/// specified in [RFC 5280 Section 7.2], except that underscores are also +/// allowed. +/// +/// `Name` stores a copy of the input it was constructed from in a `String` +/// and so it is only available when the `std` default feature is enabled. +/// +/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct Name(String); + +/// A reference to a DNS Name suitable for use in the TLS Server Name Indication +/// (SNI) extension and/or for use as the reference hostname for which to verify +/// a certificate. +/// +/// A `NameRef` is guaranteed to be syntactically valid. The validity rules /// are specified in [RFC 5280 Section 7.2], except that underscores are also /// allowed. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct Name(webpki::DNSName); +/// +/// `Eq`, `PartialEq`, etc. are not implemented because name comparison +/// frequently should be done case-insensitively and/or with other caveats that +/// depend on the specific circumstances in which the comparison is done. +/// +/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2 +#[derive(Clone, Copy, Debug)] +pub struct NameRef<'a>(&'a [u8]); #[derive(Copy, Clone, Debug, Eq, PartialEq, Error)] #[error("invalid DNS name")] pub struct InvalidName; +// === impl Name === + impl Name { #[inline] pub fn is_localhost(&self) -> bool { - self.as_ref().eq_ignore_ascii_case("localhost.") + self.as_str().eq_ignore_ascii_case("localhost.") } #[inline] pub fn without_trailing_dot(&self) -> &str { - self.as_ref().trim_end_matches('.') + self.as_str().trim_end_matches('.') } #[inline] - pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { - self.0.as_ref() + pub fn as_ref(&self) -> NameRef<'_> { + NameRef(self.0.as_bytes()) } -} -impl fmt::Debug for Name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let s: &str = AsRef::::as_ref(&self.0); - s.fmt(f) + pub fn as_str(&self) -> &str { + self.0.as_str() } -} -impl fmt::Display for Name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let s: &str = AsRef::::as_ref(&self.0); - s.fmt(f) + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() } } -impl From for Name { - fn from(n: webpki::DNSName) -> Self { - Name(n) +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) } } impl<'a> TryFrom<&'a [u8]> for Name { type Error = InvalidName; fn try_from(s: &[u8]) -> Result { - webpki::DNSNameRef::try_from_ascii(s) - .map_err(|_invalid| InvalidName) - .map(|r| r.to_owned().into()) + let n = NameRef::try_from_ascii(s)?; + Ok(n.to_owned()) } } @@ -65,23 +100,153 @@ impl std::str::FromStr for Name { } } -impl From for webpki::DNSName { - fn from(Name(name): Name) -> webpki::DNSName { - name +impl Deref for Name { + type Target = str; + + #[inline] + fn deref(&self) -> &str { + self.0.as_str() } } -impl AsRef for Name { +impl Deref for NameRef<'_> { + type Target = [u8]; #[inline] - fn as_ref(&self) -> &str { - >::as_ref(&self.0) + fn deref(&self) -> &[u8] { + self.0 + } +} + +impl<'a> NameRef<'a> { + /// Constructs a `NameRef` from the given input if the input is a + /// syntactically-valid DNS name. + pub fn try_from_ascii(dns_name: &'a [u8]) -> Result { + if !is_valid_reference_dns_id(untrusted::Input::from(dns_name)) { + return Err(InvalidName); + } + + Ok(Self(dns_name)) } + + /// Constructs a `NameRef` from the given input if the input is a + /// syntactically-valid DNS name. + pub fn try_from_ascii_str(dns_name: &'a str) -> Result { + Self::try_from_ascii(dns_name.as_bytes()) + } + + /// Constructs a `Name` from this `NameRef` + pub fn to_owned(self) -> Name { + // NameRef is already guaranteed to be valid ASCII, which is a + // subset of UTF-8. + let s: &str = self.into(); + Name(s.to_ascii_lowercase()) + } +} + +impl<'a> From> for &'a str { + fn from(NameRef(d): NameRef<'a>) -> Self { + // The unwrap won't fail because NameRefs are guaranteed to be ASCII + // and ASCII is a subset of UTF-8. + core::str::from_utf8(d).unwrap() + } +} + +fn is_valid_reference_dns_id(hostname: untrusted::Input<'_>) -> bool { + is_valid_dns_id(hostname) +} + +// https://tools.ietf.org/html/rfc5280#section-4.2.1.6: +// +// When the subjectAltName extension contains a domain name system +// label, the domain name MUST be stored in the dNSName (an IA5String). +// The name MUST be in the "preferred name syntax", as specified by +// Section 3.5 of [RFC1034] and as modified by Section 2.1 of +// [RFC1123]. +// +// https://bugzilla.mozilla.org/show_bug.cgi?id=1136616: As an exception to the +// requirement above, underscores are also allowed in names for compatibility. +fn is_valid_dns_id(hostname: untrusted::Input<'_>) -> bool { + // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873/ + if hostname.len() > 253 { + return false; + } + + let mut input = untrusted::Reader::new(hostname); + + let mut label_length = 0; + let mut label_is_all_numeric = false; + let mut label_ends_with_hyphen = false; + + loop { + const MAX_LABEL_LENGTH: usize = 63; + + match input.read_byte() { + Ok(b'-') => { + if label_length == 0 { + return false; // Labels must not start with a hyphen. + } + label_is_all_numeric = false; + label_ends_with_hyphen = true; + label_length += 1; + if label_length > MAX_LABEL_LENGTH { + return false; + } + } + + Ok(b'0'..=b'9') => { + if label_length == 0 { + label_is_all_numeric = true; + } + label_ends_with_hyphen = false; + label_length += 1; + if label_length > MAX_LABEL_LENGTH { + return false; + } + } + + Ok(b'a'..=b'z') | Ok(b'A'..=b'Z') | Ok(b'_') => { + label_is_all_numeric = false; + label_ends_with_hyphen = false; + label_length += 1; + if label_length > MAX_LABEL_LENGTH { + return false; + } + } + + Ok(b'.') => { + if label_ends_with_hyphen { + return false; // Labels must not end with a hyphen. + } + if label_length == 0 { + return false; + } + label_length = 0; + } + + _ => { + return false; + } + } + + if input.at_end() { + break; + } + } + + if label_ends_with_hyphen { + return false; // Labels must not end with a hyphen. + } + + if label_is_all_numeric { + return false; // Last label must not be all numeric. + } + + true } #[cfg(test)] mod tests { use super::*; - use std::str::FromStr; #[test] fn test_is_localhost() { @@ -117,7 +282,7 @@ mod tests { dns_name ) } - assert!(Name::from_str(".").is_err()); - assert!(Name::from_str("").is_err()); + assert!(".".parse::().is_err()); + assert!("".parse::().is_err()); } } diff --git a/linkerd/dns/src/lib.rs b/linkerd/dns/src/lib.rs index b476e8c3d6..4ed9b5bc70 100644 --- a/linkerd/dns/src/lib.rs +++ b/linkerd/dns/src/lib.rs @@ -1,7 +1,7 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -pub use linkerd_dns_name::{InvalidName, Name, Suffix}; +pub use linkerd_dns_name::{InvalidName, Name, NameRef, Suffix}; use linkerd_error::Error; use std::{fmt, net}; use thiserror::Error; @@ -81,7 +81,7 @@ impl Resolver { name: &Name, ) -> Result<(Vec, time::Sleep), ResolveError> { debug!(%name, "resolve_a"); - let lookup = self.dns.lookup_ip(name.as_ref()).await?; + let lookup = self.dns.lookup_ip(name.as_str()).await?; let valid_until = Instant::from_std(lookup.valid_until()); let ips = lookup.iter().collect::>(); Ok((ips, time::sleep_until(valid_until))) @@ -89,7 +89,7 @@ impl Resolver { async fn resolve_srv(&self, name: &Name) -> Result<(Vec, time::Sleep), Error> { debug!(%name, "resolve_srv"); - let srv = self.dns.srv_lookup(name.as_ref()).await?; + let srv = self.dns.srv_lookup(name.as_str()).await?; let valid_until = Instant::from_std(srv.as_lookup().valid_until()); let addrs = srv .into_iter() @@ -183,8 +183,8 @@ mod tests { ]; for case in VALID { - let name = Name::from_str(case.input); - assert_eq!(name.as_ref().map(|x| x.as_ref()), Ok(case.output)); + let name = case.input.parse::(); + assert_eq!(name.as_deref(), Ok(case.output)); } static INVALID: &[&str] = &[ diff --git a/linkerd/identity/Cargo.toml b/linkerd/identity/Cargo.toml index b4a9bb74d3..ee8782a93a 100644 --- a/linkerd/identity/Cargo.toml +++ b/linkerd/identity/Cargo.toml @@ -11,4 +11,3 @@ thiserror = "1.0" pin-project = "1" tracing = "0.1.29" untrusted = "0.7" -webpki = "=0.21.4" diff --git a/linkerd/identity/src/lib.rs b/linkerd/identity/src/lib.rs index 96bda44f39..8ac6336d4c 100644 --- a/linkerd/identity/src/lib.rs +++ b/linkerd/identity/src/lib.rs @@ -28,13 +28,6 @@ impl std::ops::Deref for Name { } } -impl Name { - #[inline] - pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { - self.0.as_webpki() - } -} - impl FromStr for Name { type Err = InvalidName; @@ -59,12 +52,6 @@ impl TryFrom<&[u8]> for Name { } } -impl AsRef for Name { - fn as_ref(&self) -> &str { - (*self.0).as_ref() - } -} - impl fmt::Debug for Name { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fmt::Debug::fmt(&self.0, f) @@ -77,6 +64,12 @@ impl fmt::Display for Name { } } +impl From for Name { + fn from(LocalId(name): LocalId) -> Name { + name + } +} + // === impl LocalId === impl From for LocalId { @@ -85,15 +78,11 @@ impl From for LocalId { } } -impl From for Name { - fn from(LocalId(name): LocalId) -> Name { - name - } -} +impl std::ops::Deref for LocalId { + type Target = Name; -impl LocalId { - pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { - self.0.as_webpki() + fn deref(&self) -> &Name { + &self.0 } } diff --git a/linkerd/tls/rustls/src/client.rs b/linkerd/tls/rustls/src/client.rs index 489e822345..638643cb8e 100644 --- a/linkerd/tls/rustls/src/client.rs +++ b/linkerd/tls/rustls/src/client.rs @@ -1,15 +1,13 @@ use futures::prelude::*; use linkerd_io as io; use linkerd_stack::Service; -use linkerd_tls::{ - client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef, ServerId, -}; +use linkerd_tls::{client::AlpnProtocols, ClientTls, HasNegotiatedProtocol, NegotiatedProtocolRef}; use std::{pin::Pin, sync::Arc}; use tokio_rustls::rustls::{ClientConfig, Session}; #[derive(Clone)] pub struct Connect { - server_id: ServerId, + server_id: webpki::DNSName, config: Arc, } @@ -41,10 +39,11 @@ impl Connect { } }; - Self { - server_id: client_tls.server_id, - config, - } + 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 } } } @@ -65,7 +64,7 @@ where fn call(&mut self, io: I) -> Self::Future { tokio_rustls::TlsConnector::from(self.config.clone()) - .connect(self.server_id.as_webpki(), io) + .connect(self.server_id.as_ref(), io) .map_ok(ClientIo) } } diff --git a/linkerd/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs index f9810b76dd..5615525a70 100644 --- a/linkerd/tls/rustls/src/lib.rs +++ b/linkerd/tls/rustls/src/lib.rs @@ -165,9 +165,11 @@ impl TrustAnchors { // // TODO: Restrict accepted signature algorithms. static NO_OCSP: &[u8] = &[]; + let crt_id = webpki::DNSNameRef::try_from_ascii((***crt.id).as_bytes()) + .map_err(|e| InvalidCrt(TLSError::General(e.to_string())))?; client .get_verifier() - .verify_server_cert(&client.root_store, &crt.chain, crt.id.as_webpki(), NO_OCSP) + .verify_server_cert(&client.root_store, &crt.chain, crt_id, NO_OCSP) .map_err(InvalidCrt)?; debug!("certified {}", crt.id); diff --git a/linkerd/tls/rustls/src/server.rs b/linkerd/tls/rustls/src/server.rs index 5e4bf4b54f..e9de8f7e65 100644 --- a/linkerd/tls/rustls/src/server.rs +++ b/linkerd/tls/rustls/src/server.rs @@ -55,9 +55,10 @@ fn client_identity(tls: &tokio_rustls::server::TlsStream) -> Option Some(ClientId(linkerd_identity::Name::from( - linkerd_dns_name::Name::from(n.to_owned()), - ))), + webpki::GeneralDNSNameRef::DNSName(n) => { + let n = linkerd_dns_name::NameRef::try_from_ascii_str((*n).into()).ok()?; + Some(ClientId(n.to_owned().into())) + } webpki::GeneralDNSNameRef::Wildcard(_) => { // Wildcards can perhaps be handled in a future path... None diff --git a/linkerd/tls/src/client.rs b/linkerd/tls/src/client.rs index 0fa7b2b1cb..d5eaf4fa02 100644 --- a/linkerd/tls/src/client.rs +++ b/linkerd/tls/src/client.rs @@ -173,12 +173,6 @@ impl Deref for ServerId { } } -impl ServerId { - pub fn as_webpki(&self) -> webpki::DNSNameRef<'_> { - self.0.as_webpki() - } -} - impl FromStr for ServerId { type Err = id::InvalidName; fn from_str(s: &str) -> Result { diff --git a/linkerd/tls/src/server/mod.rs b/linkerd/tls/src/server/mod.rs index 5ac0dde78a..d38d8c1b9e 100644 --- a/linkerd/tls/src/server/mod.rs +++ b/linkerd/tls/src/server/mod.rs @@ -10,6 +10,7 @@ use linkerd_io::{self as io, AsyncReadExt, EitherIo, PrefixedIo}; use linkerd_stack::{layer, ExtractParam, InsertParam, NewService, Param, Service, ServiceExt}; use std::{ fmt, + ops::Deref, pin::Pin, str::FromStr, task::{Context, Poll}, @@ -254,8 +255,10 @@ impl From for id::Name { } } -impl AsRef for ClientId { - fn as_ref(&self) -> &id::Name { +impl Deref for ClientId { + type Target = id::Name; + + fn deref(&self) -> &Self::Target { &self.0 } } From 117dcede8dbe32f0e2a74768a606e5540303fd23 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 16 Oct 2021 23:40:52 +0000 Subject: [PATCH 14/31] Remove webpki from linkerd-tls --- Cargo.lock | 1 - linkerd/tls/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e55c65945a..808f164852 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1372,7 +1372,6 @@ dependencies = [ "tower", "tracing", "untrusted", - "webpki", ] [[package]] diff --git a/linkerd/tls/Cargo.toml b/linkerd/tls/Cargo.toml index 979d490918..46b0fabbcb 100644 --- a/linkerd/tls/Cargo.toml +++ b/linkerd/tls/Cargo.toml @@ -21,7 +21,6 @@ thiserror = "1.0" tokio = { version = "1", features = ["macros", "time"] } tower = "0.4.8" tracing = "0.1.29" -webpki = "0.21" untrusted = "0.7" [dev-dependencies] From 058cda3ee51d81edda5c079bcce3cff811bcc106 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 19:38:40 +0000 Subject: [PATCH 15/31] back out unnecessary change --- .../outbound/src/http/require_id_header.rs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/linkerd/app/outbound/src/http/require_id_header.rs b/linkerd/app/outbound/src/http/require_id_header.rs index d3f1d963f2..bc9c431039 100644 --- a/linkerd/app/outbound/src/http/require_id_header.rs +++ b/linkerd/app/outbound/src/http/require_id_header.rs @@ -85,22 +85,21 @@ where // In either case, we clear the header so it is not passed on outbound requests. if let Some(require_id) = Self::extract_id(&mut request) { match self.tls.as_ref() { - Conditional::Some(tls::ClientTls { server_id, .. }) - if require_id == **server_id => - { - trace!(required = %require_id, "Identity required by header"); - } Conditional::Some(tls::ClientTls { server_id, .. }) => { - debug!( - required = %require_id, - found = %server_id, - "Identity required by header not satisfied" - ); - let e = IdentityRequired { - required: require_id.into(), - found: Some(server_id.clone()), - }; - return future::Either::Left(future::err(e.into())); + if require_id != **server_id { + debug!( + required = %require_id, + found = %server_id, + "Identity required by header not satisfied" + ); + let e = IdentityRequired { + required: require_id.into(), + found: Some(server_id.clone()), + }; + return future::Either::Left(future::err(e.into())); + } else { + trace!(required = %require_id, "Identity required by header"); + } } Conditional::None(_) => { debug!(required = %require_id, "Identity required by header not satisfied"); From aea2d5c9e5bb13887821d897d6f48778c3d55c62 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 19:46:08 +0000 Subject: [PATCH 16/31] fixup type alias --- linkerd/app/inbound/src/detect.rs | 2 +- linkerd/app/inbound/src/direct.rs | 2 +- linkerd/tls/src/server/mod.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/linkerd/app/inbound/src/detect.rs b/linkerd/app/inbound/src/detect.rs index 3bb3a05d83..a6863ccbc6 100644 --- a/linkerd/app/inbound/src/detect.rs +++ b/linkerd/app/inbound/src/detect.rs @@ -53,7 +53,7 @@ struct TlsParams { identity: identity::LocalCrtKey, } -type TlsIo = tls::server::Io>>; +type TlsIo = tls::server::Io>, I>; // === impl Inbound === diff --git a/linkerd/app/inbound/src/direct.rs b/linkerd/app/inbound/src/direct.rs index dfe4250c12..f2b33dd88c 100644 --- a/linkerd/app/inbound/src/direct.rs +++ b/linkerd/app/inbound/src/direct.rs @@ -52,7 +52,7 @@ pub struct ClientInfo { pub local_addr: OrigDstAddr, } -type TlsIo = tls::server::Io>>; +type TlsIo = tls::server::Io>, I>; type FwdIo = SensorIo>>; pub type GatewayIo = io::EitherIo, SensorIo>>; diff --git a/linkerd/tls/src/server/mod.rs b/linkerd/tls/src/server/mod.rs index 5545f90640..773f39196d 100644 --- a/linkerd/tls/src/server/mod.rs +++ b/linkerd/tls/src/server/mod.rs @@ -57,7 +57,7 @@ pub type ConditionalServerTls = Conditional; pub type DetectIo = EitherIo>; -pub type Io = EitherIo>; +pub type Io = EitherIo>; #[derive(Clone, Debug)] pub struct NewDetectTls { @@ -138,7 +138,7 @@ where L::Future: Send, LIo: io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin + 'static, N: NewService + Clone + Send + 'static, - NSvc: Service, Response = ()> + Send + 'static, + NSvc: Service, Response = ()> + Send + 'static, NSvc::Error: Into, NSvc::Future: Send, { From f5e8c6483d568286752fe0808a7a613584c45177 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 20:09:25 +0000 Subject: [PATCH 17/31] drop unused patch --- linkerd/tls/fuzz/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/linkerd/tls/fuzz/Cargo.toml b/linkerd/tls/fuzz/Cargo.toml index 5185c5e198..23ff35bba4 100644 --- a/linkerd/tls/fuzz/Cargo.toml +++ b/linkerd/tls/fuzz/Cargo.toml @@ -24,6 +24,3 @@ name = "fuzz_target_1" path = "fuzz_targets/fuzz_target_1.rs" test = false doc = false - -[patch.crates-io] -webpki = { git = "https://github.com/linkerd/webpki", branch = "cert-dns-names-0.21"} From 05c2be89c43607a81d3bbd47087a73b741a77f0c Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 20:09:34 +0000 Subject: [PATCH 18/31] restore comments --- linkerd/tls/src/server/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/linkerd/tls/src/server/mod.rs b/linkerd/tls/src/server/mod.rs index 773f39196d..0cc5d860d2 100644 --- a/linkerd/tls/src/server/mod.rs +++ b/linkerd/tls/src/server/mod.rs @@ -167,13 +167,18 @@ where let (peer, io) = match sni { // If we detected an SNI matching this proxy, terminate TLS. Some(ServerId(sni)) if sni == id => { + trace!("Identified local SNI"); let (peer, io) = tls.oneshot(io).await?; (Conditional::Some(peer), EitherIo::Left(io)) } + // If we detected another SNI, continue proxying the + // opaque stream. Some(sni) => { + debug!(%sni, "Identified foreign SNI"); let peer = ServerTls::Passthru { sni }; (Conditional::Some(peer), EitherIo::Right(io)) } + // If no TLS was detected, continue proxying the stream. None => ( Conditional::None(NoServerTls::NoClientHello), EitherIo::Right(io), From 4c1f719b1fa2842ddbf804e33b3b39aae8318641 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 20:10:04 +0000 Subject: [PATCH 19/31] needless change --- linkerd/app/inbound/src/direct.rs | 1 - linkerd/tls/src/server/mod.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/linkerd/app/inbound/src/direct.rs b/linkerd/app/inbound/src/direct.rs index f2b33dd88c..0bacf47db5 100644 --- a/linkerd/app/inbound/src/direct.rs +++ b/linkerd/app/inbound/src/direct.rs @@ -308,7 +308,6 @@ where task::Poll::Ready(Ok(())) } - #[inline] fn call(&mut self, io: I) -> Self::Future { // Copy the underlying TLS config and set an ALPN value. // diff --git a/linkerd/tls/src/server/mod.rs b/linkerd/tls/src/server/mod.rs index 0cc5d860d2..b82c79d31a 100644 --- a/linkerd/tls/src/server/mod.rs +++ b/linkerd/tls/src/server/mod.rs @@ -263,7 +263,7 @@ impl From for id::Name { impl Deref for ClientId { type Target = id::Name; - fn deref(&self) -> &Self::Target { + fn deref(&self) -> &id::Name { &self.0 } } From c28a124e67ca959fce9a9c477b352f3e45d1b413 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 20:33:26 +0000 Subject: [PATCH 20/31] Move Csr to proxy-identity --- linkerd/proxy/identity/src/certify.rs | 22 +++++++++++++++++++++- linkerd/proxy/identity/src/lib.rs | 2 +- linkerd/tls/rustls/src/lib.rs | 20 -------------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index be72a1b4c7..e59557fe5c 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -6,7 +6,7 @@ use linkerd_identity as id; use linkerd_metrics::Counter; use linkerd_stack::{NewService, Param, Service}; use linkerd_tls as tls; -use linkerd_tls_rustls::{self as rustls, Crt, CrtKey, Csr, Key, TrustAnchors}; +use linkerd_tls_rustls::{self as rustls, Crt, CrtKey, Key, TrustAnchors}; use std::{ convert::TryFrom, sync::Arc, @@ -34,6 +34,10 @@ pub struct Config { pub max_refresh: Duration, } +/// A DER-encoded X.509 certificate signing request. +#[derive(Clone, Debug)] +pub struct Csr(Arc>); + /// Holds the process's local TLS identity state. /// /// Updates dynamically as certificates are provisioned from the Identity service. @@ -286,3 +290,19 @@ impl Param for LocalCrtKey { self.id().clone() } } + +// === impl Csr === + +impl Csr { + pub fn from_der(der: Vec) -> Option { + if der.is_empty() { + return None; + } + + Some(Csr(Arc::new(der))) + } + + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } +} diff --git a/linkerd/proxy/identity/src/lib.rs b/linkerd/proxy/identity/src/lib.rs index 774a53d9a4..8494c3b0d8 100644 --- a/linkerd/proxy/identity/src/lib.rs +++ b/linkerd/proxy/identity/src/lib.rs @@ -6,7 +6,7 @@ pub mod metrics; mod token; pub use self::{ - certify::{AwaitCrt, CrtKeySender, LocalCrtKey}, + certify::{AwaitCrt, CrtKeySender, Csr, LocalCrtKey}, token::TokenSource, }; pub use linkerd_identity::*; diff --git a/linkerd/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs index 5615525a70..34136a040a 100644 --- a/linkerd/tls/rustls/src/lib.rs +++ b/linkerd/tls/rustls/src/lib.rs @@ -24,10 +24,6 @@ pub struct Key(Arc); struct SigningKey(Arc); struct Signer(Arc); -/// A DER-encoded X.509 certificate signing request. -#[derive(Clone, Debug)] -pub struct Csr(Arc>); - #[derive(Clone)] pub struct TrustAnchors(Arc); @@ -60,22 +56,6 @@ const SIGNATURE_ALG_RUSTLS_ALGORITHM: internal::msgs::enums::SignatureAlgorithm internal::msgs::enums::SignatureAlgorithm::ECDSA; const TLS_VERSIONS: &[ProtocolVersion] = &[ProtocolVersion::TLSv1_3]; -// === impl Csr === - -impl Csr { - pub fn from_der(der: Vec) -> Option { - if der.is_empty() { - return None; - } - - Some(Csr(Arc::new(der))) - } - - pub fn to_vec(&self) -> Vec { - self.0.to_vec() - } -} - // === impl Key === impl Key { From 5b91cb71197331b0bbd35d4ba5703167dc84c1c8 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 20:38:00 +0000 Subject: [PATCH 21/31] Split cryptographic dependencies into a dedicated crate The `ring`/`rustls`/`webpki` crates provide the cryptographic primitives that we use for the proxy's mTLS functionality. But there's a desire to support other cryptographic implementations (i.e. openssl/boringssl), especially for FIPS 140-2. This change introduces a new crate, `linkerd-tls-rustls`, into which all types that depend on `ring`/`rustls`/`webpki` are moved. Specifically, `Key`, `Crt`, and `CrtKey` are moved from `linkerd-identity` into `rustls`. The `linkerd-tls` crate becomes generic over its TLS implementation by using a `NewService` to build client connectors and a `Service` to terminate server-side TLS connections. From dd198fed88ded00953687ff16bc7c1b16a8198dd Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 20:46:40 +0000 Subject: [PATCH 22/31] unused code --- linkerd/tls/rustls/src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/linkerd/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs index 34136a040a..56e19e36f5 100644 --- a/linkerd/tls/rustls/src/lib.rs +++ b/linkerd/tls/rustls/src/lib.rs @@ -96,11 +96,6 @@ impl sign::Signer for Signer { // === impl TrustAnchors === impl TrustAnchors { - #[cfg(any(test, feature = "test-util"))] - fn empty() -> Self { - TrustAnchors(Arc::new(ClientConfig::new())) - } - pub fn from_pem(s: &str) -> Option { use std::io::Cursor; From 94dd9759a5f46635dd03fd632a4c1ad0e2749217 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 21:19:06 +0000 Subject: [PATCH 23/31] Revert "unused code" This reverts commit dd198fed88ded00953687ff16bc7c1b16a8198dd. --- linkerd/tls/rustls/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/linkerd/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs index 56e19e36f5..34136a040a 100644 --- a/linkerd/tls/rustls/src/lib.rs +++ b/linkerd/tls/rustls/src/lib.rs @@ -96,6 +96,11 @@ impl sign::Signer for Signer { // === impl TrustAnchors === impl TrustAnchors { + #[cfg(any(test, feature = "test-util"))] + fn empty() -> Self { + TrustAnchors(Arc::new(ClientConfig::new())) + } + pub fn from_pem(s: &str) -> Option { use std::io::Cursor; From 9efd9c8b7f061c40612fdc197e93bafb56c2bc89 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 21:19:57 +0000 Subject: [PATCH 24/31] fix feature flagging on TrustAnchors::empty --- linkerd/tls/rustls/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkerd/tls/rustls/src/lib.rs b/linkerd/tls/rustls/src/lib.rs index 34136a040a..93e2eac166 100644 --- a/linkerd/tls/rustls/src/lib.rs +++ b/linkerd/tls/rustls/src/lib.rs @@ -96,7 +96,7 @@ impl sign::Signer for Signer { // === impl TrustAnchors === impl TrustAnchors { - #[cfg(any(test, feature = "test-util"))] + #[cfg(feature = "test-util")] fn empty() -> Self { TrustAnchors(Arc::new(ClientConfig::new())) } From 491e0ba8f090a0388ba7dd8de935f4c7c187dd4e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 21:47:09 +0000 Subject: [PATCH 25/31] fixups for nightly --- linkerd/app/inbound/src/test_util.rs | 6 ++++-- linkerd/tls/rustls/src/server.rs | 8 +------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/linkerd/app/inbound/src/test_util.rs b/linkerd/app/inbound/src/test_util.rs index adef076bb2..7935983c53 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::LocalCrtKey, + 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_proxy_identity::LocalCrtKey::default_for_test(), + identity: LocalCrtKey::default_for_test(), metrics: metrics.proxy, tap, span_sink: None, diff --git a/linkerd/tls/rustls/src/server.rs b/linkerd/tls/rustls/src/server.rs index e9de8f7e65..ffde05067f 100644 --- a/linkerd/tls/rustls/src/server.rs +++ b/linkerd/tls/rustls/src/server.rs @@ -7,11 +7,6 @@ use std::{pin::Pin, sync::Arc}; use tokio_rustls::rustls::{Certificate, ServerConfig, Session}; use tracing::debug; -#[derive(Clone)] -pub struct Terminate { - config: Arc, -} - pub type TerminateFuture = futures::future::MapOk< tokio_rustls::Accept, fn(tokio_rustls::server::TlsStream) -> (ServerTls, ServerIo), @@ -20,8 +15,7 @@ pub type TerminateFuture = futures::future::MapOk< #[derive(Debug)] pub struct ServerIo(tokio_rustls::server::TlsStream); -// === impl Terminate === - +/// Terminates a TLS connection. pub fn terminate(config: Arc, io: I) -> TerminateFuture where I: io::AsyncRead + io::AsyncWrite + Send + Unpin, From efa84208b59b54e72ecd255988450377e6651cdb Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sun, 17 Oct 2021 22:09:58 +0000 Subject: [PATCH 26/31] fix feature flagging for inbound fuzzer --- linkerd/app/inbound/fuzz/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/linkerd/app/inbound/fuzz/Cargo.toml b/linkerd/app/inbound/fuzz/Cargo.toml index 8e1c030581..170afb67f0 100644 --- a/linkerd/app/inbound/fuzz/Cargo.toml +++ b/linkerd/app/inbound/fuzz/Cargo.toml @@ -11,14 +11,15 @@ cargo-fuzz = true [target.'cfg(fuzzing)'.dependencies] arbitrary = { version = "1", features = ["derive"] } -libfuzzer-sys = { version = "0.4.2", features = ["arbitrary-derive"] } -tokio = { version = "1", features = ["full"] } hyper = { version = "0.14.9", features = ["http1", "http2"] } http = "0.2" +libfuzzer-sys = { version = "0.4.2", features = ["arbitrary-derive"] } linkerd-app-core = { path = "../../core" } linkerd-app-inbound = { path = ".." } linkerd-app-test = { path = "../../test" } +linkerd-proxy-identity = { path = "../../../proxy/identity", features = ["test-util"] } linkerd-tracing = { path = "../../../tracing", features = ["ansi"] } +tokio = { version = "1", features = ["full"] } tracing = "0.1" # Prevent this from interfering with workspaces From b5cf66251fbf01570144e4d506b506f26165022a Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 18 Oct 2021 18:58:46 +0000 Subject: [PATCH 27/31] drop unneeded deps --- Cargo.lock | 4 ---- linkerd/identity/Cargo.toml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fe9e22ad7..78e54fb715 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -992,10 +992,6 @@ name = "linkerd-identity" version = "0.1.0" dependencies = [ "linkerd-dns-name", - "pin-project", - "thiserror", - "tracing", - "untrusted", ] [[package]] diff --git a/linkerd/identity/Cargo.toml b/linkerd/identity/Cargo.toml index ee8782a93a..dd4d10907c 100644 --- a/linkerd/identity/Cargo.toml +++ b/linkerd/identity/Cargo.toml @@ -7,7 +7,3 @@ edition = "2018" [dependencies] linkerd-dns-name = { path = "../dns/name" } -thiserror = "1.0" -pin-project = "1" -tracing = "0.1.29" -untrusted = "0.7" From 8d7d70b7acabde3d411c0764bc0a1487af3d7cfc Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 18 Oct 2021 19:08:08 +0000 Subject: [PATCH 28/31] drop unneeded deps --- Cargo.lock | 2 -- linkerd/tls/rustls/Cargo.toml | 2 -- linkerd/tls/rustls/src/server.rs | 4 ++-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78e54fb715..9102ef1bdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1375,12 +1375,10 @@ name = "linkerd-tls-rustls" version = "0.1.0" dependencies = [ "futures", - "linkerd-dns-name", "linkerd-identity", "linkerd-io", "linkerd-stack", "linkerd-tls", - "pin-project", "ring", "thiserror", "tokio-rustls", diff --git a/linkerd/tls/rustls/Cargo.toml b/linkerd/tls/rustls/Cargo.toml index 7547e5af7c..5655aa7540 100644 --- a/linkerd/tls/rustls/Cargo.toml +++ b/linkerd/tls/rustls/Cargo.toml @@ -12,12 +12,10 @@ test-util = [] [dependencies] futures = { version = "0.3", default-features = false } -linkerd-dns-name = { path = "../../dns/name" } linkerd-identity = { path = "../../identity" } linkerd-io = { path = "../../io" } linkerd-stack = { path = "../../stack" } linkerd-tls = { path = ".." } -pin-project = "1" ring = "0.16.19" thiserror = "1" tokio-rustls = "0.22" diff --git a/linkerd/tls/rustls/src/server.rs b/linkerd/tls/rustls/src/server.rs index ffde05067f..e0acfde629 100644 --- a/linkerd/tls/rustls/src/server.rs +++ b/linkerd/tls/rustls/src/server.rs @@ -50,8 +50,8 @@ fn client_identity(tls: &tokio_rustls::server::TlsStream) -> Option { - let n = linkerd_dns_name::NameRef::try_from_ascii_str((*n).into()).ok()?; - Some(ClientId(n.to_owned().into())) + let s: &str = (*n).into(); + s.parse().ok().map(ClientId) } webpki::GeneralDNSNameRef::Wildcard(_) => { // Wildcards can perhaps be handled in a future path... From ffb8c09449e9fc70ed636da4b52d69e3aac620a0 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 18 Oct 2021 19:10:57 +0000 Subject: [PATCH 29/31] tidy imports --- linkerd/app/inbound/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/linkerd/app/inbound/src/lib.rs b/linkerd/app/inbound/src/lib.rs index bc51f7aa04..0318430aef 100644 --- a/linkerd/app/inbound/src/lib.rs +++ b/linkerd/app/inbound/src/lib.rs @@ -23,8 +23,7 @@ use linkerd_app_core::{ http_tracing::OpenCensusSink, identity::LocalCrtKey, io, - proxy::tap, - proxy::tcp, + proxy::{tap, tcp}, svc, transport::{self, Remote, ServerAddr}, Error, NameMatch, ProxyRuntime, From 4335c434ff29ab69a968fbe94ffedd54e4761b09 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 18 Oct 2021 19:11:54 +0000 Subject: [PATCH 30/31] publish = false --- linkerd/identity/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/linkerd/identity/Cargo.toml b/linkerd/identity/Cargo.toml index dd4d10907c..5ae5915822 100644 --- a/linkerd/identity/Cargo.toml +++ b/linkerd/identity/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Linkerd Developers "] license = "Apache-2.0" edition = "2018" +publish = false [dependencies] linkerd-dns-name = { path = "../dns/name" } From bb3d11d36459fc43e446916c62f4c5a17e50f0c5 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Mon, 18 Oct 2021 19:14:59 +0000 Subject: [PATCH 31/31] inline --- linkerd/proxy/identity/src/certify.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/linkerd/proxy/identity/src/certify.rs b/linkerd/proxy/identity/src/certify.rs index e59557fe5c..fea85bd0bc 100644 --- a/linkerd/proxy/identity/src/certify.rs +++ b/linkerd/proxy/identity/src/certify.rs @@ -260,6 +260,7 @@ impl NewService for LocalCrtKey { type Service = rustls::Connect; /// Creates a new TLS client service. + #[inline] fn new_service(&self, target: tls::ClientTls) -> Self::Service { rustls::Connect::new(target, self.client_config()) }