-
Notifications
You must be signed in to change notification settings - Fork 495
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pluto: add 'private-dns-name' subcommand
Adds a new command for retrieving the instance's PrivateDnsName. Refactors out code for setting up proxy for the AWS API clients so it can be shared between the EKS module and EC2 module.
- Loading branch information
Showing
6 changed files
with
228 additions
and
69 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
use crate::proxy; | ||
use aws_smithy_types::error::display::DisplayErrorContext; | ||
use aws_types::region::Region; | ||
use snafu::{OptionExt, ResultExt, Snafu}; | ||
use std::time::Duration; | ||
|
||
// Limit the timeout for the EC2 describe-instances API call to 5 minutes | ||
const EC2_DESCRIBE_INSTANCES_TIMEOUT: Duration = Duration::from_secs(300); | ||
|
||
#[derive(Debug, Snafu)] | ||
pub(super) enum Error { | ||
#[snafu(display( | ||
"Error describing instance '{}': {}", | ||
instance_id, | ||
DisplayErrorContext(source) | ||
))] | ||
DescribeInstances { | ||
instance_id: String, | ||
source: aws_sdk_eks::types::SdkError<aws_sdk_ec2::error::DescribeInstancesError>, | ||
}, | ||
|
||
#[snafu(display("Timed-out waiting for EC2 DescribeInstances API response: {}", source))] | ||
DescribeInstancesTimeout { source: tokio::time::error::Elapsed }, | ||
|
||
#[snafu(display("Missing field '{}' in EC2 response", field))] | ||
Missing { field: &'static str }, | ||
|
||
#[snafu(context(false), display("{}", source))] | ||
Proxy { source: proxy::Error }, | ||
} | ||
|
||
type Result<T> = std::result::Result<T, Error>; | ||
|
||
pub(super) async fn get_private_dns_name(region: &str, instance_id: &str) -> Result<String> { | ||
// Respect proxy environment variables when making AWS EC2 API requests | ||
let (https_proxy, no_proxy) = proxy::fetch_proxy_env(); | ||
|
||
let config = aws_config::from_env() | ||
.region(Region::new(region.to_owned())) | ||
.load() | ||
.await; | ||
|
||
let client = if let Some(https_proxy) = https_proxy { | ||
let http_client = proxy::setup_http_client(https_proxy, no_proxy)?; | ||
let ec2_config = aws_sdk_ec2::config::Builder::from(&config) | ||
.http_connector(http_client) | ||
.build(); | ||
aws_sdk_ec2::Client::from_conf(ec2_config) | ||
} else { | ||
aws_sdk_ec2::Client::new(&config) | ||
}; | ||
|
||
tokio::time::timeout( | ||
EC2_DESCRIBE_INSTANCES_TIMEOUT, | ||
client | ||
.describe_instances() | ||
.instance_ids(instance_id.to_owned()) | ||
.send(), | ||
) | ||
.await | ||
.context(DescribeInstancesTimeoutSnafu)? | ||
.context(DescribeInstancesSnafu { instance_id })? | ||
.reservations | ||
.and_then(|reservations| { | ||
reservations.first().and_then(|r| { | ||
r.instances.clone().and_then(|instances| { | ||
instances | ||
.first() | ||
.and_then(|i| i.private_dns_name().map(|s| s.to_string())) | ||
}) | ||
}) | ||
}) | ||
.context(MissingSnafu { | ||
field: "Reservation.Instance.PrivateDNSName", | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
use hyper::Uri; | ||
use hyper_proxy::{Proxy, ProxyConnector}; | ||
use hyper_rustls::HttpsConnectorBuilder; | ||
use snafu::{ResultExt, Snafu}; | ||
use std::env; | ||
|
||
#[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("Failed to create proxy creator: {}", source))] | ||
ProxyConnector { source: std::io::Error }, | ||
} | ||
|
||
type Result<T> = std::result::Result<T, Error>; | ||
|
||
/// Fetches `HTTPS_PROXY` and `NO_PROXY` variables from the process environment. | ||
pub(crate) fn fetch_proxy_env() -> (Option<String>, Option<String>) { | ||
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()); | ||
(https_proxy, no_proxy) | ||
} | ||
|
||
/// Setups a hyper-based HTTP client configured with a proxy connector. | ||
pub(crate) fn setup_http_client( | ||
https_proxy: String, | ||
no_proxy: Option<String>, | ||
) -> Result<impl Into<aws_smithy_client::http_connector::HttpConnector>> { | ||
// 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::<Uri>().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::<Uri>() | ||
.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)?; | ||
Ok(aws_smithy_client::hyper_ext::Adapter::builder().build(proxy_connector)) | ||
} |