Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aws sdk rust #469

Merged
merged 1 commit into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,343 changes: 789 additions & 554 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ allow = [
"Zlib"
]

exceptions = [
{ name = "unicode-ident", version = "1.0.2", allow = ["MIT", "Apache-2.0", "Unicode-DFS-2016"] },
]

# https://github.com/hsivonen/encoding_rs The non-test code that isn't generated from the WHATWG data in this crate is
# under Apache-2.0 OR MIT. Test code is under CC0.
[[licenses.clarify]]
Expand Down Expand Up @@ -54,10 +58,13 @@ multiple-versions = "deny"
wildcards = "deny"

skip-tree = [
# rusoto_signature uses an older version of itoa
{ name = "rusoto_signature" },
# structopt-derive uses and older verision of heck
{ name = "structopt-derive" },
{ name = "h2", version = "0.3.11" },
# aws sdk depends on hyper, which pulls in an older version of itoa
{ name = "hyper", version = "0.14.16" },
# chrono pulls in an older version of time
{ name = "chrono", version = "0.4.19" },
# structopt is using an older heck
{ name = "heck", version="0.3.3"},
]

[sources]
Expand Down
19 changes: 10 additions & 9 deletions tough-kms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,25 @@ keywords = ["TUF", "KMS"]
edition = "2018"

[features]
default = ["rusoto"]
rusoto = ["rusoto-rustls"]
rusoto-native-tls = ["rusoto_core/native-tls", "rusoto_credential", "rusoto_kms/native-tls"]
rusoto-rustls = ["rusoto_core/rustls", "rusoto_credential", "rusoto_kms/rustls"]
default = ["aws-sdk-rust"]
aws-sdk-rust = ["aws-sdk-rust-rustls"]
aws-sdk-rust-tls = ["aws-config/native-tls", "aws-sdk-kms/native-tls"]
aws-sdk-rust-rustls = ["aws-config/rustls", "aws-sdk-kms/rustls"]

[dependencies]
tough = { version = "0.12.2", path = "../tough", features = ["http"] }
ring = { version = "0.16.16", features = ["std"] }
rusoto_core = { version = "0.48", optional = true, default-features = false }
rusoto_credential = { version = "0.48", optional = true }
rusoto_kms = { version = "0.48", optional = true, default-features = false }
aws-sdk-kms = "0.16.0"
aws-config = "0.46.0"
snafu = { version = "0.7", features = ["backtraces-impl-backtrace-crate"] }
tokio = "1.8"
tokio = { version = "1", features = ["fs", "io-util", "time", "macros", "rt-multi-thread"] }
pem = "1.0.2"

[dev-dependencies]
aws-smithy-client = {version = "0.46.0", features = ["test-util"]}
aws-smithy-http = "0.46.0"
base64 = "0.13"
bytes = "1"
rusoto_mock = { version = "0.48", default-features = false }
http = "0.2"
serde = "1.0.125"
serde_json = "1.0.63"
55 changes: 33 additions & 22 deletions tough-kms/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,43 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::error::{self, Result};
use rusoto_core::{HttpClient, Region};
use rusoto_credential::ProfileProvider;
use rusoto_kms::KmsClient;
use aws_sdk_kms::Client as KmsClient;
use snafu::ResultExt;
use std::str::FromStr;
use std::thread;

