Skip to content

Commit

Permalink
Adds the tough-ssm crate
Browse files Browse the repository at this point in the history
This crate is meant to implement the KeySource trait for keys
that live in AWS SSM.
  • Loading branch information
zmrow committed Apr 30, 2020
1 parent 98fe031 commit a779a3c
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 0 deletions.
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
members = [
"olpc-cjson",
"tough",
"tough-ssm",
"tuftool",
]
21 changes: 21 additions & 0 deletions tough-ssm/Cargo.toml
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 <mrowicki@amazon.com>"]
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"
53 changes: 53 additions & 0 deletions tough-ssm/src/client.rs
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(&region).context(error::RusotoRegion { region })?
}
None => Region::default(),
},
)
} else {
SsmClient::new(Region::default())
})
}
79 changes: 79 additions & 0 deletions tough-ssm/src/error.rs
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,
},
}
79 changes: 79 additions & 0 deletions tough-ssm/src/lib.rs
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()
.unwrap()
.block_on(fut)
.context(error::SsmPutParameter {
profile: self.profile.clone(),
parameter_name: &self.parameter_name,
})?;
Ok(())
}
}

0 comments on commit a779a3c

Please sign in to comment.