diff --git a/COPYRIGHT b/COPYRIGHT index 17ae94476..522b0c779 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -14,12 +14,6 @@ operating system images. =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= -Contains modified hyper-proxy files [mod.rs, stream.rs, tunnel.rs] from -https://github.com/tafia/hyper-proxy 2021-09-20. -Copyright (c) 2017 Johann Tuffe. Licensed under the MIT License. - -=^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= - -Contains aws-smithy-experimental from +Contains modified aws-smithy-experimental file(s) [hyper_1_0.rs] from https://github.com/smithy-lang/smithy-rs/tree/release-2024-10-09. Licensed under the Apache-2.0 License. diff --git a/sources/Cargo.lock b/sources/Cargo.lock index ca19e7eed..8549b6838 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -886,8 +886,10 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "h2 0.4.6", + "headers", "http 1.1.0", "hyper 1.4.1", + "hyper-http-proxy", "hyper-rustls 0.27.3", "hyper-util", "once_cell", @@ -896,6 +898,7 @@ dependencies = [ "tokio", "tower", "tracing", + "url", ] [[package]] @@ -2543,14 +2546,14 @@ checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "headers" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http 0.2.12", + "http 1.1.0", "httpdate", "mime", "sha1", @@ -2558,11 +2561,11 @@ dependencies = [ [[package]] name = "headers-core" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 0.2.12", + "http 1.1.0", ] [[package]] @@ -2763,6 +2766,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-http-proxy" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d06dbdfbacf34d996c6fb540a71a684a7aae9056c71951163af8a8a4c07b9a4" +dependencies = [ + "bytes", + "futures-util", + "headers", + "http 1.1.0", + "hyper 1.4.1", + "hyper-rustls 0.27.3", + "hyper-util", + "pin-project-lite", + "rustls-native-certs 0.7.3", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -3651,23 +3674,15 @@ dependencies = [ "aws-sdk-ec2", "aws-sdk-eks", "aws-smithy-experimental", - "aws-smithy-runtime", "aws-smithy-types", "aws-types", "base64 0.22.1", "bottlerocket-modeled-types", "bottlerocket-settings-models", - "bytes", "constants", - "futures-util", "generate-readme", - "headers", - "http 0.2.12", "httptest", - "hyper 0.14.30", - "hyper-rustls 0.24.2", "imdsclient", - "log", "rustls 0.23.14", "serde", "serde_json", @@ -3675,8 +3690,6 @@ dependencies = [ "tempfile", "tokio", "tokio-retry", - "tokio-rustls 0.24.1", - "url", ] [[package]] @@ -4098,6 +4111,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-native-certs" version = "0.8.0" @@ -5171,9 +5197,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777d57dcc6bb4cf084e3212e1858447222aa451f21b5e2452497d9100da65b91" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -5190,9 +5216,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", diff --git a/sources/Cargo.toml b/sources/Cargo.toml index 7e4ef0b5f..df7139157 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -143,15 +143,15 @@ glob = "0.3" gptman = { version = "1", default-features = false } handlebars = "4" h2 = "0.4" -headers = "0.3" +headers = "0.4" hex-literal = "0.4" http = "0.2" httparse = "1" httptest = "0.15" -hyper = { version = "0.14", default-features = false } -hyper-util = "0.1" -# FIXME: bump to 0.27 once hyper-proxy is dropped -hyper-rustls = { version = "0.24", default-features = false } +hyper = { version = "1", default-features = false } +hyper-http-proxy = "1" +hyper-rustls = { version = "0.27", default-features = false } +hyper-util = { version = "0.1", default-features = false } hyper-unix-connector = "0.2" indexmap = "2" ipnet = "2" @@ -194,9 +194,8 @@ syn = { version = "2", default-features = false } tar = { version = "0.4", default-features = false } tempfile = "3" test-case = "3" -tokio = { version = "~1.32", default-features = false } # LTS +tokio = { version = "~1.36", default-features = false } # LTS tokio-retry = "0.3" -tokio-rustls = "0.24" tokio-test = "0.4" tokio-tungstenite = { version = "0.20", default-features = false } tokio-util = "0.7" diff --git a/sources/api/apiclient/Cargo.toml b/sources/api/apiclient/Cargo.toml index 6dd71a59a..a99df571b 100644 --- a/sources/api/apiclient/Cargo.toml +++ b/sources/api/apiclient/Cargo.toml @@ -23,7 +23,8 @@ futures.workspace = true futures-channel.workspace = true http.workspace = true httparse.workspace = true -hyper = { workspace = true, features = ["client", "http1", "http2", "tcp"] } +# apiclient exec requires an older version of hyper +hyper = { version = "0.14", features = ["client", "http1", "http2", "tcp"] } hyper-unix-connector.workspace = true libc.workspace = true log.workspace = true diff --git a/sources/api/pluto/Cargo.toml b/sources/api/pluto/Cargo.toml index 0f5d6e839..63d8c997d 100644 --- a/sources/api/pluto/Cargo.toml +++ b/sources/api/pluto/Cargo.toml @@ -16,25 +16,18 @@ fips = ["aws-lc-rs/fips", "aws-smithy-experimental/crypto-aws-lc-fips", "rustls/ source-groups = ["aws-smithy-experimental"] [dependencies] -base64.workspace = true -bottlerocket-modeled-types.workspace = true -bottlerocket-settings-models.workspace = true -bytes.workspace = true -constants.workspace = true -futures-util.workspace = true -headers.workspace = true -http.workspace = true -hyper = { workspace = true, features = ["default"] } -hyper-rustls = { workspace = true, features = ["http2", "logging", "native-tokio", "tls12"] } -imdsclient.workspace = true aws-config.workspace = true aws-lc-rs = { workspace = true, features = ["bindgen"] } aws-sdk-eks.workspace = true aws-sdk-ec2.workspace = true -aws-types.workspace = true +aws-smithy-experimental = {workspace = true, features = ["crypto-aws-lc"]} aws-smithy-types.workspace = true -aws-smithy-runtime.workspace = true -aws-smithy-experimental = { workspace = true, features = ["crypto-aws-lc"] } +aws-types.workspace = true +base64.workspace = true +bottlerocket-modeled-types.workspace = true +bottlerocket-settings-models.workspace = true +constants.workspace = true +imdsclient.workspace = true rustls.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true @@ -42,9 +35,6 @@ snafu.workspace = true tempfile.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tokio-retry.workspace = true -tokio-rustls.workspace = true -url.workspace = true -log.workspace = true [build-dependencies] generate-readme.workspace = true diff --git a/sources/api/pluto/src/ec2.rs b/sources/api/pluto/src/ec2.rs index 5f3d1d22e..31baee70e 100644 --- a/sources/api/pluto/src/ec2.rs +++ b/sources/api/pluto/src/ec2.rs @@ -1,9 +1,6 @@ use crate::aws::sdk_config; -use crate::proxy; -#[cfg(feature = "fips")] -use aws_smithy_experimental::hyper_1_0::{CryptoMode, HyperClientBuilder as Hyper10ClientBuilder}; -#[cfg(not(feature = "fips"))] -use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; +use crate::PROVIDER; +use aws_smithy_experimental::hyper_1_0::HyperClientBuilder; use aws_smithy_types::error::display::DisplayErrorContext; use snafu::{OptionExt, ResultExt, Snafu}; use std::time::Duration; @@ -36,9 +33,6 @@ pub(super) enum Error { #[snafu(display("Missing field '{}' in EC2 response", field))] Missing { field: &'static str }, - - #[snafu(context(false), display("{}", source))] - Proxy { source: proxy::Error }, } type Result = std::result::Result; @@ -55,13 +49,8 @@ where { let config = sdk_config(region).await; - #[cfg(not(feature = "fips"))] let client = build_client(https_proxy, no_proxy, config)?; - // FIXME!: support proxies in FIPS mode - #[cfg(feature = "fips")] - let client = build_client(config)?; - tokio::time::timeout( FETCH_PRIVATE_DNS_NAME_TIMEOUT, Retry::spawn( @@ -94,7 +83,6 @@ where .context(FetchPrivateDnsNameTimeoutSnafu)? } -#[cfg(not(feature = "fips"))] fn build_client( https_proxy: Option, no_proxy: Option<&[N]>, @@ -104,26 +92,16 @@ where H: AsRef, N: AsRef, { - let client = if let Some(https_proxy) = https_proxy { - let http_connector = proxy::setup_http_client(https_proxy, no_proxy)?; - let http_client = HyperClientBuilder::new().build(http_connector); - let ec2_config = aws_sdk_ec2::config::Builder::from(&config) - .http_client(http_client) - .build(); - aws_sdk_ec2::Client::from_conf(ec2_config) + let http_client = if let Some(https_proxy) = https_proxy { + let https_proxy = https_proxy.as_ref().to_string(); + HyperClientBuilder::new() + .crypto_mode(PROVIDER) + .build_with_proxy(https_proxy, no_proxy) } else { - aws_sdk_ec2::Client::new(&config) + HyperClientBuilder::new() + .crypto_mode(PROVIDER) + .build_https() }; - - Ok(client) -} - -// FIXME!: support proxies in FIPS mode -#[cfg(feature = "fips")] -fn build_client(config: aws_config::SdkConfig) -> Result { - let http_client = Hyper10ClientBuilder::new() - .crypto_mode(CryptoMode::AwsLcFips) - .build_https(); let ec2_config = aws_sdk_ec2::config::Builder::from(&config) .http_client(http_client) .build(); diff --git a/sources/api/pluto/src/eks.rs b/sources/api/pluto/src/eks.rs index 1e6e786dc..547ef28d9 100644 --- a/sources/api/pluto/src/eks.rs +++ b/sources/api/pluto/src/eks.rs @@ -1,10 +1,7 @@ use crate::aws::sdk_config; -use crate::proxy; +use crate::PROVIDER; use aws_sdk_eks::types::KubernetesNetworkConfigResponse; -#[cfg(feature = "fips")] -use aws_smithy_experimental::hyper_1_0::{CryptoMode, HyperClientBuilder as Hyper10ClientBuilder}; -#[cfg(not(feature = "fips"))] -use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; +use aws_smithy_experimental::hyper_1_0::HyperClientBuilder; use snafu::{OptionExt, ResultExt, Snafu}; use std::time::Duration; @@ -27,9 +24,6 @@ pub(super) enum Error { #[snafu(display("Missing field '{}' in EKS response", field))] Missing { field: &'static str }, - - #[snafu(context(false), display("{}", source))] - Proxy { source: proxy::Error }, } type Result = std::result::Result; @@ -48,13 +42,8 @@ where { let config = sdk_config(region).await; - #[cfg(not(feature = "fips"))] let client = build_client(https_proxy, no_proxy, config)?; - // FIXME!: support proxies in FIPS mode - #[cfg(feature = "fips")] - let client = build_client(config)?; - tokio::time::timeout( EKS_DESCRIBE_CLUSTER_TIMEOUT, client.describe_cluster().name(cluster.to_owned()).send(), @@ -70,7 +59,6 @@ where }) } -#[cfg(not(feature = "fips"))] fn build_client( https_proxy: Option, no_proxy: Option<&[N]>, @@ -80,26 +68,16 @@ where H: AsRef, N: AsRef, { - let client = if let Some(https_proxy) = https_proxy { - let http_connector = proxy::setup_http_client(https_proxy, no_proxy)?; - let http_client = HyperClientBuilder::new().build(http_connector); - let eks_config = aws_sdk_eks::config::Builder::from(&config) - .http_client(http_client) - .build(); - aws_sdk_eks::Client::from_conf(eks_config) + let http_client = if let Some(https_proxy) = https_proxy { + let https_proxy = https_proxy.as_ref().to_string(); + HyperClientBuilder::new() + .crypto_mode(PROVIDER) + .build_with_proxy(https_proxy, no_proxy) } else { - aws_sdk_eks::Client::new(&config) + HyperClientBuilder::new() + .crypto_mode(PROVIDER) + .build_https() }; - - Ok(client) -} - -// FIXME!: support proxies in FIPS mode -#[cfg(feature = "fips")] -fn build_client(config: aws_config::SdkConfig) -> Result { - let http_client = Hyper10ClientBuilder::new() - .crypto_mode(CryptoMode::AwsLcFips) - .build_https(); let eks_config = aws_sdk_eks::config::Builder::from(&config) .http_client(http_client) .build(); diff --git a/sources/api/pluto/src/hyper_proxy/LICENSE-MIT.md b/sources/api/pluto/src/hyper_proxy/LICENSE-MIT.md deleted file mode 100644 index 47d7815df..000000000 --- a/sources/api/pluto/src/hyper_proxy/LICENSE-MIT.md +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017 Johann Tuffe - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/sources/api/pluto/src/hyper_proxy/mod.rs b/sources/api/pluto/src/hyper_proxy/mod.rs deleted file mode 100644 index 4cd3fb882..000000000 --- a/sources/api/pluto/src/hyper_proxy/mod.rs +++ /dev/null @@ -1,302 +0,0 @@ -//! A Proxy Connector crate for Hyper based applications - -// Original Copyright 2017 Johann Tuffe. Licensed under the MIT License. -// Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - -mod stream; -mod tunnel; -use futures_util::future::TryFutureExt; -use headers::{authorization::Credentials, Authorization, HeaderMapExt, ProxyAuthorization}; -use http::header::HeaderMap; -use hyper::{service::Service, Uri}; -use std::{fmt, io, sync::Arc}; -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; - -pub use stream::ProxyStream; -use tokio::io::{AsyncRead, AsyncWrite}; - -use hyper_rustls::ConfigBuilderExt; -use tokio_rustls::rustls::{ClientConfig, ServerName}; -use tokio_rustls::TlsConnector; - -pub(crate) type BoxError = Box; - -/// The Intercept enum to filter connections -#[derive(Debug, Clone)] -#[allow(dead_code)] -pub enum Intercept { - /// Only https connections will go through proxy - Https, - /// No connection will go through this proxy - None, - /// A custom intercept - Custom(Custom), -} - -/// A trait for matching between Destination and Uri -pub trait Dst { - /// Returns the connection scheme, e.g. "http" or "https" - fn scheme(&self) -> Option<&str>; - /// Returns the host of the connection - fn host(&self) -> Option<&str>; - /// Returns the port for the connection - fn port(&self) -> Option; -} - -impl Dst for Uri { - fn scheme(&self) -> Option<&str> { - self.scheme_str() - } - - fn host(&self) -> Option<&str> { - self.host() - } - - fn port(&self) -> Option { - self.port_u16() - } -} - -#[inline] -pub(crate) fn io_err>>(e: E) -> io::Error { - io::Error::new(io::ErrorKind::Other, e) -} - -/// A Custom struct to proxy custom uris -#[derive(Clone)] -#[allow(clippy::type_complexity)] -pub struct Custom(Arc, Option<&str>, Option) -> bool + Send + Sync>); - -impl fmt::Debug for Custom { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "_") - } -} - -impl, Option<&str>, Option) -> bool + Send + Sync + 'static> From - for Custom -{ - fn from(f: F) -> Custom { - Custom(Arc::new(f)) - } -} - -impl Intercept { - /// A function to check if given `Uri` is proxied - pub fn matches(&self, uri: &D) -> bool { - match (self, uri.scheme()) { - (&Intercept::Https, Some("https")) => true, - (&Intercept::Custom(Custom(ref f)), _) => f(uri.scheme(), uri.host(), uri.port()), - _ => false, - } - } -} - -impl, Option<&str>, Option) -> bool + Send + Sync + 'static> From - for Intercept -{ - fn from(f: F) -> Intercept { - Intercept::Custom(f.into()) - } -} - -/// A Proxy struct -#[derive(Clone, Debug)] -pub struct Proxy { - intercept: Intercept, - force_connect: bool, - headers: HeaderMap, - uri: Uri, -} - -impl Proxy { - /// Create a new `Proxy` - pub fn new>(intercept: I, uri: Uri) -> Proxy { - Proxy { - intercept: intercept.into(), - uri, - headers: HeaderMap::new(), - force_connect: false, - } - } - - /// Set `Proxy` authorization - pub fn set_authorization(&mut self, credentials: Authorization) { - // In pluto, we use custom intercept for HTTPS traffic we might proxy based on the no proxy specification. - match self.intercept { - Intercept::Custom(_) | Intercept::Https => { - self.headers.typed_insert(ProxyAuthorization(credentials.0)); - } - _ => {} - } - } -} - -/// A wrapper around `Proxy`s with a connector. -#[derive(Clone)] -pub struct ProxyConnector { - proxies: Vec, - connector: C, - tls: Option, -} - -impl fmt::Debug for ProxyConnector { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!( - f, - "ProxyConnector {}{{ proxies: {:?}, connector: {:?} }}", - if self.tls.is_some() { - "" - } else { - "(unsecured)" - }, - self.proxies, - self.connector - ) - } -} - -impl ProxyConnector { - /// Create a new secured Proxies - pub fn new(connector: C) -> Result { - let config = ClientConfig::builder() - .with_safe_defaults() - .with_native_roots() - .with_no_client_auth(); - - let cfg = Arc::new(config); - let tls = TlsConnector::from(cfg); - - Ok(ProxyConnector { - proxies: Vec::new(), - connector, - tls: Some(tls), - }) - } - - /// Create a proxy connector and attach a particular proxy - pub fn from_proxy(connector: C, proxy: Proxy) -> Result { - let mut c = ProxyConnector::new(connector)?; - c.proxies.push(proxy); - Ok(c) - } - - fn match_proxy(&self, uri: &D) -> Option<&Proxy> { - self.proxies.iter().find(|p| p.intercept.matches(uri)) - } -} - -macro_rules! mtry { - ($e:expr) => { - match $e { - Ok(v) => v, - Err(e) => break Err(e.into()), - } - }; -} - -impl Service for ProxyConnector -where - C: Service, - C::Response: AsyncRead + AsyncWrite + Send + Unpin + 'static, - C::Future: Send + 'static, - C::Error: Into, -{ - type Response = ProxyStream; - type Error = io::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - match self.connector.poll_ready(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(e)) => Poll::Ready(Err(io_err(e.into()))), - Poll::Pending => Poll::Pending, - } - } - - fn call(&mut self, uri: Uri) -> Self::Future { - if let (Some(p), Some(host)) = (self.match_proxy(&uri), uri.host()) { - if uri.scheme() == Some(&http::uri::Scheme::HTTPS) || p.force_connect { - let host = host.to_owned(); - let port = - uri.port_u16() - .unwrap_or(if uri.scheme() == Some(&http::uri::Scheme::HTTP) { - 80 - } else { - 443 - }); - let tunnel = tunnel::new(&host, port, &p.headers); - let connection = - proxy_dst(&uri, &p.uri).map(|proxy_url| self.connector.call(proxy_url)); - let tls = if uri.scheme() == Some(&http::uri::Scheme::HTTPS) { - self.tls.clone() - } else { - None - }; - - Box::pin(async move { - #[allow(clippy::never_loop)] - loop { - // this hack will gone once `try_blocks` will eventually stabilized - let proxy_stream = mtry!(mtry!(connection).await.map_err(io_err)); - let tunnel_stream = mtry!(tunnel.with_stream(proxy_stream).await); - - break match tls { - Some(tls) => { - let server_name: ServerName = - mtry!(host.as_str().try_into().map_err(io_err)); - let secure_stream = mtry!(tls - .connect(server_name, tunnel_stream) - .await - .map_err(io_err)); - - Ok(ProxyStream::Secured(Box::new(secure_stream))) - } - - None => Ok(ProxyStream::Regular(tunnel_stream)), - }; - } - }) - } else { - match proxy_dst(&uri, &p.uri) { - Ok(proxy_uri) => Box::pin( - self.connector - .call(proxy_uri) - .map_ok(ProxyStream::Regular) - .map_err(|err| io_err(err.into())), - ), - Err(err) => Box::pin(futures_util::future::err(io_err(err))), - } - } - } else { - Box::pin( - self.connector - .call(uri) - .map_ok(ProxyStream::NoProxy) - .map_err(|err| io_err(err.into())), - ) - } - } -} - -fn proxy_dst(dst: &Uri, proxy: &Uri) -> io::Result { - Uri::builder() - .scheme( - proxy - .scheme_str() - .ok_or_else(|| io_err(format!("proxy uri missing scheme: {}", proxy)))?, - ) - .authority( - proxy - .authority() - .ok_or_else(|| io_err(format!("proxy uri missing host: {}", proxy)))? - .clone(), - ) - .path_and_query(dst.path_and_query().unwrap().clone()) - .build() - .map_err(|err| io_err(format!("other error: {}", err))) -} diff --git a/sources/api/pluto/src/hyper_proxy/stream.rs b/sources/api/pluto/src/hyper_proxy/stream.rs deleted file mode 100644 index b762eaedd..000000000 --- a/sources/api/pluto/src/hyper_proxy/stream.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Original Copyright 2017 Johann Tuffe. Licensed under the MIT License. -// Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; - -use tokio_rustls::client::TlsStream as RustlsStream; - -use hyper::client::connect::{Connected, Connection}; - -pub type TlsStream = RustlsStream; - -/// A Proxy Stream wrapper -pub enum ProxyStream { - NoProxy(R), - Regular(R), - Secured(Box>), -} - -macro_rules! match_fn_pinned { - ($self:expr, $fn:ident, $ctx:expr, $buf:expr) => { - match $self.get_mut() { - ProxyStream::NoProxy(s) => Pin::new(s).$fn($ctx, $buf), - ProxyStream::Regular(s) => Pin::new(s).$fn($ctx, $buf), - ProxyStream::Secured(s) => Pin::new(s).$fn($ctx, $buf), - } - }; - - ($self:expr, $fn:ident, $ctx:expr) => { - match $self.get_mut() { - ProxyStream::NoProxy(s) => Pin::new(s).$fn($ctx), - ProxyStream::Regular(s) => Pin::new(s).$fn($ctx), - ProxyStream::Secured(s) => Pin::new(s).$fn($ctx), - } - }; -} - -impl AsyncRead for ProxyStream { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - match_fn_pinned!(self, poll_read, cx, buf) - } -} - -impl AsyncWrite for ProxyStream { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - match_fn_pinned!(self, poll_write, cx, buf) - } - - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[io::IoSlice<'_>], - ) -> Poll> { - match_fn_pinned!(self, poll_write_vectored, cx, bufs) - } - - fn is_write_vectored(&self) -> bool { - match self { - ProxyStream::NoProxy(s) => s.is_write_vectored(), - ProxyStream::Regular(s) => s.is_write_vectored(), - ProxyStream::Secured(s) => s.is_write_vectored(), - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match_fn_pinned!(self, poll_flush, cx) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match_fn_pinned!(self, poll_shutdown, cx) - } -} - -impl Connection for ProxyStream { - fn connected(&self) -> Connected { - match self { - ProxyStream::NoProxy(s) => s.connected(), - - ProxyStream::Regular(s) => s.connected().proxy(true), - - ProxyStream::Secured(s) => s.get_ref().0.connected().proxy(true), - } - } -} diff --git a/sources/api/pluto/src/hyper_proxy/tunnel.rs b/sources/api/pluto/src/hyper_proxy/tunnel.rs deleted file mode 100644 index c2e53452a..000000000 --- a/sources/api/pluto/src/hyper_proxy/tunnel.rs +++ /dev/null @@ -1,220 +0,0 @@ -// Original Copyright 2017 Johann Tuffe. Licensed under the MIT License. -// Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - -use crate::hyper_proxy::io_err; -use bytes::{buf::Buf, BytesMut}; -use http::HeaderMap; -use std::fmt::{self, Display, Formatter}; -use std::future::Future; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; - -macro_rules! try_ready { - ($x:expr) => { - match $x { - core::task::Poll::Ready(Ok(x)) => x, - core::task::Poll::Ready(Err(e)) => return core::task::Poll::Ready(Err(e.into())), - core::task::Poll::Pending => return core::task::Poll::Pending, - } - }; -} - -pub(crate) struct TunnelConnect { - buf: BytesMut, -} - -impl TunnelConnect { - /// Change stream - pub fn with_stream(self, stream: S) -> Tunnel { - Tunnel { - buf: self.buf, - stream: Some(stream), - state: TunnelState::Writing, - } - } -} - -pub(crate) struct Tunnel { - buf: BytesMut, - stream: Option, - state: TunnelState, -} - -#[derive(Debug)] -enum TunnelState { - Writing, - Reading, -} - -struct HeadersDisplay<'a>(&'a HeaderMap); - -impl<'a> Display for HeadersDisplay<'a> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - for (key, value) in self.0 { - let value_str = value.to_str().map_err(|_| fmt::Error)?; - write!(f, "{}: {}\r\n", key.as_str(), value_str)?; - } - - Ok(()) - } -} - -/// Creates a new tunnel through proxy -pub(crate) fn new(host: &str, port: u16, headers: &HeaderMap) -> TunnelConnect { - let buf = format!( - "CONNECT {0}:{1} HTTP/1.1\r\n\ - Host: {0}:{1}\r\n\ - {2}\ - \r\n", - host, - port, - HeadersDisplay(headers) - ) - .into_bytes(); - - TunnelConnect { - buf: buf.as_slice().into(), - } -} - -impl Future for Tunnel { - type Output = Result; - - fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { - if self.stream.is_none() { - panic!("must not poll after future is complete") - } - - let this = self.get_mut(); - - loop { - if let TunnelState::Writing = &this.state { - let fut = this.stream.as_mut().unwrap().write_buf(&mut this.buf); - futures_util::pin_mut!(fut); - let n = try_ready!(fut.poll(ctx)); - - if !this.buf.has_remaining() { - this.state = TunnelState::Reading; - this.buf.truncate(0); - } else if n == 0 { - return Poll::Ready(Err(io_err("unexpected EOF while tunnel writing"))); - } - } else { - let fut = this.stream.as_mut().unwrap().read_buf(&mut this.buf); - futures_util::pin_mut!(fut); - let n = try_ready!(fut.poll(ctx)); - - if n == 0 { - return Poll::Ready(Err(io_err("unexpected EOF while tunnel reading"))); - } else { - let read = &this.buf[..]; - if read.len() > 12 { - if read.starts_with(b"HTTP/1.1 200") || read.starts_with(b"HTTP/1.0 200") { - if read.ends_with(b"\r\n\r\n") { - return Poll::Ready(Ok(this.stream.take().unwrap())); - } - // else read more - } else { - let len = read.len().min(16); - return Poll::Ready(Err(io_err(format!( - "unsuccessful tunnel ({})", - String::from_utf8_lossy(&read[0..len]) - )))); - } - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::{HeaderMap, Tunnel}; - use futures_util::future::TryFutureExt; - use std::io::{Read, Write}; - use std::net::TcpListener; - use std::thread; - use tokio::net::TcpStream; - use tokio::runtime::Runtime; - - fn tunnel(conn: S, host: String, port: u16) -> Tunnel { - super::new(&host, port, &HeaderMap::new()).with_stream(conn) - } - - #[rustfmt::skip] - macro_rules! mock_tunnel { - () => {{ - mock_tunnel!( - b"\ - HTTP/1.1 200 OK\r\n\ - \r\n\ - " - ) - }}; - ($write:expr) => {{ - let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - let addr = listener.local_addr().unwrap(); - let connect_expected = format!( - "\ - CONNECT {0}:{1} HTTP/1.1\r\n\ - Host: {0}:{1}\r\n\ - \r\n\ - ", - addr.ip(), - addr.port() - ).into_bytes(); - - thread::spawn(move || { - let (mut sock, _) = listener.accept().unwrap(); - let mut buf = [0u8; 4096]; - let n = sock.read(&mut buf).unwrap(); - assert_eq!(&buf[..n], &connect_expected[..]); - - sock.write_all($write).unwrap(); - }); - addr - }}; - } - - #[test] - fn test_tunnel() { - let addr = mock_tunnel!(); - - let core = Runtime::new().unwrap(); - let work = TcpStream::connect(&addr); - let host = addr.ip().to_string(); - let port = addr.port(); - let work = work.and_then(|tcp| tunnel(tcp, host, port)); - - core.block_on(work).unwrap(); - } - - #[test] - fn test_tunnel_eof() { - let addr = mock_tunnel!(b"HTTP/1.1 200 OK"); - - let core = Runtime::new().unwrap(); - let work = TcpStream::connect(&addr); - let host = addr.ip().to_string(); - let port = addr.port(); - let work = work.and_then(|tcp| tunnel(tcp, host, port)); - - core.block_on(work).unwrap_err(); - } - - #[test] - fn test_tunnel_bad_response() { - let addr = mock_tunnel!(b"foo bar baz hallo"); - - let core = Runtime::new().unwrap(); - let work = TcpStream::connect(&addr); - let host = addr.ip().to_string(); - let port = addr.port(); - let work = work.and_then(|tcp| tunnel(tcp, host, port)); - - core.block_on(work).unwrap_err(); - } -} diff --git a/sources/api/pluto/src/main.rs b/sources/api/pluto/src/main.rs index c0388e878..8b3a1ba76 100644 --- a/sources/api/pluto/src/main.rs +++ b/sources/api/pluto/src/main.rs @@ -35,10 +35,9 @@ mod api; mod aws; mod ec2; mod eks; -mod hyper_proxy; -mod proxy; use api::{settings_view_get, settings_view_set, SettingsViewDelta}; +use aws_smithy_experimental::hyper_1_0::CryptoMode; use base64::Engine; use bottlerocket_modeled_types::{KubernetesClusterDnsIp, KubernetesHostnameOverrideSource}; use imdsclient::ImdsClient; @@ -64,6 +63,12 @@ const AWS_CONFIG_FILE: &str = "config.pluto"; /// The environment variable that specifies the path to the AWS config file. const AWS_CONFIG_FILE_ENV_VAR: &str = "AWS_CONFIG_FILE"; +// Shared crypto provider for HyperClients +#[cfg(not(feature = "fips"))] +const PROVIDER: CryptoMode = CryptoMode::AwsLc; +#[cfg(feature = "fips")] +const PROVIDER: CryptoMode = CryptoMode::AwsLcFips; + mod error { use crate::{api, ec2, eks}; use snafu::Snafu; diff --git a/sources/api/pluto/src/proxy.rs b/sources/api/pluto/src/proxy.rs deleted file mode 100644 index a4e3f6d4e..000000000 --- a/sources/api/pluto/src/proxy.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::hyper_proxy::{Proxy, ProxyConnector}; -use headers::Authorization; -use hyper::client::HttpConnector; -use hyper::Uri; -use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; -use snafu::{ResultExt, Snafu}; -use url::Url; - -#[derive(Debug, Snafu)] -pub(super) enum Error { - #[snafu(display("Unable to parse '{}' as URI: {}", input, source))] - UriParse { - input: String, - source: hyper::http::uri::InvalidUri, - }, - - #[snafu(display("Unable to parse '{}' as URL: {}", input, source))] - UrlParse { - input: String, - source: url::ParseError, - }, - - #[snafu(display("Failed to create proxy creator: {}", source))] - ProxyConnector { source: std::io::Error }, -} - -type Result = std::result::Result; - -/// Setups a hyper-based HTTP client configured with a proxy connector. -pub(crate) fn setup_http_client( - https_proxy: H, - no_proxy: Option<&[N]>, -) -> Result>> -where - H: AsRef, - N: AsRef, -{ - // Determines whether a request of a given scheme, host and port should be proxied - // according to `https_proxy` and `no_proxy`. - - // The no-proxy intercept requires ownership of its input data. - let no_proxy: Option> = - no_proxy.map(|n| n.iter().map(|s| s.as_ref().to_owned()).collect()); - let intercept = move |scheme: Option<&str>, host: Option<&str>, _port| { - if let Some(host) = host { - if let Some(no_proxy) = &no_proxy { - if scheme != Some("https") { - return false; - } - if no_proxy.iter().any(|s| s == "*") { - // Don't proxy anything - return false; - } - // If the host matches one of the no proxy list entries, return false (don't proxy) - // Note that we're not doing anything fancy here for checking `no_proxy` since - // we only expect requests here to be going out to some AWS API endpoint. - return !no_proxy.iter().any(|no_proxy_host| { - !no_proxy_host.is_empty() && host.ends_with(no_proxy_host) - }); - } - true - } else { - false - } - }; - - let https_proxy = https_proxy.as_ref(); - let mut proxy_uri = https_proxy.parse::().context(UriParseSnafu { - input: https_proxy.to_owned(), - })?; - // If the proxy's URI doesn't have a scheme, assume HTTP for the scheme and let the proxy - // server forward HTTPS connections and start a tunnel. - if proxy_uri.scheme().is_none() { - proxy_uri = format!("http://{}", https_proxy) - .parse::() - .context(UriParseSnafu { - input: https_proxy.to_owned(), - })?; - } - let mut proxy = Proxy::new(intercept, proxy_uri); - // Parse https_proxy as URL to extract out auth information if any - let proxy_url = Url::parse(https_proxy).context(UrlParseSnafu { - input: https_proxy.to_owned(), - })?; - - if !proxy_url.username().is_empty() || proxy_url.password().is_some() { - proxy.set_authorization(Authorization::basic( - proxy_url.username(), - proxy_url.password().unwrap_or_default(), - )); - } - - let https_connector = HttpsConnectorBuilder::new() - .with_native_roots() - .https_or_http() - .enable_http2() - .build(); - let proxy_connector = - ProxyConnector::from_proxy(https_connector, proxy).context(ProxyConnectorSnafu)?; - Ok(proxy_connector) -} diff --git a/sources/aws-smithy-experimental/Cargo.toml b/sources/aws-smithy-experimental/Cargo.toml index b894a55fe..2d504606e 100644 --- a/sources/aws-smithy-experimental/Cargo.toml +++ b/sources/aws-smithy-experimental/Cargo.toml @@ -19,8 +19,9 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(crypto_unstable)'] } aws-smithy-types = { workspace = true, features = ["http-body-1-x"] } aws-smithy-runtime-api = { workspace = true, features = ["client", "http-1x"] } aws-smithy-runtime = { workspace = true, features = ["client"] } -aws-smithy-async.workspace = true +aws-smithy-async.workspace = true h2.workspace = true +headers.workspace = true hyper-util.workspace = true once_cell.workspace = true pin-project-lite.workspace = true @@ -28,10 +29,11 @@ rustls.workspace = true tracing.workspace = true tokio.workspace = true tower.workspace = true -# FIXME: migrate these dependencies once we migrate to http-hyper-proxy -hyper = { version = "1", features = ["client", "http1", "http2"] } -hyper-rustls = { version = "0.27", features = ["http2", "http1", "native-tokio", "tls12"], default-features = false } +hyper = { workspace = true, features = ["client", "http1", "http2"] } +hyper-http-proxy.workspace = true +hyper-rustls = { workspace = true, features = ["http2", "http1", "native-tokio", "tls12"], default-features = false } http = "1" +url.workspace = true [dev-dependencies] aws-smithy-async = { workspace = true, features = ["rt-tokio", "test-util"] } diff --git a/sources/aws-smithy-experimental/src/hyper_1_0.rs b/sources/aws-smithy-experimental/src/hyper_1_0.rs index 82253dc1c..6fde63d3f 100644 --- a/sources/aws-smithy-experimental/src/hyper_1_0.rs +++ b/sources/aws-smithy-experimental/src/hyper_1_0.rs @@ -152,9 +152,13 @@ mod build_connector { use crate::hyper_1_0::{HyperUtilResolver, Inner}; use aws_smithy_runtime_api::client::dns::ResolveDns; use client::connect::HttpConnector; + use headers::Authorization; + use hyper::Uri; + use hyper_http_proxy::{Proxy, ProxyConnector}; use hyper_util::client::legacy as client; use rustls::crypto::CryptoProvider; use std::sync::Arc; + use url::Url; fn restrict_ciphers(base: CryptoProvider) -> CryptoProvider { let suites = &[ @@ -209,6 +213,60 @@ mod build_connector { ) -> hyper_rustls::HttpsConnector>> { make_tls(HyperUtilResolver { resolver }, crypto_provider.provider()) } + + pub(super) fn https_with_proxy( + https_connector: hyper_rustls::HttpsConnector, + https_proxy: &str, + no_proxy: Option>, + ) -> hyper_http_proxy::ProxyConnector> { + // Determines whether a request of a given scheme, host and port should be proxied + // according to `https_proxy` and `no_proxy`. + + let intercept = move |scheme: Option<&str>, host: Option<&str>, _port| { + if let Some(host) = host { + if let Some(no_proxy) = &no_proxy { + if scheme != Some("https") { + return false; + } + if no_proxy.iter().any(|s| s == "*") { + // Don't proxy anything + return false; + } + // If the host matches one of the no proxy list entries, return false (don't proxy) + // Note that we're not doing anything fancy here for checking `no_proxy` since + // we only expect requests here to be going out to some AWS API endpoint. + return !no_proxy.iter().any(|no_proxy_host| { + !no_proxy_host.is_empty() && host.ends_with(no_proxy_host) + }); + } + true + } else { + false + } + }; + + let mut proxy_uri = https_proxy.parse::().expect("Invalid proxy URI"); + + // If the proxy's URI doesn't have a scheme, assume HTTP for the scheme and let the proxy + // server forward HTTPS connections and start a tunnel. + if proxy_uri.scheme().is_none() { + proxy_uri = format!("http://{}", https_proxy) + .parse::() + .expect("Unable to parse proxy URI as HTTPS"); + } + let mut proxy = Proxy::new(intercept, proxy_uri); + // Parse https_proxy as URL to extract out auth information if any + let proxy_url = Url::parse(https_proxy).expect("Unable to parse HTTPS proxy as URL"); + + if !proxy_url.username().is_empty() || proxy_url.password().is_some() { + proxy.set_authorization(Authorization::basic( + proxy_url.username(), + proxy_url.password().unwrap_or_default(), + )); + } + ProxyConnector::from_proxy(https_connector, proxy) + .expect("Failed to create proxy connector") + } } /// [`HttpConnector`] that uses [`hyper`] to make HTTP requests. @@ -664,6 +722,24 @@ impl HyperClientBuilder { ) }) } + + /// Create a hyper client using a proxy connector + pub fn build_with_proxy(self, https_proxy: H, no_proxy: Option<&[N]>) -> SharedHttpClient + where + H: AsRef + Clone + Send + Sync + 'static, + N: AsRef, + { + let crypto = self.crypto_provider.crypto_provider; + let no_proxy: Option> = + no_proxy.map(|n| n.iter().map(|s| s.as_ref().to_owned()).collect()); + build_with_fn(self.client_builder, move || { + build_connector::https_with_proxy( + cached_connectors::cached_https(crypto.clone()), + https_proxy.as_ref(), + no_proxy.clone(), + ) + }) + } } impl HyperClientBuilder { diff --git a/sources/deny.toml b/sources/deny.toml index 25d648b75..2e256c4d5 100644 --- a/sources/deny.toml +++ b/sources/deny.toml @@ -67,9 +67,9 @@ skip-tree = [ { name = "httptest", version = "=0.15" }, # schnauzer uses cached, which uses an older version of darling { name = "darling", version = "=0.14" }, - # reqwest brings a set of new dependencies that the aws-sdk doesn't - # support - { name = "reqwest", version = "=0.12" }, + # aws-smithy-experimental brings a set of new dependencies that + # the aws-sdk doesn't support + { name = "aws-smithy-experimental", version = "=0.1" }, ] [bans.workspace-dependencies]