/// Builds a KMS client for a given profile name.
pub(crate) fn build_client_kms(profile: Option<&str>) -> Result<KmsClient> {
Ok(if let Some(profile) = profile {
let mut provider = ProfileProvider::new().context(error::RusotoCredsSnafu)?;
provider.set_profile(profile);
let region = provider
.region_from_profile()
.context(error::RusotoRegionFromProfileSnafu { profile })?;
// We are cloning this so that we can send it across a thread boundary
let profile = profile.map(std::borrow::ToOwned::to_owned);
// We need to spin up a new thread to deal with the async nature of the
// AWS SDK Rust
let client: Result<KmsClient> = thread::spawn(move || {
let runtime = tokio::runtime::Runtime::new().context(error::RuntimeCreationSnafu)?;
Ok(runtime.block_on(async_build_client_kms(profile)))
})
.join()
.map_err(|_| error::Error::ThreadJoin {})?;
client
}

KmsClient::new_with(
HttpClient::new().context(error::RusotoTlsSnafu)?,
provider,
match region {
Some(region) => {
Region::from_str(&region).context(error::RusotoRegionSnafu { region })?
}
None => Region::default(),
},
)
async fn async_build_client_kms(profile: Option<String>) -> KmsClient {
let config = aws_config::from_env();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does from_env already use the profile if environment variables are not present? Although the behavior was previously not commented, I think it would help to have some comments here explaining why from_env happens first, then things are overridden by the profile.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.rs/aws-config/0.13.0/aws_config/fn.from_env.html it creates a ConfigLoader, which is a builder for the SdkConfig. Looking at the source, it's the same as ConfigLoader::default()

https://docs.rs/aws-config/0.13.0/aws_config/struct.ConfigLoader.html .from_env() is how the docs always refer to creating the builder for the SdkConfig, by default it looks in your environment and default profile, and the overrides are if you're doing anything else.

Copy link
Contributor

@etungsten etungsten Jul 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Matt meant adding code comments for the explanation.

edit: Since ConfigLoader does document that the default chain implementation goes through from_env first, I think it's ok to document that here.

let client_config = if let Some(profile) = profile {
config
.region(
aws_config::profile::ProfileFileRegionProvider::builder()
.profile_name(&profile)
.build(),
)
.credentials_provider(
aws_config::profile::ProfileFileCredentialsProvider::builder()
.profile_name(&profile)
.build(),
)
.load()
.await
} else {
KmsClient::new(Region::default())
})
config.load().await
};
KmsClient::new(&client_config)
}
37 changes: 5 additions & 32 deletions tough-kms/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,15 @@ pub type Result<T> = std::result::Result<T, Error>;
#[non_exhaustive]
#[allow(missing_docs)]
pub enum Error {
/// The library failed to authenticate Aws account.
#[snafu(display("Error creating AWS credentials provider: {}", source))]
RusotoCreds {
source: rusoto_credential::CredentialsError,
backtrace: Backtrace,
},

/// The library failed to get the region for the given profile.
#[snafu(display("Unable to determine region from profile '{}': {}", profile, source))]
RusotoRegionFromProfile {
profile: String,
source: rusoto_credential::CredentialsError,
backtrace: Backtrace,
},

/// The library failed to identify the region obtained from the given profile.
#[snafu(display("Unknown AWS region '{}': {}", region, source))]
RusotoRegion {
region: String,
source: rusoto_core::region::ParseRegionError,
backtrace: Backtrace,
},

/// The library failed to instantiate 'HttpClient'.
#[snafu(display("Error creating AWS request dispatcher: {}", source))]
RusotoTls {
source: rusoto_core::request::TlsError,
backtrace: Backtrace,
},

/// The library failed to instantiate 'tokio Runtime'.
#[snafu(display("Unable to create tokio runtime: {}", source))]
RuntimeCreation {
source: std::io::Error,
backtrace: Backtrace,
},
/// The library failed to join 'tokio Runtime'.
#[snafu(display("Unable to join tokio thread used to offload async workloads"))]
ThreadJoin,

/// The library failed to get public key from AWS KMS
#[snafu(display(
Expand All @@ -63,7 +36,7 @@ pub enum Error {
KmsGetPublicKey {
profile: Option<String>,
key_id: String,
source: rusoto_core::RusotoError<rusoto_kms::GetPublicKeyError>,
source: aws_sdk_kms::types::SdkError<aws_sdk_kms::error::GetPublicKeyError>,
backtrace: Backtrace,
},

Expand All @@ -80,7 +53,7 @@ pub enum Error {
KmsSignMessage {
key_id: String,
profile: Option<String>,
source: rusoto_core::RusotoError<rusoto_kms::SignError>,
source: aws_sdk_kms::types::SdkError<aws_sdk_kms::error::SignError>,
backtrace: Backtrace,
},

Expand Down
46 changes: 26 additions & 20 deletions tough-kms/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@

mod client;
pub mod error;
use aws_sdk_kms::types::Blob;
use aws_sdk_kms::Client as KmsClient;
use ring::digest::{digest, SHA256};
use ring::rand::SecureRandom;
use rusoto_kms::{Kms, KmsClient, SignRequest};
use snafu::{ensure, OptionExt, ResultExt};
use std::collections::HashMap;
use std::fmt;
Expand All @@ -42,12 +43,14 @@ pub enum KmsSigningAlgorithm {
}

impl KmsSigningAlgorithm {
fn value(self) -> String {
fn value(self) -> aws_sdk_kms::model::SigningAlgorithmSpec {
// Currently we are supporting only single algorithm, but code stub is added to support
// multiple algorithms in future.
String::from(match self {
KmsSigningAlgorithm::RsassaPssSha256 => "RSASSA_PSS_SHA_256",
})
match self {
KmsSigningAlgorithm::RsassaPssSha256 => {
aws_sdk_kms::model::SigningAlgorithmSpec::RsassaPssSha256
}
}
}
}

Expand Down Expand Up @@ -83,10 +86,10 @@ impl KeySource for KmsKeySource {
None => client::build_client_kms(self.profile.as_deref())?,
};
// Get the public key from AWS KMS
let fut = kms_client.get_public_key(rusoto_kms::GetPublicKeyRequest {
key_id: self.key_id.clone(),
..rusoto_kms::GetPublicKeyRequest::default()
});
let fut = kms_client
.get_public_key()
.key_id(self.key_id.clone())
.send();
let response = tokio::runtime::Runtime::new()
etungsten marked this conversation as resolved.
Show resolved Hide resolved
.context(error::RuntimeCreationSnafu)?
.block_on(fut)
Expand All @@ -100,7 +103,7 @@ impl KeySource for KmsKeySource {
contents: response
.public_key
.context(error::PublicKeyNoneSnafu)?
.to_vec(),
.into_inner(),
},
pem::EncodeConfig {
line_ending: pem::LineEnding::LF,
Expand All @@ -115,15 +118,16 @@ impl KeySource for KmsKeySource {
);
Ok(Box::new(KmsRsaKey {
profile: self.profile.clone(),
client: Some(kms_client.clone()),
client: Some(kms_client),
key_id: self.key_id.clone(),
public_key: key.parse().context(error::PublicKeyParseSnafu)?,
signing_algorithm: self.signing_algorithm,
modulus_size_bytes: parse_modulus_length_bytes(
response
.customer_master_key_spec
.as_ref()
.context(error::MissingCustomerMasterKeySpecSnafu)?,
.context(error::MissingCustomerMasterKeySpecSnafu)?
.as_str(),
)?,
}))
}
Expand Down Expand Up @@ -185,13 +189,15 @@ impl Sign for KmsRsaKey {
Some(value) => value,
None => client::build_client_kms(self.profile.as_deref())?,
};
let sign_fut = kms_client.sign(SignRequest {
key_id: self.key_id.clone(),
message: digest(&SHA256, msg).as_ref().to_vec().into(),
message_type: Some(String::from("DIGEST")),
signing_algorithm: self.signing_algorithm.value(),
..rusoto_kms::SignRequest::default()
});
let blob = Blob::new(digest(&SHA256, msg).as_ref().to_vec());
let sign_fut = kms_client
.sign()
.key_id(self.key_id.clone())
.message(blob)
.message_type(aws_sdk_kms::model::MessageType::Digest)
.signing_algorithm(self.signing_algorithm.value())
.send();

let response = tokio::runtime::Runtime::new()
.context(error::RuntimeCreationSnafu)?
.block_on(sign_fut)
Expand All @@ -202,7 +208,7 @@ impl Sign for KmsRsaKey {
let signature = response
.signature
.context(error::SignatureNotFoundSnafu)?
.to_vec();
.into_inner();

// sometimes KMS produces a signature that is shorter than the modulus. in those cases,
// we have observed that openssl and KMS will both validate the signature, but ring will
Expand Down
Loading