Skip to content

Commit

Permalink
Add FIPS support to Hyper 1.0 Client (smithy-lang#3539)
Browse files Browse the repository at this point in the history
## Description
This does several things:
1. Upgrade to RusTLS 0.23 which enables FIPS support
2. Add smoke test of the clients. This revealed a bug where https URLs
were not supported.

This is technically a breaking change because I added `non_exhaustive`
to the CryptoMode enum.

<!--- Describe your changes in detail -->

## Testing
New integration tests. I expect this to fail in CI since I'll need to
update the build image to match.

## Checklist
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the
smithy-rs codegen or runtime crates
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the AWS
SDK, generated SDK code, or SDK runtime crates

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
  • Loading branch information
rcoh authored Apr 2, 2024
1 parent d37ac94 commit 09ba40e
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 17 deletions.
1 change: 1 addition & 0 deletions .cargo-deny-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ confidence-threshold = 1.0
exceptions = [
{ allow = ["OpenSSL"], name = "ring", version = "*" },
{ allow = ["OpenSSL"], name = "aws-lc-sys", version = "*" },
{ allow = ["OpenSSL"], name = "aws-lc-fips-sys", version = "*" },
]

[[licenses.clarify]]
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,24 +232,24 @@ jobs:
- target: i686-unknown-linux-gnu
build_smithy_rs_features: --all-features
build_aws_exclude: ''
build_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript
build_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript --exclude aws-smithy-experimental
test_smithy_rs_features: --all-features
test_aws_exclude: ''
test_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript
test_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript --exclude aws-smithy-experimental
- target: powerpc-unknown-linux-gnu
build_smithy_rs_features: ''
build_aws_exclude: --exclude aws-inlineable
build_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript
build_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript --exclude aws-smithy-experimental
test_smithy_rs_features: ''
test_aws_exclude: --exclude aws-inlineable
test_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript
- target: powerpc64-unknown-linux-gnu
build_smithy_rs_features: ''
build_aws_exclude: --exclude aws-inlineable
build_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript
build_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript --exclude aws-smithy-experimental
test_smithy_rs_features: ''
test_aws_exclude: --exclude aws-inlineable
test_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript
test_smithy_rs_exclude: --exclude aws-smithy-http-server-python --exclude aws-smithy-http-server-typescript --exclude aws-smithy-experimental
env:
CROSS_CONFIG: Cross.toml
steps:
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,25 @@ message = "Users may now set service-specific configuration in the environment.
references = ["smithy-rs#3493"]
meta = { "breaking" = false, "tada" = true, "bug" = false }
author = "Velfi"

[[smithy-rs]]
message = "Fix bug in Hyper 1.0 support where https URLs returned an error"
references = ["smithy-rs#3539"]
meta = { "breaking" = false, "tada" = false, "bug" = false }
author = "rcoh"

[[smithy-rs]]
message = """Add FIPS support to our Hyper 1.0-based client. Customers can enable this mode by enabling the `crypto-aws-lc-fips` on `aws-smithy-experimental`. To construct a client using the new client, consult this [example](https://github.com/awslabs/aws-sdk-rust/blob/release-2024-03-29/sdk/s3/tests/hyper-10.rs).
Please note that support for Hyper 1.0 remains experimental."""
references = ["smithy-rs#3539"]
meta = { "breaking" = false, "tada" = true, "bug" = false }
author = "rcoh"

[[aws-sdk-rust]]
message = """Add FIPS support to our Hyper 1.0-based client. Customers can enable this mode by enabling the `crypto-aws-lc-fips` on `aws-smithy-experimental`. To construct a client using the new client, consult this [example](https://github.com/awslabs/aws-sdk-rust/blob/release-2024-03-29/sdk/s3/tests/hyper-10.rs).
Please note that support for Hyper 1.0 remains experimental."""
references = ["smithy-rs#3539"]
meta = { "breaking" = false, "tada" = true, "bug" = false }
author = "rcoh"
12 changes: 6 additions & 6 deletions rust-runtime/aws-smithy-experimental/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aws-smithy-experimental"
version = "0.1.0"
version = "0.1.1"
authors = ["AWS Rust SDK Team <[email protected]>"]
description = "Experiments for the smithy-rs ecosystem"
edition = "2021"
Expand All @@ -9,7 +9,8 @@ repository = "https://github.com/smithy-lang/smithy-rs"

[features]
crypto-ring = ["rustls/ring"]
crypto-aws-lc = ["rustls/aws_lc_rs", "dep:fs_extra"]
crypto-aws-lc = ["rustls/aws_lc_rs"]
crypto-aws-lc-fips = ["rustls/fips"]

[dependencies]
aws-smithy-types = { path = "../aws-smithy-types", features = ["http-body-1-x"] }
Expand All @@ -20,13 +21,12 @@ pin-project-lite = "0.2.13"
hyper-util = "0.1.3"
http = "1"
tokio = "1"
hyper-rustls = { version = "0.26", features = ["http2", "http1"] }
rustls = { version = "0.22.2", default-features = false }
hyper-rustls = { version = "0.27", features = ["http2", "http1", "native-tokio", "tls12"], default-features = false }
rustls = { version = "0.23", default-features = false }
h2 = "0.4"
once_cell = "1.18.0"
tracing = "0.1.40"
tower = "0.4.1"
fs_extra = { version = "1.3.0", optional = true } # hack for cargo-minimal-versions

[dev-dependencies]
aws-smithy-async = { path = "../aws-smithy-async", features = ["rt-tokio", "test-util"] }
Expand All @@ -40,7 +40,7 @@ doc-scrape-examples = true

[[example]]
name = "client-aws-lc"
required-features = ["crypto-aws-lc"]
required-features = ["crypto-aws-lc", "crypto-aws-lc-fips"]
doc-scrape-examples = true

[[example]]
Expand Down
10 changes: 6 additions & 4 deletions rust-runtime/aws-smithy-experimental/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
Staging ground for experimental new features in the smithy-rs ecosystem.

### Hyper 1.0 Support
This crate adds support for Hyper 1.0 (see [examples](./examples)). There a few blockers before stablization:
1. Moving to `rustls` 0.23 to take advantage of FIPS support. This is blocked on `hyper-rustls` being upgraded.
2. Expose an API for providing a custom connector. Currently that API is not exposed because a shim layer is needed to avoid taking a hard dependency on hyper-util.
3. Add support for poisoning connections in the connection pool. This API needs to be either backported into hyper-util or we need to establish our own client.
This crate allows customers to use Hyper 1.0. A valuable consequence of this is access to aws-lc-rs and its `FIPS` compliant crypto. This is available behind the `crypto-aws-lc-fips` feature. **Note**: FIPS support has somewhat [complex build requirements](https://github.com/aws/aws-lc/blob/main/BUILDING.md), namely CMake and Go.

## Crate Stabilization

This crate adds support for Hyper 1.0 (see [examples](./examples)). There a few blockers before stablization:
1. Expose an API for providing a custom connector. Currently, that API is not exposed because a shim layer is needed to avoid taking a hard dependency on hyper-util.
2. Add support for poisoning connections in the connection pool. This API needs to be either backported into hyper-util or we need to establish our own client.

<!-- anchor_start:footer -->
This crate is part of the [AWS SDK for Rust](https://awslabs.github.io/aws-sdk-rust/) and the [smithy-rs](https://github.com/smithy-lang/smithy-rs) code generator.
Expand Down
10 changes: 9 additions & 1 deletion rust-runtime/aws-smithy-experimental/examples/client-aws-lc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@
*/

use aws_smithy_experimental::hyper_1_0::{CryptoMode, HyperClientBuilder};
#[tokio::main]

fn main() {
async fn main() {
// feature = crypto-aws-lc
let _client = HyperClientBuilder::new()
.crypto_mode(CryptoMode::AwsLc)
.build_https();

// feature = crypto-aws-lc-fips
// A FIPS client can also be created. Note that this has a more complex build environment required.
let _client = HyperClientBuilder::new()
.crypto_mode(CryptoMode::AwsLcFips)
.build_https();
}
26 changes: 25 additions & 1 deletion rust-runtime/aws-smithy-experimental/src/hyper_1_0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@ use aws_smithy_types::retry::ErrorKind;

use crate::hyper_1_0::timeout_middleware::{ConnectTimeout, HttpTimeoutError};
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
#[non_exhaustive]
pub enum CryptoMode {
#[cfg(feature = "crypto-ring")]
Ring,
#[cfg(feature = "crypto-aws-lc")]
AwsLc,
#[cfg(feature = "crypto-aws-lc-fips")]
AwsLcFips,
}

impl CryptoMode {
Expand All @@ -60,6 +63,16 @@ impl CryptoMode {

#[cfg(feature = "crypto-ring")]
CryptoMode::Ring => rustls::crypto::ring::default_provider(),

#[cfg(feature = "crypto-aws-lc-fips")]
CryptoMode::AwsLcFips => {
let provider = rustls::crypto::default_fips_provider();
assert!(
provider.fips(),
"FIPS was requested but the provider did not support FIPS"
);
provider
}
}
}
}
Expand Down Expand Up @@ -113,12 +126,21 @@ mod cached_connectors {
HttpsConnector<HttpConnector>,
> = once_cell::sync::Lazy::new(|| make_tls(GaiResolver::new(), CryptoMode::AwsLc.provider()));

#[cfg(feature = "crypto-aws-lc-fips")]
pub(crate) static HTTPS_NATIVE_ROOTS_AWS_LC_FIPS: once_cell::sync::Lazy<
HttpsConnector<HttpConnector>,
> = once_cell::sync::Lazy::new(|| {
make_tls(GaiResolver::new(), CryptoMode::AwsLcFips.provider())
});

pub(super) fn cached_https(mode: Inner) -> hyper_rustls::HttpsConnector<HttpConnector> {
match mode {
#[cfg(feature = "crypto-ring")]
Inner::Standard(CryptoMode::Ring) => HTTPS_NATIVE_ROOTS_RING.clone(),
#[cfg(feature = "crypto-aws-lc")]
Inner::Standard(CryptoMode::AwsLc) => HTTPS_NATIVE_ROOTS_AWS_LC.clone(),
#[cfg(feature = "crypto-aws-lc-fips")]
Inner::Standard(CryptoMode::AwsLcFips) => HTTPS_NATIVE_ROOTS_AWS_LC_FIPS.clone(),
#[allow(unreachable_patterns)]
Inner::Standard(_) => unreachable!("unexpected mode"),
Inner::Custom(provider) => make_tls(GaiResolver::new(), provider),
Expand Down Expand Up @@ -169,6 +191,8 @@ mod build_connector {
crypto_provider: CryptoProvider,
) -> HttpsConnector<HttpConnector<R>> {
use hyper_rustls::ConfigBuilderExt;
let mut base_connector = HttpConnector::new_with_resolver(resolver);
base_connector.enforce_http(false);
hyper_rustls::HttpsConnectorBuilder::new()
.with_tls_config(
rustls::ClientConfig::builder_with_provider(Arc::new(restrict_ciphers(crypto_provider)))
Expand All @@ -180,7 +204,7 @@ mod build_connector {
.https_or_http()
.enable_http1()
.enable_http2()
.wrap_connector(HttpConnector::new_with_resolver(resolver))
.wrap_connector(base_connector)
}

pub(super) fn https_with_resolver<R: ResolveDns>(
Expand Down
91 changes: 91 additions & 0 deletions rust-runtime/aws-smithy-experimental/tests/smoke_test_clients.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use aws_smithy_async::time::SystemTimeSource;
use aws_smithy_experimental::hyper_1_0::{CryptoMode, HyperClientBuilder};
use aws_smithy_runtime_api::client::dns::{DnsFuture, ResolveDns, ResolveDnsError};
use aws_smithy_runtime_api::client::http::{HttpClient, HttpConnector, HttpConnectorSettings};
use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
use hyper_util::client::legacy::connect::dns::{GaiResolver, Name};
use std::error::Error;
use std::str::FromStr;
use std::sync::Arc;
use tower::Service;

#[cfg(feature = "crypto-ring")]
#[tokio::test]
async fn ring_client() {
let client = HyperClientBuilder::new()
.crypto_mode(CryptoMode::Ring)
.build_https();
smoke_test_client(&client).await.unwrap();
}

#[cfg(feature = "crypto-aws-lc-fips")]
#[tokio::test]
async fn aws_lc_fips_client() {
let client = HyperClientBuilder::new()
.crypto_mode(CryptoMode::AwsLcFips)
.build_https();
smoke_test_client(&client).await.unwrap();
}

#[cfg(feature = "crypto-aws-lc")]
#[tokio::test]
async fn aws_lc_client() {
let client = HyperClientBuilder::new()
.crypto_mode(CryptoMode::AwsLc)
.build_https();
smoke_test_client(&client).await.unwrap();
}

#[cfg(feature = "crypto-ring")]
#[tokio::test]
async fn custom_dns_client() {
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone)]
struct PassThroughResolver {
inner: GaiResolver,
count: Arc<AtomicUsize>,
}
impl ResolveDns for PassThroughResolver {
fn resolve_dns<'a>(&'a self, _name: &'a str) -> DnsFuture<'a> {
let mut inner = self.inner.clone();
let name = Name::from_str(_name).unwrap();
let count = self.count.clone();
DnsFuture::new(async move {
count.fetch_add(1, Ordering::Relaxed);
let result = inner
.call(name)
.await
.map_err(|err| ResolveDnsError::new(err))?;
Ok(result.map(|addr| addr.ip()).collect::<Vec<_>>())
})
}
}
let resolver = PassThroughResolver {
inner: GaiResolver::new(),
count: Default::default(),
};
let client = HyperClientBuilder::new()
.crypto_mode(CryptoMode::Ring)
.build_with_resolver(resolver.clone());
smoke_test_client(&client).await.unwrap();
assert_eq!(resolver.count.load(Ordering::Relaxed), 1);
}

async fn smoke_test_client(client: &dyn HttpClient) -> Result<(), Box<dyn Error>> {
let connector_settings = HttpConnectorSettings::builder().build();
let runtime_components = RuntimeComponentsBuilder::for_tests()
.with_time_source(Some(SystemTimeSource::new()))
.build()
.unwrap();
let connector = client.http_connector(&connector_settings, &runtime_components);
let _response = connector
.call(HttpRequest::get("https://amazon.com").unwrap())
.await?;
Ok(())
}
1 change: 1 addition & 0 deletions tools/ci-build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ RUN set -eux; \
gcc \
git \
glibc-langpack-en \
go \
java-11-amazon-corretto-headless \
make \
openssl-devel \
Expand Down
2 changes: 2 additions & 0 deletions tools/ci-scripts/test-windows.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set -eu -o pipefail

exclusions=("--exclude" "aws-smithy-http-server-python" "--exclude" "aws-smithy-http-server-typescript" "--exclude" "aws-smithy-experimental")
for runtime_path in "rust-runtime" "aws/rust-runtime"; do
echo "testing $runtime_path"
pushd "${runtime_path}" &>/dev/null
# aws-smithy-http-server-python cannot be compiled on Windows since it uses the `signal-hook` crate
# which is not really yet fully supported on the platform.
Expand All @@ -18,4 +19,5 @@ for runtime_path in "rust-runtime" "aws/rust-runtime"; do
done
# TODO(https://github.com/awslabs/aws-sdk-rust/issues/1117) We don't have a way to codegen the deps needed by the aws-config crate
# (cd aws/rust-runtime/aws-config && cargo test --all-features) # aws-config is not part of the workspace so we have to test it separately
echo "Testing isolated features of aws-smithy-experimental"
(cd rust-runtime && cargo test -p aws-smithy-experimental --features crypto-ring) # only ring works on windows

0 comments on commit 09ba40e

Please sign in to comment.