Skip to content

Commit a779a3c

Browse files
committed
Adds the tough-ssm crate
This crate is meant to implement the KeySource trait for keys that live in AWS SSM.
1 parent 98fe031 commit a779a3c

File tree

6 files changed

+248
-0
lines changed

6 files changed

+248
-0
lines changed

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
members = [
33
"olpc-cjson",
44
"tough",
5+
"tough-ssm",
56
"tuftool",
67
]

tough-ssm/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "tough-ssm"
3+
version = "0.1.0"
4+
authors = ["Zac Mrowicki <[email protected]>"]
5+
edition = "2018"
6+
7+
[features]
8+
default = ["rusoto"]
9+
rusoto = ["rusoto-rustls"]
10+
rusoto-native-tls = ["rusoto_core/native-tls", "rusoto_credential", "rusoto_ssm/native-tls"]
11+
rusoto-rustls = ["rusoto_core/rustls", "rusoto_credential", "rusoto_ssm/rustls"]
12+
13+
[dependencies]
14+
tough = { version = "0.5.0", path = "../tough", features = ["http"] }
15+
rusoto_core = { version = "0.43", optional = true, default-features = false }
16+
rusoto_credential = { version = "0.43", optional = true }
17+
rusoto_ssm = { version = "0.43", optional = true, default-features = false }
18+
serde = "1.0.105"
19+
serde_json = "1.0.50"
20+
snafu = { version = "0.6.6", features = ["backtraces-impl-backtrace-crate"] }
21+
tokio = "0.2.13"

tough-ssm/src/client.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT OR Apache-2.0
3+
4+
use crate::error::{self, Result};
5+
use rusoto_core::{HttpClient, Region};
6+
use rusoto_credential::ProfileProvider;
7+
use rusoto_ssm::SsmClient;
8+
use snafu::ResultExt;
9+
use std::env;
10+
use std::str::FromStr;
11+
12+
/// Builds an SSM client for a given profile name.
13+
///
14+
/// This **cannot** be called concurrently as it modifies environment variables (due to Rusoto's
15+
/// inflexibility for determining the region given a profile name).
16+
17+
// A better explanation: we want to know what region to make SSM calls in based on ~/.aws/config,
18+
// but `ProfileProvider::region` is an associated function, not a method; this means we can't tell
19+
// it what profile to select the region for.
20+
//
21+
// However, `region` calls `ProfileProvider::default_profile_name`, which uses the `AWS_PROFILE`
22+
// environment variable. So we set that :(
23+
//
24+
// This behavior should be better supported in `rusoto_credential`: this PR
25+
// implements it: https://github.com/rusoto/rusoto/pull/1741
26+
// TODO: Update rusoto once the above PR is merged and all our problems magically dissapear!
27+
pub(crate) fn build_client(profile: Option<&str>) -> Result<SsmClient> {
28+
Ok(if let Some(profile) = profile {
29+
let mut provider = ProfileProvider::new().context(error::RusotoCreds)?;
30+
provider.set_profile(profile);
31+
32+
let profile_prev = env::var_os("AWS_PROFILE");
33+
env::set_var("AWS_PROFILE", profile);
34+
let region = ProfileProvider::region().context(error::RusotoCreds)?;
35+
match profile_prev {
36+
Some(v) => env::set_var("AWS_PROFILE", v),
37+
None => env::remove_var("AWS_PROFILE"),
38+
}
39+
40+
SsmClient::new_with(
41+
HttpClient::new().context(error::RusotoTls)?,
42+
provider,
43+
match region {
44+
Some(region) => {
45+
Region::from_str(&region).context(error::RusotoRegion { region })?
46+
}
47+
None => Region::default(),
48+
},
49+
)
50+
} else {
51+
SsmClient::new(Region::default())
52+
})
53+
}

