Skip to content

Commit

Permalink
build(rustls): Upgrade tokio-rustls to 0.26 (#3557)
Browse files Browse the repository at this point in the history
* Revert "Revert "chore(deps): Upgrade tokio-rustls to 0.26 (#3419)""

This reverts commit de25333.

* Expand the set of supported signature algorithms

During the rustls upgrade, it accidentally limited the set of supported signature algorithms to ECDSA256 signatures. This would cause the identity control plane proxy to reject all certify requests with BadSignature if an RSA certificate was used instead of ECDSA.

This updates the set of supported algorithms to most of the full set of what rustls+ring supports, minus a few legacy algorithms.

Tested by deploying to a local cluster and verifying the control plane comes up correctly and app-level networking works as expected.

Signed-off-by: Scott Fleener <[email protected]>

---------

Signed-off-by: Scott Fleener <[email protected]>
  • Loading branch information
sfleen authored Jan 22, 2025
1 parent 399ab1d commit 4f0775d
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 120 deletions.
46 changes: 26 additions & 20 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3425,23 +3425,26 @@ dependencies = [

[[package]]
name = "rustls"
version = "0.21.12"
version = "0.23.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"sct",
"subtle",
"zeroize",
]

[[package]]
name = "rustls-pemfile"
version = "1.0.4"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"base64 0.21.7",
"rustls-pki-types",
]

[[package]]
Expand All @@ -3452,11 +3455,12 @@ checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"

[[package]]
name = "rustls-webpki"
version = "0.101.7"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]

Expand All @@ -3478,16 +3482,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"

[[package]]
name = "sct"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
]

[[package]]
name = "semver"
version = "1.0.25"
Expand Down Expand Up @@ -3630,6 +3624,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"

