From d2263504376b629a5540f4549b6394353812480f Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Wed, 27 Mar 2024 07:44:32 +0000 Subject: [PATCH 01/10] example(bindings): add async ConfigResolver --- .../client-hello-config-resolution/README.md | 3 + .../src/bin/async_load_server.rs | 132 ++++++++++++++++++ .../src/bin/server.rs | 1 + 3 files changed, 136 insertions(+) create mode 100644 bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs diff --git a/bindings/rust-examples/client-hello-config-resolution/README.md b/bindings/rust-examples/client-hello-config-resolution/README.md index a0e22f9962..302770d5da 100644 --- a/bindings/rust-examples/client-hello-config-resolution/README.md +++ b/bindings/rust-examples/client-hello-config-resolution/README.md @@ -41,3 +41,6 @@ TlsStream { The server said Hello, you are speaking to www.wombat.com ``` Once again there is a successful handshake showing that the server responded with the proper certificate. In this case, the config that the server configured for `www.wombat.com` did not support TLS 1.3, so the TLS 1.2 was negotiated instead. + +## Async Config Resolution +The [async load server](src/bin/async_load_server.rs) has the same functionality as the default [server](src/bin/server.rs), but implements the config resolution in an asynchronous manner. This allows the certificates to be loaded from disk without blocking the tokio runtime. A similar technique could be used to retrieve certificates over the network without blocking the runtime. diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs new file mode 100644 index 0000000000..13db53e698 --- /dev/null +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -0,0 +1,132 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use s2n_tls::{ + callbacks::{ClientHelloCallback, ConfigResolver, ConnectionFuture}, + security::{Policy, DEFAULT_TLS13}, +}; +use s2n_tls_tokio::TlsAcceptor; +use std::{error::Error, pin::Pin}; +use tokio::{io::AsyncWriteExt, net::*, try_join}; + +const PORT: u16 = 1738; + +#[derive(Clone)] +pub struct AsyncAnimalConfigResolver { + // the directory that contains the relevant certs + cert_directory: String, +} + +impl AsyncAnimalConfigResolver { + fn new(cert_directory: String) -> Self { + AsyncAnimalConfigResolver { cert_directory } + } + + // This method will lookup the appropriate certificates and read them from disc + // in an async manner which won't block the tokio task. + async fn server_config( + &self, + animal: String, + ) -> Result { + let cert_path = format!("{}/{}-chain.pem", self.cert_directory, animal); + let key_path = format!("{}/{}-key.pem", self.cert_directory, animal); + // we asynchronously read the cert chain and key from disk + let (cert, key) = try_join!(tokio::fs::read(cert_path), tokio::fs::read(key_path)) + // we map any IO errors to the s2n-tls Error type, as required by the ConfigResolver bounds. + .map_err(|io_error| s2n_tls::error::Error::application(Box::new(io_error)))?; + + let mut config = s2n_tls::config::Builder::new(); + // we can set different policies for different configs. "20190214" doesn't + // support TLS 1.3, so any customer requesting www.wombat.com won't be able + // to negotiate TLS 1.3 + let security_policy = match animal.as_str() { + "wombat" => Policy::from_version("20190214")?, + _ => DEFAULT_TLS13, + }; + config.set_security_policy(&security_policy)?; + config.load_pem(&cert, &key)?; + config.build() + } +} + +impl ClientHelloCallback for AsyncAnimalConfigResolver { + fn on_client_hello( + &self, + connection: &mut s2n_tls::connection::Connection, + ) -> Result>>, s2n_tls::error::Error> { + let sni = match connection.server_name() { + Some(sni) => sni, + None => { + println!("connection contained no SNI"); + return Err(s2n_tls::error::Error::application("no sni".into())); + } + }; + + // simple, limited logic to parse "animal" from "www.animal.com". + let mut tokens = sni.split('.'); + tokens.next(); // "www" + let animal = match tokens.next() { + Some(animal) => animal.to_owned(), // "animal" + None => { + println!("unable to parse sni"); + return Err(s2n_tls::error::Error::application( + format!("unable to parse sni: {}", sni).into(), + )); + } + }; + + // A ConfigResolver can be constructed from a future that returns + // `Result`, with the main additional + // requirements that the future is `'static`, which generally means that + // it can't have any interior references. This will prevent you from + // doing something like + // ``` + // let config_resolver = ConfigResolver::new(self.server_config(animal)); + // ``` + // because the compiler will complain that `&self` doesn't live long enough. + // + // One easy way to get around this is to create a new async block that + // owns all of the necessary data. Here we do this by first cloning the + // async resolver and then passing it into a closure which owns all of + // it's data (using the `move` keyword). + let async_resolver_clone = self.clone(); + let config_resolver = + ConfigResolver::new(async move { async_resolver_clone.server_config(animal).await }); + Ok(Some(Box::pin(config_resolver))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let cert_directory = format!("{}/certs", env!("CARGO_MANIFEST_DIR")); + let resolver = AsyncAnimalConfigResolver::new(cert_directory); + let mut initial_config = s2n_tls::config::Builder::new(); + initial_config.set_client_hello_callback(resolver)?; + + let server = TlsAcceptor::new(initial_config.build()?); + + let listener = TcpListener::bind(&format!("0.0.0.0:{PORT}")).await?; + loop { + let server = server.clone(); + let (stream, _) = listener.accept().await?; + tokio::spawn(async move { + // handshake with the client + let handshake = server.accept(stream).await; + let mut tls = match handshake { + Ok(tls) => tls, + Err(e) => { + println!("error during handshake: {:?}", e); + return Ok(()); + } + }; + + let connection = tls.as_ref(); + let offered_sni = connection.server_name().unwrap(); + let _ = tls + .write(format!("Hello, you are speaking to {offered_sni}").as_bytes()) + .await?; + tls.shutdown().await?; + Ok::<(), Box>(()) + }); + } +} diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/server.rs index 7ad887cfa0..958fdb5163 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/server.rs @@ -30,6 +30,7 @@ impl Default for AnimalConfigResolver { // using the ConfigResolver: https://docs.rs/s2n-tls/latest/s2n_tls/callbacks/struct.ConfigResolver.html# // This is useful if servers need to read from disk or make network calls as part // of the configuration, and want to avoid blocking the tokio task while doing so. +// An example of this implementation is contained in the "async_load_server". impl ClientHelloCallback for AnimalConfigResolver { fn on_client_hello( &self, From eabba83211b93fa2e822e45891cea45f84283376 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Thu, 23 May 2024 17:33:06 -0700 Subject: [PATCH 02/10] Update bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs Co-authored-by: Sam Clark <3758302+goatgoose@users.noreply.github.com> --- .../client-hello-config-resolution/src/bin/async_load_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs index 13db53e698..64370cc162 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -22,7 +22,7 @@ impl AsyncAnimalConfigResolver { AsyncAnimalConfigResolver { cert_directory } } - // This method will lookup the appropriate certificates and read them from disc + // This method will lookup the appropriate certificates and read them from disk // in an async manner which won't block the tokio task. async fn server_config( &self, From af8098937a9161574397950b69ccce32ad52cc77 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Fri, 24 May 2024 00:57:35 +0000 Subject: [PATCH 03/10] address pr feedback - make server config consume self --- .../src/bin/async_load_server.rs | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs index 64370cc162..b1559ec938 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -3,10 +3,11 @@ use s2n_tls::{ callbacks::{ClientHelloCallback, ConfigResolver, ConnectionFuture}, + config::Config, security::{Policy, DEFAULT_TLS13}, }; use s2n_tls_tokio::TlsAcceptor; -use std::{error::Error, pin::Pin}; +use std::{error::Error, future::Future, pin::Pin}; use tokio::{io::AsyncWriteExt, net::*, try_join}; const PORT: u16 = 1738; @@ -17,6 +18,11 @@ pub struct AsyncAnimalConfigResolver { cert_directory: String, } +struct SpecificAnimalResolver { + cert_directory: String, + animal: String, +} + impl AsyncAnimalConfigResolver { fn new(cert_directory: String) -> Self { AsyncAnimalConfigResolver { cert_directory } @@ -24,8 +30,20 @@ impl AsyncAnimalConfigResolver { // This method will lookup the appropriate certificates and read them from disk // in an async manner which won't block the tokio task. + // + // Note that this method consumes `self`. A ConfigResolver can be constructed + // from a future that returns `Result`, with + // the main additional requirements that the future is `'static`. This generally + // means that it can't have any interior references. + // + // If this method took in `&self`, then + // ``` + // let config_resolver = ConfigResolver::new(self.server_config(animal)); + // ``` + // wouldn't compile because the compiler would complain that `&self` doesn't + // live long enough. async fn server_config( - &self, + self, animal: String, ) -> Result { let cert_path = format!("{}/{}-chain.pem", self.cert_directory, animal); @@ -75,23 +93,8 @@ impl ClientHelloCallback for AsyncAnimalConfigResolver { } }; - // A ConfigResolver can be constructed from a future that returns - // `Result`, with the main additional - // requirements that the future is `'static`, which generally means that - // it can't have any interior references. This will prevent you from - // doing something like - // ``` - // let config_resolver = ConfigResolver::new(self.server_config(animal)); - // ``` - // because the compiler will complain that `&self` doesn't live long enough. - // - // One easy way to get around this is to create a new async block that - // owns all of the necessary data. Here we do this by first cloning the - // async resolver and then passing it into a closure which owns all of - // it's data (using the `move` keyword). let async_resolver_clone = self.clone(); - let config_resolver = - ConfigResolver::new(async move { async_resolver_clone.server_config(animal).await }); + let config_resolver = ConfigResolver::new(async_resolver_clone.server_config(animal)); Ok(Some(Box::pin(config_resolver))) } } From 403c33d88f33e45bc68d16b6732b9c7964791bd4 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Fri, 24 May 2024 01:07:24 +0000 Subject: [PATCH 04/10] explicitly mark connection as using sni --- .../client-hello-config-resolution/src/bin/async_load_server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs index b1559ec938..bcd6edb9da 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -95,6 +95,7 @@ impl ClientHelloCallback for AsyncAnimalConfigResolver { let async_resolver_clone = self.clone(); let config_resolver = ConfigResolver::new(async_resolver_clone.server_config(animal)); + connection.server_name_extension_used(); Ok(Some(Box::pin(config_resolver))) } } From 41992bd210016fc8bcb9f060eab4e2dea6184ca2 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Tue, 4 Jun 2024 01:03:32 +0000 Subject: [PATCH 05/10] address pr feedback - remove unused struct --- .../src/bin/async_load_server.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs index bcd6edb9da..676326c60b 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -18,11 +18,6 @@ pub struct AsyncAnimalConfigResolver { cert_directory: String, } -struct SpecificAnimalResolver { - cert_directory: String, - animal: String, -} - impl AsyncAnimalConfigResolver { fn new(cert_directory: String) -> Self { AsyncAnimalConfigResolver { cert_directory } From 25ef8d90027aa0ce3369f48087a23bceb252543a Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Tue, 4 Jun 2024 01:25:05 +0000 Subject: [PATCH 06/10] address lint errors - remove unused imports. Need to add this check to CI --- .../src/bin/async_load_server.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs index 676326c60b..9924d6b206 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -3,11 +3,10 @@ use s2n_tls::{ callbacks::{ClientHelloCallback, ConfigResolver, ConnectionFuture}, - config::Config, security::{Policy, DEFAULT_TLS13}, }; use s2n_tls_tokio::TlsAcceptor; -use std::{error::Error, future::Future, pin::Pin}; +use std::{error::Error, pin::Pin}; use tokio::{io::AsyncWriteExt, net::*, try_join}; const PORT: u16 = 1738; From 1b21e3aad222e91b5b72b2f345c2bad6bd1352c0 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Fri, 14 Jun 2024 00:38:52 +0000 Subject: [PATCH 07/10] address pr feedback - add contribute from lindsay refacoring cert loading to take in owned string --- .../src/bin/async_load_server.rs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs index 9924d6b206..863ca1752b 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -25,23 +25,16 @@ impl AsyncAnimalConfigResolver { // This method will lookup the appropriate certificates and read them from disk // in an async manner which won't block the tokio task. // - // Note that this method consumes `self`. A ConfigResolver can be constructed - // from a future that returns `Result`, with - // the main additional requirements that the future is `'static`. This generally - // means that it can't have any interior references. - // - // If this method took in `&self`, then - // ``` - // let config_resolver = ConfigResolver::new(self.server_config(animal)); - // ``` - // wouldn't compile because the compiler would complain that `&self` doesn't - // live long enough. + // Note that this method takes `String` instead of `&str` like the synchronous + // version in server.rs. ConfigResolver requires a future that is `'static`. async fn server_config( - self, animal: String, + cert_directory: String, ) -> Result { - let cert_path = format!("{}/{}-chain.pem", self.cert_directory, animal); - let key_path = format!("{}/{}-key.pem", self.cert_directory, animal); + println!("asynchronously setting connection config associated with {animal}"); + + let cert_path = format!("{}/{}-chain.pem", cert_directory, animal); + let key_path = format!("{}/{}-key.pem", cert_directory, animal); // we asynchronously read the cert chain and key from disk let (cert, key) = try_join!(tokio::fs::read(cert_path), tokio::fs::read(key_path)) // we map any IO errors to the s2n-tls Error type, as required by the ConfigResolver bounds. @@ -87,8 +80,8 @@ impl ClientHelloCallback for AsyncAnimalConfigResolver { } }; - let async_resolver_clone = self.clone(); - let config_resolver = ConfigResolver::new(async_resolver_clone.server_config(animal)); + let config_future = AsyncAnimalConfigResolver::server_config(animal, self.cert_directory.clone()); + let config_resolver = ConfigResolver::new(config_future); connection.server_name_extension_used(); Ok(Some(Box::pin(config_resolver))) } From dd785c6349e8d0226ec6acb131ec082a84d4336e Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Mon, 17 Jun 2024 21:33:51 +0000 Subject: [PATCH 08/10] address pr feedback - move server_config method out of struct impl --- .../src/bin/async_load_server.rs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs index 863ca1752b..177c6ffc0d 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -21,37 +21,6 @@ impl AsyncAnimalConfigResolver { fn new(cert_directory: String) -> Self { AsyncAnimalConfigResolver { cert_directory } } - - // This method will lookup the appropriate certificates and read them from disk - // in an async manner which won't block the tokio task. - // - // Note that this method takes `String` instead of `&str` like the synchronous - // version in server.rs. ConfigResolver requires a future that is `'static`. - async fn server_config( - animal: String, - cert_directory: String, - ) -> Result { - println!("asynchronously setting connection config associated with {animal}"); - - let cert_path = format!("{}/{}-chain.pem", cert_directory, animal); - let key_path = format!("{}/{}-key.pem", cert_directory, animal); - // we asynchronously read the cert chain and key from disk - let (cert, key) = try_join!(tokio::fs::read(cert_path), tokio::fs::read(key_path)) - // we map any IO errors to the s2n-tls Error type, as required by the ConfigResolver bounds. - .map_err(|io_error| s2n_tls::error::Error::application(Box::new(io_error)))?; - - let mut config = s2n_tls::config::Builder::new(); - // we can set different policies for different configs. "20190214" doesn't - // support TLS 1.3, so any customer requesting www.wombat.com won't be able - // to negotiate TLS 1.3 - let security_policy = match animal.as_str() { - "wombat" => Policy::from_version("20190214")?, - _ => DEFAULT_TLS13, - }; - config.set_security_policy(&security_policy)?; - config.load_pem(&cert, &key)?; - config.build() - } } impl ClientHelloCallback for AsyncAnimalConfigResolver { @@ -80,13 +49,44 @@ impl ClientHelloCallback for AsyncAnimalConfigResolver { } }; - let config_future = AsyncAnimalConfigResolver::server_config(animal, self.cert_directory.clone()); + let config_future = server_config(animal, self.cert_directory.clone()); let config_resolver = ConfigResolver::new(config_future); connection.server_name_extension_used(); Ok(Some(Box::pin(config_resolver))) } } +// This method will lookup the appropriate certificates and read them from disk +// in an async manner which won't block the tokio task. +// +// Note that this method takes `String` instead of `&str` like the synchronous +// version in server.rs. ConfigResolver requires a future that is `'static`. +async fn server_config( + animal: String, + cert_directory: String, +) -> Result { + println!("asynchronously setting connection config associated with {animal}"); + + let cert_path = format!("{}/{}-chain.pem", cert_directory, animal); + let key_path = format!("{}/{}-key.pem", cert_directory, animal); + // we asynchronously read the cert chain and key from disk + let (cert, key) = try_join!(tokio::fs::read(cert_path), tokio::fs::read(key_path)) + // we map any IO errors to the s2n-tls Error type, as required by the ConfigResolver bounds. + .map_err(|io_error| s2n_tls::error::Error::application(Box::new(io_error)))?; + + let mut config = s2n_tls::config::Builder::new(); + // we can set different policies for different configs. "20190214" doesn't + // support TLS 1.3, so any customer requesting www.wombat.com won't be able + // to negotiate TLS 1.3 + let security_policy = match animal.as_str() { + "wombat" => Policy::from_version("20190214")?, + _ => DEFAULT_TLS13, + }; + config.set_security_policy(&security_policy)?; + config.load_pem(&cert, &key)?; + config.build() +} + #[tokio::main] async fn main() -> Result<(), Box> { let cert_directory = format!("{}/certs", env!("CARGO_MANIFEST_DIR")); From fb9e7bd1ebf9510bc21492e6a033faf731748b8f Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Mon, 17 Jun 2024 21:45:42 +0000 Subject: [PATCH 09/10] ci: shallow clone musl repo --- codebuild/spec/buildspec_musl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebuild/spec/buildspec_musl.yml b/codebuild/spec/buildspec_musl.yml index f81d4143c8..72d6e54fe5 100644 --- a/codebuild/spec/buildspec_musl.yml +++ b/codebuild/spec/buildspec_musl.yml @@ -23,7 +23,7 @@ phases: on-failure: ABORT commands: # Install musl libc - - git clone https://git.musl-libc.org/git/musl $MUSL_DIR + - git clone --depth=1 https://git.musl-libc.org/git/musl $MUSL_DIR - echo "Installing musl to $CODEBUILD_SRC_DIR/$MUSL_DIR" - cd $MUSL_DIR - ./configure --prefix=$CODEBUILD_SRC_DIR/$MUSL_DIR From d3ec302eea61f6d8d044e180fee2cf2c60c8905a Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Mon, 17 Jun 2024 21:46:26 +0000 Subject: [PATCH 10/10] Revert "ci: shallow clone musl repo" This reverts commit fb9e7bd1ebf9510bc21492e6a033faf731748b8f. --- codebuild/spec/buildspec_musl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebuild/spec/buildspec_musl.yml b/codebuild/spec/buildspec_musl.yml index 72d6e54fe5..f81d4143c8 100644 --- a/codebuild/spec/buildspec_musl.yml +++ b/codebuild/spec/buildspec_musl.yml @@ -23,7 +23,7 @@ phases: on-failure: ABORT commands: # Install musl libc - - git clone --depth=1 https://git.musl-libc.org/git/musl $MUSL_DIR + - git clone https://git.musl-libc.org/git/musl $MUSL_DIR - echo "Installing musl to $CODEBUILD_SRC_DIR/$MUSL_DIR" - cd $MUSL_DIR - ./configure --prefix=$CODEBUILD_SRC_DIR/$MUSL_DIR