tough-ssm/src/error.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT OR Apache-2.0
3+
4+
use snafu::{Backtrace, Snafu};
5+
6+
pub type Result<T> = std::result::Result<T, Error>;
7+
8+
/// The error type for this library.
9+
#[derive(Debug, Snafu)]
10+
#[snafu(visibility = "pub(crate)")]
11+
pub enum Error {
12+
#[snafu(display("Unable to parse keypair: {}", source))]
13+
KeyPairParse {
14+
source: tough::error::Error,
15+
backtrace: Backtrace,
16+
},
17+
18+
#[snafu(display("Error creating AWS credentials provider: {}", source))]
19+
RusotoCreds {
20+
source: rusoto_credential::CredentialsError,
21+
backtrace: Backtrace,
22+
},
23+
24+
#[snafu(display("Unknown AWS region \"{}\": {}", region, source))]
25+
RusotoRegion {
26+
region: String,
27+
source: rusoto_core::region::ParseRegionError,
28+
backtrace: Backtrace,
29+
},
30+
31+
#[snafu(display("Error creating AWS request dispatcher: {}", source))]
32+
RusotoTls {
33+
source: rusoto_core::request::TlsError,
34+
backtrace: Backtrace,
35+
},
36+
37+
#[snafu(display("Unable to create tokio runtime: {}", source))]
38+
RuntimeCreation {
39+
source: std::io::Error,
40+
backtrace: Backtrace,
41+
},
42+
43+
#[snafu(display(
44+
"Failed to get aws-ssm://{}{}: {}",
45+
profile.as_deref().unwrap_or(""),
46+
parameter_name,
47+
source,
48+
))]
49+
SsmGetParameter {
50+
profile: Option<String>,
51+
parameter_name: String,
52+
source: rusoto_core::RusotoError<rusoto_ssm::GetParameterError>,
53+
backtrace: Backtrace,
54+
},
55+
56+
#[snafu(display(
57+
"Missing field in SSM response for parameter '{}': {}",
58+
parameter_name,
59+
field
60+
))]
61+
SsmMissingField {
62+
parameter_name: String,
63+
field: &'static str,
64+
backtrace: Backtrace,
65+
},
66+
67+
#[snafu(display(
68+
"Failed to put aws-ssm://{}{}: {}",
69+
profile.as_deref().unwrap_or(""),
70+
parameter_name,
71+
source,
72+
))]
73+
SsmPutParameter {
74+
profile: Option<String>,
75+
parameter_name: String,
76+
source: rusoto_core::RusotoError<rusoto_ssm::PutParameterError>,
77+
backtrace: Backtrace,
78+
},
79+
}

tough-ssm/src/lib.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT OR Apache-2.0
3+
4+
mod client;
5+
pub mod error;
6+
7+
use rusoto_ssm::Ssm;
8+
use snafu::{OptionExt, ResultExt};
9+
use tough::key_source::KeySource;
10+
use tough::sign::{parse_keypair, Sign};
11+
12+
/// Implements the KeySource trait for keys that live in AWS SSM.
13+
#[derive(Debug)]
14+
pub struct SsmKeySource {
15+
pub profile: Option<String>,
16+
pub parameter_name: String,
17+
pub key_id: Option<String>,
18+
}
19+
20+
/// Implements the KeySource trait.
21+
impl KeySource for SsmKeySource {
22+
fn as_sign(
23+
&self,
24+
) -> std::result::Result<Box<dyn Sign>, Box<dyn std::error::Error + Send + Sync + 'static>>
25+
{
26+
let ssm_client = client::build_client(self.profile.as_deref())?;
27+
let fut = ssm_client.get_parameter(rusoto_ssm::GetParameterRequest {
28+
name: self.parameter_name.to_owned(),
29+
with_decryption: Some(true),
30+
});
31+
let response = tokio::runtime::Runtime::new()
32+
.context(error::RuntimeCreation)?
33+
.block_on(fut)
34+
.context(error::SsmGetParameter {
35+
profile: self.profile.clone(),
36+
parameter_name: &self.parameter_name,
37+
})?;
38+
let data = response
39+
.parameter
40+
.context(error::SsmMissingField {
41+
parameter_name: &self.parameter_name,
42+
field: "parameter",
43+
})?
44+
.value
45+
.context(error::SsmMissingField {
46+
parameter_name: &self.parameter_name,
47+
field: "parameter.value",
48+
})?
49+
.as_bytes()
50+
.to_vec();
51+
let sign = Box::new(parse_keypair(&data).context(error::KeyPairParse)?);
52+
Ok(sign)
53+
}
54+
55+
fn write(
56+
&self,
57+
value: &str,
58+
key_id_hex: &str,
59+
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
60+
let ssm_client = client::build_client(self.profile.as_deref())?;
61+
let fut = ssm_client.put_parameter(rusoto_ssm::PutParameterRequest {
62+
name: self.parameter_name.to_owned(),
63+
description: Some(key_id_hex.to_owned()),
64+
key_id: self.key_id.as_ref().cloned(),
65+
overwrite: Some(true),
66+
type_: "SecureString".to_owned(),
67+
value: value.to_owned(),
68+
..rusoto_ssm::PutParameterRequest::default()
69+
});
70+
tokio::runtime::Runtime::new()
71+
.unwrap()
72+
.block_on(fut)
73+
.context(error::SsmPutParameter {
74+
profile: self.profile.clone(),
75+
parameter_name: &self.parameter_name,
76+
})?;
77+
Ok(())
78+
}
79+
}

0 commit comments

Comments
 (0)