forked from awslabs/tough
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This crate is meant to implement the KeySource trait for keys that live in AWS SSM.
- Loading branch information
Showing
6 changed files
with
248 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,6 @@ | |
members = [ | ||
"olpc-cjson", | ||
"tough", | ||
"tough-ssm", | ||
"tuftool", | ||
] |
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,21 @@ | ||
[package] | ||
name = "tough-ssm" | ||
version = "0.1.0" | ||
authors = ["Zac Mrowicki <[email protected]>"] | ||
edition = "2018" | ||
|
||
[features] | ||
default = ["rusoto"] | ||
rusoto = ["rusoto-rustls"] | ||
rusoto-native-tls = ["rusoto_core/native-tls", "rusoto_credential", "rusoto_ssm/native-tls"] | ||
rusoto-rustls = ["rusoto_core/rustls", "rusoto_credential", "rusoto_ssm/rustls"] | ||
|
||
[dependencies] | ||
tough = { version = "0.5.0", path = "../tough", features = ["http"] } | ||
rusoto_core = { version = "0.43", optional = true, default-features = false } | ||
rusoto_credential = { version = "0.43", optional = true } | ||
rusoto_ssm = { version = "0.43", optional = true, default-features = false } | ||
serde = "1.0.105" | ||
serde_json = "1.0.50" | ||
snafu = { version = "0.6.6", features = ["backtraces-impl-backtrace-crate"] } | ||
tokio = "0.2.13" |
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,53 @@ | ||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
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`: this PR | ||
// implements it: https://github.com/rusoto/rusoto/pull/1741 | ||
// TODO: Update rusoto once the above PR is merged and all our problems magically dissapear! | ||
pub(crate) fn build_client(profile: Option<&str>) -> Result<SsmClient> { | ||
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()) | ||
}) | ||
} |
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,79 @@ | ||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
use snafu::{Backtrace, Snafu}; | ||
|
||
pub type Result<T> = std::result::Result<T, Error>; | ||
|
||
/// The error type for this library. | ||
#[derive(Debug, Snafu)] | ||
#[snafu(visibility = "pub(crate)")] | ||
pub enum Error { | ||
#[snafu(display("Unable to parse keypair: {}", source))] | ||
KeyPairParse { | ||
source: tough::error::Error, | ||
backtrace: Backtrace, | ||
}, | ||
|
||
#[snafu(display("Error creating AWS credentials provider: {}", source))] | ||
RusotoCreds { | ||
source: rusoto_credential::CredentialsError, | ||
backtrace: Backtrace, | ||
}, | ||
|
||
#[snafu(display("Unknown AWS region \"{}\": {}", region, source))] | ||
RusotoRegion { | ||
region: String, | ||
source: rusoto_core::region::ParseRegionError, | ||
backtrace: Backtrace, | ||
}, | ||
|
||
#[snafu(display("Error creating AWS request dispatcher: {}", source))] | ||
RusotoTls { | ||
source: rusoto_core::request::TlsError, | ||
backtrace: Backtrace, | ||
}, | ||
|
||
#[snafu(display("Unable to create tokio runtime: {}", source))] | ||
RuntimeCreation { | ||
source: std::io::Error, | ||
backtrace: Backtrace, | ||
}, | ||
|
||
#[snafu(display( | ||
"Failed to get aws-ssm://{}{}: {}", | ||
profile.as_deref().unwrap_or(""), | ||
parameter_name, | ||
source, | ||
))] | ||
SsmGetParameter { | ||
profile: Option<String>, | ||
parameter_name: String, | ||
source: rusoto_core::RusotoError<rusoto_ssm::GetParameterError>, | ||
backtrace: Backtrace, | ||
}, | ||
|
||
#[snafu(display( | ||
"Missing field in SSM response for parameter '{}': {}", | ||
parameter_name, | ||
field | ||
))] | ||
SsmMissingField { | ||
parameter_name: String, | ||
field: &'static str, | ||
backtrace: Backtrace, | ||
}, | ||
|
||
#[snafu(display( | ||
"Failed to put aws-ssm://{}{}: {}", | ||
profile.as_deref().unwrap_or(""), | ||
parameter_name, | ||
source, | ||
))] | ||
SsmPutParameter { | ||
profile: Option<String>, | ||
parameter_name: String, | ||
source: rusoto_core::RusotoError<rusoto_ssm::PutParameterError>, | ||
backtrace: Backtrace, | ||
}, | ||
} |
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,79 @@ | ||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
mod client; | ||
pub mod error; | ||
|
||
use rusoto_ssm::Ssm; | ||
use snafu::{OptionExt, ResultExt}; | ||
use tough::key_source::KeySource; | ||
use tough::sign::{parse_keypair, Sign}; | ||
|
||
/// Implements the KeySource trait for keys that live in AWS SSM. | ||
#[derive(Debug)] | ||
pub struct SsmKeySource { | ||
pub profile: Option<String>, | ||
pub parameter_name: String, | ||
pub key_id: Option<String>, | ||
} | ||
|
||
/// Implements the KeySource trait. | ||
impl KeySource for SsmKeySource { | ||
fn as_sign( | ||
&self, | ||
) -> std::result::Result<Box<dyn Sign>, Box<dyn std::error::Error + Send + Sync + 'static>> | ||
{ | ||
let ssm_client = client::build_client(self.profile.as_deref())?; | ||
let fut = ssm_client.get_parameter(rusoto_ssm::GetParameterRequest { | ||
name: self.parameter_name.to_owned(), | ||
with_decryption: Some(true), | ||
}); | ||
let response = tokio::runtime::Runtime::new() | ||
.context(error::RuntimeCreation)? | ||
.block_on(fut) | ||
.context(error::SsmGetParameter { | ||
profile: self.profile.clone(), | ||
parameter_name: &self.parameter_name, | ||
})?; | ||
let data = response | ||
.parameter | ||
.context(error::SsmMissingField { | ||
parameter_name: &self.parameter_name, | ||
field: "parameter", | ||
})? | ||
.value | ||
.context(error::SsmMissingField { | ||
parameter_name: &self.parameter_name, | ||
field: "parameter.value", | ||
})? | ||
.as_bytes() | ||
.to_vec(); | ||
let sign = Box::new(parse_keypair(&data).context(error::KeyPairParse)?); | ||
Ok(sign) | ||
} | ||
|
||
fn write( | ||
&self, | ||
value: &str, | ||
key_id_hex: &str, | ||
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { | ||
let ssm_client = client::build_client(self.profile.as_deref())?; | ||
let fut = ssm_client.put_parameter(rusoto_ssm::PutParameterRequest { | ||
name: self.parameter_name.to_owned(), | ||
description: Some(key_id_hex.to_owned()), | ||
key_id: self.key_id.as_ref().cloned(), | ||
overwrite: Some(true), | ||
type_: "SecureString".to_owned(), | ||
value: value.to_owned(), | ||
..rusoto_ssm::PutParameterRequest::default() | ||
}); | ||
tokio::runtime::Runtime::new() | ||
.context(error::RuntimeCreation)? | ||
.block_on(fut) | ||
.context(error::SsmPutParameter { | ||
profile: self.profile.clone(), | ||
parameter_name: &self.parameter_name, | ||
})?; | ||
Ok(()) | ||
} | ||
} |