From 40803c0934a4110efc01d3d26b64001d4e16deba Mon Sep 17 00:00:00 2001 From: Erikson Tung Date: Tue, 21 Jun 2022 12:50:13 -0700 Subject: [PATCH 1/2] sundog: run settings generators with network proxy environment vars We make sure settings generators are kicked-off with user-specified network proxy environment variables. --- sources/api/sundog/src/main.rs | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/sources/api/sundog/src/main.rs b/sources/api/sundog/src/main.rs index 3886ecf9e73..72340566499 100644 --- a/sources/api/sundog/src/main.rs +++ b/sources/api/sundog/src/main.rs @@ -221,6 +221,73 @@ where Ok(populated_settings) } +// Builds the proxy environment variables to pass to settings generators +async fn build_proxy_env

(socket_path: P) -> Result> +where + P: AsRef, +{ + // Retrieve network proxy related settings. + let prefixes = vec!["settings.network".to_string()]; + let response = apiclient::get::get_prefixes(&socket_path, prefixes.to_owned()) + .await + .context(error::GetPrefixSnafu { prefixes })?; + + let mut proxy_envs = HashMap::new(); + if let Some(https_proxy) = response + .get("settings") + .and_then(|settings| settings.get("network")) + .and_then(|network_settings| network_settings.get("https-proxy")) + .and_then(|s| s.as_str()) + { + proxy_envs.insert("https_proxy".to_string(), https_proxy.to_string()); + proxy_envs.insert("HTTPS_PROXY".to_string(), https_proxy.to_string()); + } else { + // If the https-proxy isn't set, we can return early since no-proxy has no effect. + return Ok(proxy_envs); + } + + let mut no_proxy = vec!["localhost".to_string(), "127.0.0.1".to_string()]; + // Append user-specified no-proxy setting to the no-proxy list + if let Some(np) = response + .get("settings") + .and_then(|settings| settings.get("network")) + .and_then(|network_settings| network_settings.get("no-proxy")) + .and_then(|v| v.as_array()) + { + no_proxy.append( + &mut np + .iter() + .map(|s| s.as_str().unwrap_or_default().to_string()) + .filter(|s| !s.is_empty()) + .collect(), + ); + } + // We potentially need to also no-proxy some K8s related domains for K8s variants + let prefixes = vec!["settings.kubernetes".to_string()]; + let response = apiclient::get::get_prefixes(&socket_path, prefixes.to_owned()) + .await + .context(error::GetPrefixSnafu { prefixes })?; + + if let Some(k8s_settings) = response + .get("settings") + .and_then(|settings| settings.get("kubernetes")) + { + if let Some(k8s_apiserver) = k8s_settings.get("api-server").and_then(|s| s.as_str()) { + no_proxy.push(k8s_apiserver.to_string()); + } + if let Some(k8s_cluster_domain) = + k8s_settings.get("cluster-domain").and_then(|s| s.as_str()) + { + no_proxy.push(k8s_cluster_domain.to_string()); + } + } + let no_proxy_value = no_proxy.join(","); + proxy_envs.insert("no_proxy".to_string(), no_proxy_value.to_owned()); + proxy_envs.insert("NO_PROXY".to_string(), no_proxy_value); + + Ok(proxy_envs) +} + /// Run the setting generators and collect the output async fn get_dynamic_settings

( socket_path: P, @@ -238,6 +305,9 @@ where let settings_to_query: Vec<&str> = generators.keys().map(|s| s.as_ref()).collect(); let populated_settings = get_populated_settings(&socket_path, settings_to_query).await?; + // Get the proxy envs for the settings generators + let proxy_envs = build_proxy_env(socket_path).await?; + // For each generator, run it and capture the output for (setting_str, generator) in generators { let setting = Key::new(KeyType::Data, &setting_str).context(error::InvalidKeySnafu { @@ -270,6 +340,7 @@ where })?; let result = process::Command::new(command) + .envs(&proxy_envs) .args(command_strings) .output() .context(error::CommandFailureSnafu { From 538de7a5529aeba719cd56ca16d6da58d1a6858e Mon Sep 17 00:00:00 2001 From: Erikson Tung Date: Tue, 21 Jun 2022 18:16:38 -0700 Subject: [PATCH 2/2] pluto: respect `https_proxy` and `no_proxy` when making EKS API reqs Both `rusoto` and `aws-sdk-rust` do not respect `https_proxy` and `no_proxy` env vars. We have to handle it ourselves by creating a custom hyper http 'connector' that's configured with the right proxying configuration. --- sources/Cargo.lock | 137 ++++++++++++++++++++++++++++++++--- sources/api/pluto/Cargo.toml | 3 + sources/api/pluto/src/eks.rs | 80 +++++++++++++++++++- 3 files changed, 209 insertions(+), 11 deletions(-) diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 2d890fb50b5..d3e2a6057c9 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -968,6 +968,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ct-logs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +dependencies = [ + "sct 0.6.1", +] + [[package]] name = "darling" version = "0.14.1" @@ -1559,6 +1568,31 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "headers" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1 0.10.0", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.3.3" @@ -1710,6 +1744,42 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-proxy" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" +dependencies = [ + "bytes", + "futures", + "headers", + "http", + "hyper", + "hyper-rustls 0.22.1", + "rustls-native-certs 0.5.0", + "tokio", + "tokio-rustls 0.22.0", + "tower-service", + "webpki 0.21.4", +] + +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "ct-logs", + "futures-util", + "hyper", + "log", + "rustls 0.19.1", + "rustls-native-certs 0.5.0", + "tokio", + "tokio-rustls 0.22.0", + "webpki 0.21.4", +] + [[package]] name = "hyper-rustls" version = "0.23.0" @@ -1719,10 +1789,10 @@ dependencies = [ "http", "hyper", "log", - "rustls", - "rustls-native-certs", + "rustls 0.20.6", + "rustls-native-certs 0.6.2", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", ] [[package]] @@ -2584,6 +2654,9 @@ dependencies = [ "bottlerocket-variant", "constants", "generate-readme", + "hyper", + "hyper-proxy", + "hyper-rustls 0.23.0", "imdsclient", "models", "rusoto_core", @@ -2808,7 +2881,7 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-rustls", + "hyper-rustls 0.23.0", "ipnet", "js-sys", "lazy_static", @@ -2816,13 +2889,13 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.20.6", "rustls-pemfile 0.3.0", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -2880,7 +2953,7 @@ dependencies = [ "futures", "http", "hyper", - "hyper-rustls", + "hyper-rustls 0.23.0", "lazy_static", "log", "rusoto_credential", @@ -2975,6 +3048,19 @@ dependencies = [ "nom", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + [[package]] name = "rustls" version = "0.20.6" @@ -2983,10 +3069,22 @@ checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ "log", "ring", - "sct", + "sct 0.7.0", "webpki 0.22.0", ] +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls 0.19.1", + "schannel", + "security-framework", +] + [[package]] name = "rustls-native-certs" version = "0.6.2" @@ -3072,6 +3170,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sct" version = "0.7.0" @@ -3713,13 +3821,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + [[package]] name = "tokio-rustls" version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.6", "tokio", "webpki 0.22.0", ] diff --git a/sources/api/pluto/Cargo.toml b/sources/api/pluto/Cargo.toml index 236e81b1f2d..2cb181ae081 100644 --- a/sources/api/pluto/Cargo.toml +++ b/sources/api/pluto/Cargo.toml @@ -12,6 +12,9 @@ exclude = ["README.md"] [dependencies] apiclient = { path = "../apiclient", version = "0.1.0" } constants = { path = "../../constants", version = "0.1.0" } +hyper = "0.14.2" +hyper-proxy = { version = "0.9", default-features = false, features = ["rustls"] } +hyper-rustls = "0.23" imdsclient = { path = "../../imdsclient", version = "0.1.0" } models = { path = "../../models", version = "0.1.0" } rusoto_core = { version = "0.48.0", default-features = false, features = ["rustls"] } diff --git a/sources/api/pluto/src/eks.rs b/sources/api/pluto/src/eks.rs index d15c3bd7204..5d9f91d6106 100644 --- a/sources/api/pluto/src/eks.rs +++ b/sources/api/pluto/src/eks.rs @@ -1,7 +1,13 @@ +use hyper::http::uri::InvalidUri; +use hyper::Uri; +use hyper_proxy::{Proxy, ProxyConnector}; +use hyper_rustls::HttpsConnectorBuilder; +use rusoto_core::credential::ChainProvider; use rusoto_core::region::ParseRegionError; use rusoto_core::{Region, RusotoError}; use rusoto_eks::{DescribeClusterError, Eks, EksClient, KubernetesNetworkConfigResponse}; use snafu::{OptionExt, ResultExt, Snafu}; +use std::env; use std::str::FromStr; pub(crate) type ClusterNetworkConfig = KubernetesNetworkConfigResponse; @@ -21,6 +27,12 @@ pub(super) enum Error { region: String, source: ParseRegionError, }, + + #[snafu(display("Unable to parse '{}' as URI: {}", input, source))] + UriParse { input: String, source: InvalidUri }, + + #[snafu(display("Failed to create proxy creator: {}", source))] + ProxyConnector { source: std::io::Error }, } type Result = std::result::Result; @@ -32,7 +44,71 @@ pub(super) async fn get_cluster_network_config( cluster: &str, ) -> Result { let parsed_region = Region::from_str(region).context(RegionParseSnafu { region })?; - let client = EksClient::new(parsed_region); + + // Respect proxy environment variables when making AWS EKS API requests + let https_proxy = ["https_proxy", "HTTPS_PROXY"] + .iter() + .map(env::var) + .find(|env_var| *env_var != Err(env::VarError::NotPresent)) + .and_then(|s| s.ok()); + let no_proxy = ["no_proxy", "NO_PROXY"] + .iter() + .map(env::var) + .find(|env_var| *env_var != Err(env::VarError::NotPresent)) + .and_then(|s| s.ok()); + + let client = if let Some(https_proxy) = https_proxy { + // 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; + } + let no_proxy_hosts: Vec<&str> = no_proxy.split(',').map(|s| s.trim()).collect(); + if no_proxy_hosts.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_hosts.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::().context(UriParseSnafu { + input: &https_proxy, + })?; + // 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, + })?; + } + let proxy = Proxy::new(intercept, proxy_uri); + 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)?; + let http_client = rusoto_core::request::HttpClient::from_connector(proxy_connector); + EksClient::new_with(http_client, ChainProvider::new(), parsed_region) + } else { + EksClient::new(parsed_region) + }; let describe_cluster = rusoto_eks::DescribeClusterRequest { name: cluster.to_owned(), }; @@ -40,7 +116,7 @@ pub(super) async fn get_cluster_network_config( client .describe_cluster(describe_cluster) .await - .context(DescribeClusterSnafu {})? + .context(DescribeClusterSnafu)? .cluster .context(MissingSnafu { field: "cluster" })? .kubernetes_network_config