From fbd976e5b65e140c2d76c97fb81e2a9e5a906131 Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Fri, 17 Apr 2020 21:20:45 +0000 Subject: [PATCH] tuftool: Remove KeySource enum, use tough-ssm and KeySource trait This is a decent set of changes to tuftool. It fully removes the KeySource enum in source.rs and instead uses the KeySource trait from tough. This commit also removes all SSM related code in favor of the tough-ssm crate (which is a copy of this code). It also removes the deref.rs file, as it is no longer need because these features have stabilized in upstream Rust. See: https://github.com/rust-lang/rust/issues/50264 and https://github.com/rust-lang/rust/pull/64708 --- tuftool/Cargo.toml | 1 + tuftool/src/create.rs | 7 +- tuftool/src/deref.rs | 18 --- tuftool/src/error.rs | 80 ++----------- tuftool/src/main.rs | 2 - tuftool/src/refresh.rs | 7 +- tuftool/src/root.rs | 32 +++-- tuftool/src/root_digest.rs | 6 +- tuftool/src/sign.rs | 7 +- tuftool/src/source.rs | 232 ++++++++++++------------------------- tuftool/src/ssm.rs | 54 --------- 11 files changed, 121 insertions(+), 325 deletions(-) delete mode 100644 tuftool/src/deref.rs delete mode 100644 tuftool/src/ssm.rs diff --git a/tuftool/Cargo.toml b/tuftool/Cargo.toml index e792829a..e5a82d90 100644 --- a/tuftool/Cargo.toml +++ b/tuftool/Cargo.toml @@ -34,6 +34,7 @@ tempfile = "3.1.0" url = "2.1.0" walkdir = "2.2.9" tough = { version = "0.5.0", path = "../tough", features = ["http"] } +tough-ssm = { version = "0.1.0", path = "../tough-ssm" } tokio = "0.2.13" [dev-dependencies] diff --git a/tuftool/src/create.rs b/tuftool/src/create.rs index 946a6e3a..ec34463f 100644 --- a/tuftool/src/create.rs +++ b/tuftool/src/create.rs @@ -6,7 +6,7 @@ use crate::error::{self, Result}; use crate::key::RootKeys; use crate::metadata; use crate::root_digest::RootDigest; -use crate::source::KeySource; +use crate::source::parse_key_source; use chrono::{DateTime, Utc}; use maplit::hashmap; use rayon::prelude::*; @@ -20,6 +20,7 @@ use std::io::Read; use std::num::{NonZeroU64, NonZeroUsize}; use std::path::{Path, PathBuf}; use structopt::StructOpt; +use tough::key_source::KeySource; use tough::schema::{ decoded::Decoded, Hashes, Role, Snapshot, SnapshotMeta, Target, Targets, Timestamp, TimestampMeta, @@ -37,8 +38,8 @@ pub(crate) struct CreateArgs { jobs: Option, /// Key files to sign with - #[structopt(short = "k", long = "key", required = true)] - keys: Vec, + #[structopt(short = "k", long = "key", required = true, parse(try_from_str = parse_key_source))] + keys: Vec>, /// Version of snapshot.json file #[structopt(long = "snapshot-version")] diff --git a/tuftool/src/deref.rs b/tuftool/src/deref.rs deleted file mode 100644 index aa64786c..00000000 --- a/tuftool/src/deref.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT OR Apache-2.0 - -//! Stable-supported shim for [`Option::deref`] ([tracking issue][inner_deref]). -//! -//! [inner_deref]: https://github.com/rust-lang/rust/issues/50264 - -use std::ops::Deref; - -pub(crate) trait OptionDeref { - fn deref_shim(&self) -> Option<&T::Target>; -} - -impl OptionDeref for Option { - fn deref_shim(&self) -> Option<&T::Target> { - self.as_ref().map(Deref::deref) - } -} diff --git a/tuftool/src/error.rs b/tuftool/src/error.rs index 5e2effb8..369e907e 100644 --- a/tuftool/src/error.rs +++ b/tuftool/src/error.rs @@ -6,9 +6,6 @@ use snafu::{Backtrace, Snafu}; use std::path::PathBuf; -#[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] -use crate::deref::OptionDeref; - pub(crate) type Result = std::result::Result; #[derive(Debug, Snafu)] @@ -124,14 +121,6 @@ pub(crate) enum Error { backtrace: Backtrace, }, - #[snafu(display("{}: {}", path.display(), source))] - Key { - path: PathBuf, - #[snafu(source(from(Error, Box::new)))] - #[snafu(backtrace)] - source: Box, - }, - #[snafu(display("Duplicate key ID: {}", key_id))] KeyDuplicate { key_id: String, @@ -150,6 +139,12 @@ pub(crate) enum Error { backtrace: Backtrace, }, + #[snafu(display("Unable to parse keypair: {}", source))] + KeyPairFromKeySource { + source: Box, + backtrace: Backtrace, + }, + #[snafu(display("Unable to match any of the provided keys with root.json"))] KeysNotFoundInRoot { backtrace: Backtrace }, @@ -200,28 +195,6 @@ pub(crate) enum Error { backtrace: Backtrace, }, - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - #[snafu(display("Error creating AWS credentials provider: {}", source))] - RusotoCreds { - source: rusoto_credential::CredentialsError, - backtrace: Backtrace, - }, - - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - #[snafu(display("Unknown AWS region \"{}\": {}", region, source))] - RusotoRegion { - region: String, - source: rusoto_core::region::ParseRegionError, - backtrace: Backtrace, - }, - - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - #[snafu(display("Error creating AWS request dispatcher: {}", source))] - RusotoTls { - source: rusoto_core::request::TlsError, - backtrace: Backtrace, - }, - #[snafu(display("Failed to sign message"))] Sign { source: tough::error::Error, @@ -234,41 +207,6 @@ pub(crate) enum Error { backtrace: Backtrace, }, - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - #[snafu(display( - "Failed to get aws-ssm://{}{}: {}", - profile.deref_shim().unwrap_or(""), - parameter_name, - source, - ))] - SsmGetParameter { - profile: Option, - parameter_name: String, - source: rusoto_core::RusotoError, - backtrace: Backtrace, - }, - - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - #[snafu(display( - "Failed to put aws-ssm://{}{}: {}", - profile.deref_shim().unwrap_or(""), - parameter_name, - source, - ))] - SsmPutParameter { - profile: Option, - parameter_name: String, - source: rusoto_core::RusotoError, - backtrace: Backtrace, - }, - - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - #[snafu(display("Missing field in SSM response: {}", field))] - SsmMissingField { - field: &'static str, - backtrace: Backtrace, - }, - #[snafu(display("Target not found: {}", target))] TargetNotFound { target: String, @@ -309,6 +247,12 @@ pub(crate) enum Error { backtrace: Backtrace, }, + #[snafu(display("Failed write: {}", source))] + WriteKeySource { + source: Box, + backtrace: Backtrace, + }, + #[snafu(display("Failed writing target data to disk: {}", source))] WriteTarget { source: std::io::Error, diff --git a/tuftool/src/main.rs b/tuftool/src/main.rs index 2b0661a4..732d610c 100644 --- a/tuftool/src/main.rs +++ b/tuftool/src/main.rs @@ -13,7 +13,6 @@ mod create; mod datetime; -mod deref; mod download; mod error; mod key; @@ -23,7 +22,6 @@ mod root; mod root_digest; mod sign; mod source; -mod ssm; use crate::error::Result; use snafu::{ErrorCompat, OptionExt, ResultExt}; diff --git a/tuftool/src/refresh.rs b/tuftool/src/refresh.rs index 668a796a..313abbf7 100644 --- a/tuftool/src/refresh.rs +++ b/tuftool/src/refresh.rs @@ -5,7 +5,7 @@ use crate::datetime::parse_datetime; use crate::error::{self, Result}; use crate::metadata; use crate::root_digest::RootDigest; -use crate::source::KeySource; +use crate::source::parse_key_source; use chrono::{DateTime, Utc}; use maplit::hashmap; use ring::rand::SystemRandom; @@ -15,6 +15,7 @@ use std::fs::File; use std::num::{NonZeroU64, NonZeroUsize}; use std::path::PathBuf; use structopt::StructOpt; +use tough::key_source::KeySource; use tough::schema::{Hashes, Snapshot, SnapshotMeta, Targets, Timestamp, TimestampMeta}; use tough::{FilesystemTransport, HttpTransport, Limits, Repository, Transport}; use url::Url; @@ -42,8 +43,8 @@ pub(crate) struct RefreshArgs { jobs: Option, /// Key files to sign with - #[structopt(short = "k", long = "key", required = true)] - keys: Vec, + #[structopt(short = "k", long = "key", required = true, parse(try_from_str = parse_key_source))] + keys: Vec>, /// Version of snapshot.json file #[structopt(long = "snapshot-version")] diff --git a/tuftool/src/root.rs b/tuftool/src/root.rs index 2bcd97b7..11211238 100644 --- a/tuftool/src/root.rs +++ b/tuftool/src/root.rs @@ -3,7 +3,7 @@ use crate::datetime::parse_datetime; use crate::error::{self, Result}; -use crate::source::KeySource; +use crate::source::parse_key_source; use crate::{load_file, write_file}; use chrono::{DateTime, Timelike, Utc}; use maplit::hashmap; @@ -12,6 +12,7 @@ use std::collections::HashMap; use std::num::NonZeroU64; use std::path::PathBuf; use structopt::StructOpt; +use tough::key_source::KeySource; use tough::schema::decoded::{Decoded, Hex}; use tough::schema::{key::Key, RoleKeys, RoleType, Root, Signed}; use tough::sign::{parse_keypair, Sign}; @@ -51,7 +52,8 @@ pub(crate) enum Command { /// Path to root.json path: PathBuf, /// The new key - key_path: KeySource, + #[structopt(parse(try_from_str = parse_key_source))] + key_source: Box, /// The role to add the key to #[structopt(short = "r", long = "role")] roles: Vec, @@ -71,7 +73,8 @@ pub(crate) enum Command { /// Path to root.json path: PathBuf, /// Where to write the new key - key_path: KeySource, + #[structopt(parse(try_from_str = parse_key_source))] + key_source: Box, /// Bit length of new key #[structopt(short = "b", long = "bits", default_value = "2048")] bits: u16, @@ -113,16 +116,16 @@ impl Command { Command::AddKey { path, roles, - key_path, - } => Command::add_key(path, roles, key_path), + key_source, + } => Command::add_key(path, roles, key_source), Command::RemoveKey { path, key_id, role } => Command::remove_key(path, key_id, *role), Command::GenRsaKey { path, roles, - key_path, + key_source, bits, exponent, - } => Command::gen_rsa_key(path, roles, key_path, *bits, *exponent), + } => Command::gen_rsa_key(path, roles, key_source, *bits, *exponent), } } @@ -181,9 +184,13 @@ impl Command { write_file(path, &root) } - fn add_key(path: &PathBuf, roles: &[RoleType], key_path: &KeySource) -> Result<()> { + #[allow(clippy::borrowed_box)] + fn add_key(path: &PathBuf, roles: &[RoleType], key_source: &Box) -> Result<()> { let mut root: Signed = load_file(path)?; - let key_pair = key_path.as_public_key()?; + let key_pair = key_source + .as_sign() + .context(error::KeyPairFromKeySource)? + .tuf_key(); let key_id = hex::encode(add_key(&mut root.signed, roles, key_pair)?); clear_sigs(&mut root); println!("{}", key_id); @@ -214,10 +221,11 @@ impl Command { write_file(path, &root) } + #[allow(clippy::borrowed_box)] fn gen_rsa_key( path: &PathBuf, roles: &[RoleType], - key_path: &KeySource, + key_source: &Box, bits: u16, exponent: u32, ) -> Result<()> { @@ -247,7 +255,9 @@ impl Command { let key_pair = parse_keypair(stdout.as_bytes()).context(error::KeyPairParse)?; let key_id = hex::encode(add_key(&mut root.signed, roles, key_pair.tuf_key())?); - key_path.write(&stdout, &key_id)?; + key_source + .write(&stdout, &key_id) + .context(error::WriteKeySource)?; clear_sigs(&mut root); println!("{}", key_id); write_file(path, &root) diff --git a/tuftool/src/root_digest.rs b/tuftool/src/root_digest.rs index 9fdbe77e..57a1b5ad 100644 --- a/tuftool/src/root_digest.rs +++ b/tuftool/src/root_digest.rs @@ -1,12 +1,12 @@ use crate::error; use crate::error::Result; use crate::key::RootKeys; -use crate::source::KeySource; use ring::digest::{SHA256, SHA256_OUTPUT_LEN}; use snafu::ensure; use snafu::ResultExt; use std::collections::HashMap; use std::path::PathBuf; +use tough::key_source::KeySource; use tough::schema::{Root, Signed}; /// Represents a loaded root.json file along with its sha256 digest and size in bytes @@ -56,10 +56,10 @@ impl RootDigest { /// /// * An error can occur for io reasons /// - pub(crate) fn load_keys(&self, keys: &[KeySource]) -> Result { + pub(crate) fn load_keys(&self, keys: &[Box]) -> Result { let mut map = HashMap::new(); for source in keys { - let key_pair = source.as_sign()?; + let key_pair = source.as_sign().context(error::KeyPairFromKeySource)?; if let Some((keyid, _)) = self .root .keys diff --git a/tuftool/src/sign.rs b/tuftool/src/sign.rs index 2d03bec8..e046ae96 100644 --- a/tuftool/src/sign.rs +++ b/tuftool/src/sign.rs @@ -4,13 +4,14 @@ use crate::error::Result; use crate::key::sign_metadata; use crate::root_digest::RootDigest; -use crate::source::KeySource; +use crate::source::parse_key_source; use crate::{load_file, write_file}; use ring::rand::SystemRandom; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; use structopt::StructOpt; +use tough::key_source::KeySource; use tough::schema::{RoleType, Signed}; #[derive(Debug, StructOpt)] @@ -20,8 +21,8 @@ pub(crate) struct SignArgs { root: PathBuf, /// Key files to sign with - #[structopt(short = "k", long = "key")] - keys: Vec, + #[structopt(short = "k", long = "key", parse(try_from_str = parse_key_source))] + keys: Vec>, /// Metadata file to sign metadata_file: PathBuf, diff --git a/tuftool/src/source.rs b/tuftool/src/source.rs index 77fac167..9a33e7b6 100644 --- a/tuftool/src/source.rs +++ b/tuftool/src/source.rs @@ -1,176 +1,88 @@ // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT OR Apache-2.0 +#![allow(clippy::doc_markdown)] //! Private keys are generally provided as paths, but may sometimes be provided as a URL. For -//! example, when one of the Rusoto features is enabled, you can use an aws-ssm:// URL to refer to -//! a key accessible in SSM. +//! example, when one of the Rusoto features is enabled, you can use an aws-ssm:// special URL +//! to refer to a key accessible in SSM. (See below for more format examples) //! //! This module parses a key source command line parameter as a URL, relative to `file://$PWD`, //! then matches the URL scheme against ones we understand. +//! +//! Currently supported key sources are local files and AWS SSM. +//! +//! Examples of currently supported formats: +//! +//! Local files may be specified using a path or "file:///" prefixed path: +//! "./a/key/file/here" +//! "file:///./a/key/file/here" (notice the 3 slashes after the colon) +//! +//! Keys stored in AWS SSM use a special format: +//! "aws-ssm:///key/path/in/SSM?kms-key-id=12345" +//! +//! "kms-key-id" is an optional parameter you can provide. It is only used for writing +//! a key back to SSM. If it is not provided, the default key associated with your AWS +//! account is used. +//! +//! For example, using a profile "foo" and a key located at "a/key" +//! "aws-ssm://foo/a/key" +//! +//! Adding a specific KMS key: +//! "aws-ssm://foo/a/key?kms-key-id=1234567890" +//! +//! You may also skip the profile bit and just use your local environment's default profile: +//! "aws-ssm:///a/key" (notice the 3 slashes after the colon) -use crate::error::{self, Error, Result}; -use snafu::{OptionExt, ResultExt}; +use crate::error::{self, Result}; +use snafu::ResultExt; use std::path::PathBuf; -use std::str::FromStr; -use tough::schema::key::Key; -use tough::sign::{parse_keypair, Sign}; +use tough::key_source::{KeySource, LocalKeySource}; +use tough_ssm::SsmKeySource; use url::Url; -#[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] -use tokio; +/// Parses a user-specified source of signing keys. +/// Sources are passed to `tuftool` as arguments in string format: +/// "file:///..." or "./a/path/here" or "aws-ssm://...". See above +/// doc comment for more info on the appropriate format. +/// +/// Users are welcome to add their own sources of keys by implementing +/// the `KeySource` trait in the `tough` library. A user can then add +/// to this parser to support them in `tuftool`. +pub(crate) fn parse_key_source(input: &str) -> Result> { + let pwd_url = Url::from_directory_path(std::env::current_dir().context(error::CurrentDir)?) + .expect("expected current directory to be absolute"); + let url = Url::options() + .base_url(Some(&pwd_url)) + .parse(input) + .context(error::UrlParse { url: input })?; -#[derive(Debug)] -pub(crate) enum KeySource { - Local(PathBuf), - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - Ssm { - profile: Option, - parameter_name: String, - key_id: Option, - }, -} - -impl KeySource { - pub(crate) fn as_sign(&self) -> Result> { - let keypair = parse_keypair(&self.read()?).context(error::KeyPairParse)?; - Ok(Box::new(keypair)) - } - - pub(crate) fn as_public_key(&self) -> Result { - let data = self.read()?; - if let Ok(key_pair) = parse_keypair(&data) { - Ok(key_pair.tuf_key()) - } else { - let data = String::from_utf8(data) - .ok() - .context(error::UnrecognizedKey)?; - Key::from_str(&data).ok().context(error::UnrecognizedKey) - } - } - - fn read(&self) -> Result> { - match self { - KeySource::Local(path) => std::fs::read(path).context(error::FileRead { path }), - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - KeySource::Ssm { - profile, - parameter_name, - .. - } => KeySource::read_with_ssm_key(profile, ¶meter_name), - } - } - - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - fn read_with_ssm_key(profile: &Option, parameter_name: &str) -> Result> { - use crate::deref::OptionDeref; - use rusoto_ssm::Ssm; - - let ssm_client = crate::ssm::build_client(profile.deref_shim())?; - let fut = ssm_client.get_parameter(rusoto_ssm::GetParameterRequest { - name: parameter_name.to_owned(), - with_decryption: Some(true), - }); - let response = tokio::runtime::Runtime::new() - .unwrap() - .block_on(fut) - .context(error::SsmGetParameter { - profile: profile.clone(), - parameter_name, - })?; - Ok(response - .parameter - .context(error::SsmMissingField { field: "parameter" })? - .value - .context(error::SsmMissingField { - field: "parameter.value", - })? - .as_bytes() - .to_vec()) - } - - #[cfg_attr( - not(any(feature = "rusoto-native-tls", feature = "rusoto-rustls")), - allow(unused) - )] - pub(crate) fn write(&self, value: &str, key_id_hex: &str) -> Result<()> { - match self { - KeySource::Local(path) => { - std::fs::write(path, value.as_bytes()).context(error::FileWrite { path }) - } - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - KeySource::Ssm { - profile, - parameter_name, - key_id, - } => KeySource::write_with_ssm_key(value, key_id_hex, profile, ¶meter_name, key_id), - } - } - - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - fn write_with_ssm_key( - value: &str, - key_id_hex: &str, - profile: &Option, - parameter_name: &str, - key_id: &Option, - ) -> Result<()> { - use crate::deref::OptionDeref; - use rusoto_ssm::Ssm; - - let ssm_client = crate::ssm::build_client(profile.deref_shim())?; - let fut = ssm_client.put_parameter(rusoto_ssm::PutParameterRequest { - name: parameter_name.to_owned(), - description: Some(key_id_hex.to_owned()), - key_id: key_id.as_ref().cloned(), - overwrite: Some(true), - type_: "SecureString".to_owned(), - value: value.to_owned(), - ..rusoto_ssm::PutParameterRequest::default() - }); - tokio::runtime::Runtime::new() - .unwrap() - .block_on(fut) - .context(error::SsmPutParameter { - profile: profile.clone(), - parameter_name, - })?; - Ok(()) - } -} - -impl FromStr for KeySource { - type Err = Error; - - #[allow(clippy::find_map)] - fn from_str(s: &str) -> Result { - let pwd_url = Url::from_directory_path(std::env::current_dir().context(error::CurrentDir)?) - .expect("expected current directory to be absolute"); - let url = Url::options() - .base_url(Some(&pwd_url)) - .parse(s) - .context(error::UrlParse { url: s })?; - - match url.scheme() { - "file" => Ok(KeySource::Local(PathBuf::from(url.path()))), - #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - "aws-ssm" => Ok(KeySource::Ssm { - profile: url.host_str().and_then(|s| { - if s.is_empty() { - None - } else { - Some(s.to_owned()) - } - }), - parameter_name: url.path().to_owned(), - key_id: url - .query_pairs() - .find(|(k, _)| k == "kms-key-id") - .map(|(_, v)| v.into_owned()), + match url.scheme() { + "file" => Ok(Box::new(LocalKeySource { + path: PathBuf::from(url.path()), + })), + #[cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] + "aws-ssm" => Ok(Box::new(SsmKeySource { + profile: url.host_str().and_then(|s| { + if s.is_empty() { + None + } else { + Some(s.to_owned()) + } + }), + parameter_name: url.path().to_owned(), + // If a key ID isn't provided, the system uses the default key + // associated with your AWS account. + key_id: url.query_pairs().find_map(|(k, v)| { + if k == "kms-key-id" { + Some(v.into_owned()) + } else { + None + } }), - _ => error::UnrecognizedScheme { - scheme: url.scheme(), - } - .fail(), + })), + _ => error::UnrecognizedScheme { + scheme: url.scheme(), } + .fail(), } } diff --git a/tuftool/src/ssm.rs b/tuftool/src/ssm.rs deleted file mode 100644 index fed2d22a..00000000 --- a/tuftool/src/ssm.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT OR Apache-2.0 - -#![cfg(any(feature = "rusoto-native-tls", feature = "rusoto-rustls"))] - -use crate::error::{self, Result}; -use rusoto_core::{HttpClient, Region}; -use rusoto_credential::ProfileProvider; -use rusoto_ssm::SsmClient; -use snafu::ResultExt; -use std::env; -use std::str::FromStr; - -/// Builds an SSM client for a given profile name. -/// -/// This **cannot** be called concurrently as it modifies environment variables (due to Rusoto's -/// inflexibility for determining the region given a profile name). -// -// A better explanation: we want to know what region to make SSM calls in based on ~/.aws/config, -// but `ProfileProvider::region` is an associated function, not a method; this means we can't tell -// it what profile to select the region for. -// -// However, `region` calls `ProfileProvider::default_profile_name`, which uses the `AWS_PROFILE` -// environment variable. So we set that :( -// -// This behavior should be better supported in `rusoto_credential` -// TODO(iliana): submit issue + PR upstream -pub(crate) fn build_client(profile: Option<&str>) -> Result { - Ok(if let Some(profile) = profile { - let mut provider = ProfileProvider::new().context(error::RusotoCreds)?; - provider.set_profile(profile); - - let profile_prev = env::var_os("AWS_PROFILE"); - env::set_var("AWS_PROFILE", profile); - let region = ProfileProvider::region().context(error::RusotoCreds)?; - match profile_prev { - Some(v) => env::set_var("AWS_PROFILE", v), - None => env::remove_var("AWS_PROFILE"), - } - - SsmClient::new_with( - HttpClient::new().context(error::RusotoTls)?, - provider, - match region { - Some(region) => { - Region::from_str(®ion).context(error::RusotoRegion { region })? - } - None => Region::default(), - }, - ) - } else { - SsmClient::new(Region::default()) - }) -}