From e272d1b7d1c153cea7d132532b42d8cf4eed4b8e Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Sat, 22 Aug 2020 23:50:43 +0000 Subject: [PATCH] pubsys: use base region for contacting STS The region used for the base credentials provider should be the one in which you want to talk to STS to get temporary credentials, not the region in which you want to talk to a service endpoint like EC2. This is needed because you may be assuming a role in an opt-in region from an account that has not opted-in to that region, and you need to get session credentials from an STS endpoint in a region to which you have access in the base account. --- tools/pubsys/src/aws/ami/mod.rs | 7 ++++--- tools/pubsys/src/aws/ami/wait.rs | 3 ++- tools/pubsys/src/aws/client.rs | 17 +++++++++++++---- tools/pubsys/src/aws/publish_ami/mod.rs | 5 ++++- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/tools/pubsys/src/aws/ami/mod.rs b/tools/pubsys/src/aws/ami/mod.rs index 1e67734e327..1ff662bf384 100644 --- a/tools/pubsys/src/aws/ami/mod.rs +++ b/tools/pubsys/src/aws/ami/mod.rs @@ -111,11 +111,11 @@ async fn _run(args: &Args, ami_args: &AmiArgs) -> Result })?; // Build EBS client for snapshot management, and EC2 client for registration - let ebs_client = build_client::(&base_region, &aws).context(error::Client { + let ebs_client = build_client::(&base_region, &base_region, &aws).context(error::Client { client_type: "EBS", region: base_region.name(), })?; - let ec2_client = build_client::(&base_region, &aws).context(error::Client { + let ec2_client = build_client::(&base_region, &base_region, &aws).context(error::Client { client_type: "EC2", region: base_region.name(), })?; @@ -172,6 +172,7 @@ async fn _run(args: &Args, ami_args: &AmiArgs) -> Result wait_for_ami( &image_id, &base_region, + &base_region, "available", successes_required, &aws, @@ -187,7 +188,7 @@ async fn _run(args: &Args, ami_args: &AmiArgs) -> Result // live until the future is resolved. let mut ec2_clients = HashMap::with_capacity(regions.len()); for region in regions.iter() { - let ec2_client = build_client::(®ion, &aws).context(error::Client { + let ec2_client = build_client::(®ion, &base_region, &aws).context(error::Client { client_type: "EC2", region: base_region.name(), })?; diff --git a/tools/pubsys/src/aws/ami/wait.rs b/tools/pubsys/src/aws/ami/wait.rs index c1570e8701c..752142c5673 100644 --- a/tools/pubsys/src/aws/ami/wait.rs +++ b/tools/pubsys/src/aws/ami/wait.rs @@ -12,6 +12,7 @@ use std::time::Duration; pub(crate) async fn wait_for_ami( id: &str, region: &Region, + sts_region: &Region, state: &str, successes_required: u8, aws: &AwsConfig, @@ -49,7 +50,7 @@ pub(crate) async fn wait_for_ami( }; // Use a new client each time so we have more confidence that different endpoints can see // the new AMI. - let ec2_client = build_client::(®ion, &aws).context(error::Client { + let ec2_client = build_client::(®ion, &sts_region, &aws).context(error::Client { client_type: "EC2", region: region.name(), })?; diff --git a/tools/pubsys/src/aws/client.rs b/tools/pubsys/src/aws/client.rs index 489806219a0..d73a76ebc7d 100644 --- a/tools/pubsys/src/aws/client.rs +++ b/tools/pubsys/src/aws/client.rs @@ -38,10 +38,14 @@ impl NewWith for Ec2Client { } /// Create a rusoto client of the given type using the given region and configuration. -pub(crate) fn build_client(region: &Region, aws: &AwsConfig) -> Result { +pub(crate) fn build_client( + region: &Region, + sts_region: &Region, + aws: &AwsConfig, +) -> Result { let maybe_regional_role = aws.region.get(region.name()).and_then(|r| r.role.clone()); let assume_roles = aws.role.iter().chain(maybe_regional_role.iter()).cloned(); - let provider = build_provider(®ion, assume_roles.clone(), base_provider(&aws.profile)?)?; + let provider = build_provider(&sts_region, assume_roles.clone(), base_provider(&aws.profile)?)?; Ok(T::new_with( rusoto_core::HttpClient::new().context(error::HttpClient)?, provider, @@ -61,8 +65,13 @@ impl ProvideAwsCredentials for CredentialsProvider { } /// Chains credentials providers to assume the given roles in order. +/// The region given should be the one in which you want to talk to STS to get temporary +/// credentials, not the region in which you want to talk to a service endpoint like EC2. This is +/// needed because you may be assuming a role in an opt-in region from an account that has not +/// opted-in to that region, and you need to get session credentials from an STS endpoint in a +/// region to which you have access in the base account. fn build_provider

( - region: &Region, + sts_region: &Region, assume_roles: impl Iterator, base_provider: P, ) -> Result @@ -74,7 +83,7 @@ where let sts = StsClient::new_with( HttpClient::new().context(error::HttpClient)?, provider, - region.clone(), + sts_region.clone(), ); let expiring_provider = StsAssumeRoleSessionCredentialsProvider::new( sts, diff --git a/tools/pubsys/src/aws/publish_ami/mod.rs b/tools/pubsys/src/aws/publish_ami/mod.rs index 7b92224a2a3..94dea6fe43b 100644 --- a/tools/pubsys/src/aws/publish_ami/mod.rs +++ b/tools/pubsys/src/aws/publish_ami/mod.rs @@ -90,6 +90,9 @@ pub(crate) async fn run(args: &Args, publish_args: &PublishArgs) -> Result<()> { } else { aws.regions.clone().into() }; + ensure!(!regions.is_empty(), error::MissingConfig { missing: "aws.regions" }); + let base_region = region_from_string(®ions[0], &aws).context(error::ParseRegion)?; + // Check that the requested regions are a subset of the regions we *could* publish from the AMI // input JSON. let requested_regions = HashSet::from_iter(regions.iter()); @@ -121,7 +124,7 @@ pub(crate) async fn run(args: &Args, publish_args: &PublishArgs) -> Result<()> { // live until the future is resolved. let mut ec2_clients = HashMap::with_capacity(amis.len()); for region in amis.keys() { - let ec2_client = build_client::(®ion, &aws).context(error::Client { + let ec2_client = build_client::(®ion, &base_region, &aws).context(error::Client { client_type: "EC2", region: region.name(), })?;