diff --git a/Cargo.lock b/Cargo.lock index 4abd08d..293dc24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1889,6 +1889,7 @@ dependencies = [ "prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", "rusoto_core 0.36.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rusoto_sts 0.36.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/aws/src/auth.rs b/aws/src/auth.rs index 94904ed..3df919e 100644 --- a/aws/src/auth.rs +++ b/aws/src/auth.rs @@ -8,9 +8,10 @@ use rusoto_core::{ }; use rusoto_sts::{StsAssumeRoleSessionCredentialsProvider, StsClient}; use std::{path::PathBuf, time::Duration}; +use rusoto_core::credential::StaticProvider; pub fn create_provider() -> Result, Error> { - let ceres_credential_provider = CeresAwsCredentialProvider::new(None)?; + let ceres_credential_provider = CeresAwsCredentialProvider::sts(None)?; let credentials_provider = AutoRefreshingProvider::new(ceres_credential_provider)?; Ok(credentials_provider) @@ -19,7 +20,16 @@ pub fn create_provider() -> Result Result, Error> { - let ceres_credential_provider = CeresAwsCredentialProvider::new(sts_config)?; + let ceres_credential_provider = CeresAwsCredentialProvider::sts(sts_config)?; + let credentials_provider = AutoRefreshingProvider::new(ceres_credential_provider)?; + + Ok(credentials_provider) +} + +pub fn create_provider_with_static_provider( + static_provider: StaticProvider, +) -> Result, Error> { + let ceres_credential_provider = CeresAwsCredentialProvider::static_provider(static_provider)?; let credentials_provider = AutoRefreshingProvider::new(ceres_credential_provider)?; Ok(credentials_provider) @@ -50,16 +60,23 @@ impl StsAssumeRoleSessionCredentialsProviderConfig { pub struct CeresAwsCredentialProvider { sts: Option, + static_provider: Option, chain: ChainProvider, } impl CeresAwsCredentialProvider { - pub fn new>>(sts_config: T) -> Result { + pub fn sts>>(sts_config: T) -> Result { let sts_config = sts_config.into(); let sts = sts_config.and_then(|x| sts_provider(x.credentials_path, x.profile_name, x.role_arn, x.region).ok()); let chain = chain_provider()?; - Ok(CeresAwsCredentialProvider { sts, chain }) + Ok(CeresAwsCredentialProvider { sts, static_provider: None, chain }) + } + + pub fn static_provider(static_provider: StaticProvider) -> Result { + let chain = chain_provider()?; + + Ok(CeresAwsCredentialProvider { sts: None, static_provider: Some(static_provider), chain }) } } @@ -101,6 +118,11 @@ impl ProvideAwsCredentials for CeresAwsCredentialProvider { let chain_f = self.chain.credentials(); let f = sts_f.or_else(|_| chain_f); Box::new(f) + } else if let Some(ref static_provider) = self.static_provider { + let static_provider_f = static_provider.credentials(); + let chain_f = self.chain.credentials(); + let f = static_provider_f.or_else(|_| chain_f); + Box::new(f) } else { let f = self.chain.credentials(); Box::new(f) diff --git a/aws/src/iam.rs b/aws/src/iam.rs index 7047c15..eb70423 100644 --- a/aws/src/iam.rs +++ b/aws/src/iam.rs @@ -1,8 +1,8 @@ use crate::AwsClientConfig; use chrono::{DateTime, Utc}; use failure::{err_msg, Error}; -use log::{debug, warn}; -use rusoto_iam::{GetAccessKeyLastUsedRequest, Iam, IamClient, ListAccessKeysRequest, ListUsersRequest, UpdateAccessKeyRequest, DeleteAccessKeyRequest, DeleteLoginProfileRequest, DeleteUserRequest}; +use log::{debug, error, warn}; +use rusoto_iam::{GetAccessKeyLastUsedRequest, Iam, IamClient, ListAccessKeysRequest, ListUsersRequest, UpdateAccessKeyRequest, DeleteAccessKeyRequest, DeleteLoginProfileRequest, DeleteUserRequest, ListUsersError}; use std::str::FromStr; #[derive(Debug, Clone)] @@ -43,6 +43,13 @@ pub fn list_users(aws_client_config: &AwsClientConfig) -> Result, Erro }; let res = iam.list_users(request).sync(); debug!("Finished list user request; success={}.", res.is_ok()); + match res { + Err(ListUsersError::Unknown(ref buf)) => { + let str = String::from_utf8_lossy(&buf.body); + error!("Error: {}", str); + } + _ => {} + } let res = res.expect("failed to list users"); if log::max_level() >= log::Level::Warn && res.is_truncated.is_some() && res.is_truncated.unwrap() { diff --git a/security-watchtower/Cargo.toml b/security-watchtower/Cargo.toml index 1ee1592..7b3d088 100644 --- a/security-watchtower/Cargo.toml +++ b/security-watchtower/Cargo.toml @@ -41,6 +41,7 @@ log = "0.4" prettytable-rs = "0.8" reqwest = "0.9" rusoto_core = "0.36" +rusoto_sts = "0.36" serde = "1" serde_derive = "1" serde_json = "1" diff --git a/security-watchtower/src/events/cron.rs b/security-watchtower/src/events/cron.rs index 5046d55..4d981a8 100644 --- a/security-watchtower/src/events/cron.rs +++ b/security-watchtower/src/events/cron.rs @@ -1,10 +1,11 @@ use chrono::Utc; -use failure::Error; +use failure::{Error, Fail}; use lambda_runtime::Context; -use log::{debug, info, trace}; +use log::{debug, error, info, trace}; +use rusoto_sts::{StsClient, Sts, AssumeRoleRequest, AssumeRoleError}; use serde_derive::{Deserialize, Serialize}; -use aws::{AwsClientConfig, Filter}; +use aws::AwsClientConfig; use bosun::{Bosun, Datum, Tags}; use duo::DuoClient; @@ -15,6 +16,10 @@ use crate::check_credentials::{ use crate::config::{CredentialsConfig, FunctionConfig}; use crate::events::HandleResult; use crate::metrics; +use aws::auth::{create_provider_with_static_provider}; +use rusoto_core::Region; +use rusoto_core::credential::StaticProvider; +use lambda::error::LambdaError; // cf. https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html // { @@ -41,7 +46,7 @@ pub struct ScheduledEvent { } pub fn handle( - aws_client_config: &AwsClientConfig, + _: &AwsClientConfig, _: &Context, config: &FunctionConfig, bosun: &T, @@ -54,13 +59,45 @@ pub fn handle( &config.duo.secret_key, )?; - let credentials = credentials(aws_client_config, &duo_client, &config.credentials, bosun)?; + let iam_role_arn = std::env::var("CD_IAM_ROLE_ARN").map_err(|e| e.context(LambdaError::FailedEnvVar("CD_IAM_ROLE_ARN")))?; + let iam_aws_client_config = assume_iam_role(iam_role_arn)?; + + let credentials = get_credentials(&iam_aws_client_config, &duo_client, &config.credentials, bosun)?; let handle_result = HandleResult::Cron { credentials }; Ok(handle_result) } +fn assume_iam_role(iam_role_arn: String) -> Result { + let sts = StsClient::new(Region::UsEast1); + let credentials = sts.assume_role(AssumeRoleRequest { + role_arn: iam_role_arn, + role_session_name: "Lambda2Iam".to_string(), + ..Default::default() + }).sync(); + match credentials { + Err(AssumeRoleError::Unknown(ref buf)) => { + let str = String::from_utf8_lossy(&buf.body); + error!("Error: {}", str); + } + _ => {} + } + let credentials = credentials?.credentials.unwrap(); // Safe unwrap, because the call was successfull + let static_provider = StaticProvider::new( + credentials.access_key_id, + credentials.secret_access_key, + Some(credentials.session_token), + None, + ); + + let credential_provider = create_provider_with_static_provider(static_provider)?; + let iam_aws_client_config = + AwsClientConfig::with_credentials_provider_and_region(credential_provider, Region::UsEast1)?; + + Ok(iam_aws_client_config) +} + #[derive(Debug, Serialize)] pub struct CredentialStats { pub total: usize, @@ -70,7 +107,7 @@ pub struct CredentialStats { pub failed: usize, } -pub fn credentials( +pub fn get_credentials( aws_client_config: &AwsClientConfig, duo_client: &DuoClient, config: &CredentialsConfig, @@ -79,11 +116,11 @@ pub fn credentials( debug!("Config: {:?}", config); let mut credentials = check_duo_credentials(&duo_client).expect("Failed to get Duo credentials"); debug!("Retrieved DUO credentials: {}", credentials.len()); - /* + let aws_credentials = check_aws_credentials(&aws_client_config).expect("failed to load credentials"); debug!("Retrieved AWS credentials: {}", aws_credentials.len()); credentials.extend(aws_credentials); - */ + bosun_emit_credential_last_used(bosun, &credentials)?; debug!("Checking for inactive credentials");