[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"

[[package]]
name = "symbolic-common"
version = "12.13.3"
Expand Down Expand Up @@ -3876,9 +3876,9 @@ dependencies = [

[[package]]
name = "tokio-rustls"
version = "0.24.1"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
dependencies = [
"rustls",
"tokio",
Expand Down Expand Up @@ -4500,6 +4500,12 @@ dependencies = [
"synstructure",
]

[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"

[[package]]
name = "zerovec"
version = "0.10.4"
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ http-body = { version = "0.4" }
hyper = { version = "0.14.32", default-features = false }
prost = { version = "0.12" }
prost-types = { version = "0.12" }
tokio-rustls = { version = "0.26", default-features = false, features = [
"ring",
"logging",
] }
tonic = { version = "0.10", default-features = false }
tonic-build = { version = "0.10", default-features = false }

Expand Down
4 changes: 2 additions & 2 deletions linkerd/app/integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ regex = "1"
socket2 = "0.5"
tokio = { version = "1", features = ["io-util", "net", "rt", "macros"] }
tokio-stream = { version = "0.1", features = ["sync"] }
tokio-rustls = "0.24"
rustls-pemfile = "1.0"
tokio-rustls = { workspace = true }
rustls-pemfile = "2.2"
tower = { version = "0.4", default-features = false }
tonic = { workspace = true, features = ["transport"], default-features = false }
tracing = "0.1"
Expand Down
10 changes: 5 additions & 5 deletions linkerd/app/integration/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use super::*;
use linkerd_app_core::proxy::http::TracingExecutor;
use parking_lot::Mutex;
use std::io;
use tokio::net::TcpStream;
use tokio::task::JoinHandle;
use tokio::{net::TcpStream, task::JoinHandle};
use tokio_rustls::rustls::{self, ClientConfig};
use tracing::info_span;

Expand All @@ -15,12 +14,13 @@ type Sender = mpsc::UnboundedSender<(Request, oneshot::Sender<Result<Response, C
#[derive(Clone)]
pub struct TlsConfig {
client_config: Arc<ClientConfig>,
name: rustls::ServerName,
name: rustls::pki_types::ServerName<'static>,
}

impl TlsConfig {
pub fn new(client_config: Arc<ClientConfig>, name: &str) -> Self {
let name = rustls::ServerName::try_from(name).expect("name must be a valid DNS name");
pub fn new(client_config: Arc<ClientConfig>, name: &'static str) -> Self {
let name =
rustls::pki_types::ServerName::try_from(name).expect("name must be a valid DNS name");
TlsConfig {
client_config,
name,
Expand Down
61 changes: 38 additions & 23 deletions linkerd/app/integration/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{
};

use linkerd2_proxy_api::identity as pb;
use tokio_rustls::rustls;
use tokio_rustls::rustls::{self, pki_types::CertificateDer, server::WebPkiClientVerifier};
use tonic as grpc;

pub struct Identity {
Expand Down Expand Up @@ -36,7 +36,7 @@ type Certify = Box<

static TLS_VERSIONS: &[&rustls::SupportedProtocolVersion] = &[&rustls::version::TLS13];
static TLS_SUPPORTED_CIPHERSUITES: &[rustls::SupportedCipherSuite] =
&[rustls::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256];
&[rustls::crypto::ring::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256];

struct Certificates {
pub leaf: Vec<u8>,
Expand All @@ -50,23 +50,32 @@ impl Certificates {
{
let f = fs::File::open(p)?;
let mut r = io::BufReader::new(f);
let mut certs = rustls_pemfile::certs(&mut r)
let mut certs = rustls_pemfile::certs(&mut r);
let leaf = certs
.next()
.expect("no leaf cert in pemfile")
.map_err(|_| io::Error::new(io::ErrorKind::Other, "rustls error reading certs"))?
.as_ref()
.to_vec();
let intermediates = certs
.map(|cert| cert.map(|cert| cert.as_ref().to_vec()))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| io::Error::new(io::ErrorKind::Other, "rustls error reading certs"))?;
let mut certs = certs.drain(..);
let leaf = certs.next().expect("no leaf cert in pemfile");
let intermediates = certs.collect();

Ok(Certificates {
leaf,
intermediates,
})
}

pub fn chain(&self) -> Vec<rustls::Certificate> {
pub fn chain(&self) -> Vec<rustls::pki_types::CertificateDer<'static>> {
let mut chain = Vec::with_capacity(self.intermediates.len() + 1);
chain.push(self.leaf.clone());
chain.extend(self.intermediates.clone());
chain.into_iter().map(rustls::Certificate).collect()
chain
.into_iter()
.map(rustls::pki_types::CertificateDer::from)
.collect()
}

pub fn response(&self) -> pb::CertifyResponse {
Expand All @@ -79,43 +88,49 @@ impl Certificates {
}

impl Identity {
fn load_key<P>(p: P) -> rustls::PrivateKey
fn load_key<P>(p: P) -> rustls::pki_types::PrivateKeyDer<'static>
where
P: AsRef<Path>,
{
let p8 = fs::read(&p).expect("read key");
rustls::PrivateKey(p8)
rustls::pki_types::PrivateKeyDer::try_from(p8).expect("decode key")
}

fn configs(
trust_anchors: &str,
certs: &Certificates,
key: rustls::PrivateKey,
key: rustls::pki_types::PrivateKeyDer<'static>,
) -> (Arc<rustls::ClientConfig>, Arc<rustls::ServerConfig>) {
use std::io::Cursor;
let mut roots = rustls::RootCertStore::empty();
let trust_anchors =
rustls_pemfile::certs(&mut Cursor::new(trust_anchors)).expect("error parsing pemfile");
let (added, skipped) = roots.add_parsable_certificates(&trust_anchors[..]);
let trust_anchors = rustls_pemfile::certs(&mut Cursor::new(trust_anchors))
.map(|bytes| bytes.map(CertificateDer::from))
.collect::<Result<Vec<_>, _>>()
.expect("error parsing pemfile");
let (added, skipped) = roots.add_parsable_certificates(trust_anchors);
assert_ne!(added, 0, "trust anchors must include at least one cert");
assert_eq!(skipped, 0, "no certs in pemfile should be invalid");

let client_config = rustls::ClientConfig::builder()
.with_cipher_suites(TLS_SUPPORTED_CIPHERSUITES)
.with_safe_default_kx_groups()
let mut provider = rustls::crypto::ring::default_provider();
provider.cipher_suites = TLS_SUPPORTED_CIPHERSUITES.to_vec();
let provider = Arc::new(provider);

let client_config = rustls::ClientConfig::builder_with_provider(provider.clone())
.with_protocol_versions(TLS_VERSIONS)
.expect("client config must be valid")
.with_root_certificates(roots.clone())
.with_no_client_auth();

let server_config = rustls::ServerConfig::builder()
.with_cipher_suites(TLS_SUPPORTED_CIPHERSUITES)
.with_safe_default_kx_groups()
let client_cert_verifier =
WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider.clone())
.allow_unauthenticated()
.build()
.expect("server verifier must be valid");

let server_config = rustls::ServerConfig::builder_with_provider(provider)
.with_protocol_versions(TLS_VERSIONS)
.expect("server config must be valid")
.with_client_cert_verifier(Arc::new(
rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(roots),
))
.with_client_cert_verifier(client_cert_verifier)
.with_single_cert(certs.chain(), key)
.unwrap();

Expand Down
2 changes: 1 addition & 1 deletion linkerd/app/outbound/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ futures-util = "0.3"
http-body = { workspace = true }
hyper = { workspace = true, features = ["backports", "deprecated", "http1", "http2"] }
tokio = { version = "1", features = ["macros", "sync", "time"] }
tokio-rustls = "0.24"
tokio-rustls = { workspace = true }
tokio-test = "0.4"
tower-test = "0.4"

Expand Down
33 changes: 28 additions & 5 deletions linkerd/app/outbound/src/tls/logical/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::{
time::Duration,
};
use tokio::sync::watch;
use tokio_rustls::rustls::pki_types::DnsName;

mod basic;

Expand Down Expand Up @@ -171,28 +172,37 @@ fn generate_client_hello(sni: &str) -> Vec<u8> {
use tokio_rustls::rustls::{
internal::msgs::{
base::Payload,
codec::{Codec, Reader},
enums::Compression,
handshake::{
ClientExtension, ClientHelloPayload, HandshakeMessagePayload, HandshakePayload,
Random, SessionId,
Random, ServerName, SessionId,
},
message::{MessagePayload, PlainMessage},
},
server::DnsName,
CipherSuite, ContentType, HandshakeType, ProtocolVersion,
};

let sni = DnsName::try_from(sni.to_string()).unwrap();
let sni = trim_hostname_trailing_dot_for_sni(&sni);

let mut server_name_bytes = vec![];
0u8.encode(&mut server_name_bytes); // encode the type first
(sni.as_ref().len() as u16).encode(&mut server_name_bytes); // then the length as u16
server_name_bytes.extend_from_slice(sni.as_ref().as_bytes()); // then the server name itself

let server_name =
ServerName::read(&mut Reader::init(&server_name_bytes)).expect("Server name is valid");

let hs_payload = HandshakeMessagePayload {
typ: HandshakeType::ClientHello,
payload: HandshakePayload::ClientHello(ClientHelloPayload {
client_version: ProtocolVersion::TLSv1_2,
random: Random::from([0; 32]),
session_id: SessionId::empty(),
session_id: SessionId::read(&mut Reader::init(&[0])).unwrap(),
cipher_suites: vec![CipherSuite::TLS_NULL_WITH_NULL_NULL],
compression_methods: vec![Compression::Null],
extensions: vec![ClientExtension::make_sni(sni.borrow())],
extensions: vec![ClientExtension::ServerName(vec![server_name])],
}),
};

Expand All @@ -202,8 +212,21 @@ fn generate_client_hello(sni: &str) -> Vec<u8> {
let message = PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload(hs_payload_bytes),
payload: Payload::Owned(hs_payload_bytes),
};

message.into_unencrypted_opaque().encode()
}

fn trim_hostname_trailing_dot_for_sni(dns_name: &DnsName<'_>) -> DnsName<'static> {
let dns_name_str = dns_name.as_ref();

// RFC6066: "The hostname is represented as a byte string using
// ASCII encoding without a trailing dot"
if dns_name_str.ends_with('.') {
let trimmed = &dns_name_str[0..dns_name_str.len() - 1];
DnsName::try_from(trimmed).unwrap().to_owned()
} else {
dns_name.to_owned()
}
}
6 changes: 3 additions & 3 deletions linkerd/meshtls/rustls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ test-util = ["linkerd-tls-test-util"]
[dependencies]
futures = { version = "0.3", default-features = false }
ring = { version = "0.17", features = ["std"] }
rustls-pemfile = "1.0"
rustls-webpki = { version = "0.101.5", features = ["std"] }
rustls-pemfile = "2.2"
rustls-webpki = { version = "0.102.8", features = ["std"] }
thiserror = "2"
tokio = { version = "1", features = ["macros", "rt", "sync"] }
tokio-rustls = { version = "0.24", features = ["dangerous_configuration"] }
tokio-rustls = { workspace = true }
tracing = "0.1"

linkerd-dns-name = { path = "../../dns/name" }
Expand Down
Loading

0 comments on commit 4f0775d

Please sign in to comment.