From 458808c53371103bdf36de95d6f910c57e937b87 Mon Sep 17 00:00:00 2001 From: Marcelo Boeira Date: Mon, 24 Sep 2018 15:20:29 +0200 Subject: [PATCH] Store the session tokens on the credentials file - Store the tokens - Read them before requesting a new one - Make sure to respect the expiration date comming from AWS Closes #2 --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 111 ++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 79 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c96616a..81cd588 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,7 @@ dependencies = [ name = "awsudo" version = "0.0.1" dependencies = [ + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "rusoto_core 0.33.1 (registry+https://github.com/rust-lang/crates.io-index)", "rusoto_sts 0.33.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 21c0272..b04fcad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ clap = "2.31.2" rusoto_core = "0.33" rusoto_sts = "0.33" rust-ini = "0.12" +chrono = "0.4" diff --git a/src/main.rs b/src/main.rs index 6eaa923..fe9bfad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ extern crate clap; extern crate ini; extern crate rusoto_core; extern crate rusoto_sts; +extern crate chrono; use std::env; use std::path::Path; @@ -10,18 +11,13 @@ use std::process::{Command, Stdio}; use ini::Ini; use clap::{Arg, App}; -use rusoto_sts::{Sts, StsClient, Credentials, AssumeRoleRequest}; +use rusoto_sts::{Sts, StsClient, AssumeRoleRequest}; use rusoto_core::{Region}; +use chrono::prelude::*; const AWS_DEFAULT_CONFIG_PATH: &str = ".aws/credentials"; const AWS_DEFAULT_SESSION_NAME: &str = "awsudo"; -fn inject_environment_with(credentials: Credentials) { - env::set_var("AWS_ACCESS_KEY_ID", credentials.access_key_id); - env::set_var("AWS_SECRET_ACCESS_KEY", credentials.secret_access_key); - env::set_var("AWS_SESSION_TOKEN", credentials.session_token); -} - fn main() { // CLI //TODO Extract this to its own module/file/package... @@ -61,7 +57,7 @@ fn main() { // END CLI // State manager - let conf = match Ini::load_from_file(config_file_path) { + let conf = match Ini::load_from_file(config_file_path.clone()) { Err(message) => panic!(message), Ok(value) => value }; @@ -71,43 +67,90 @@ fn main() { let mfa_serial = section.get("mfa_serial").unwrap(); //TODO parse region or default - //TODO use a debug flag for those + // ~~~cached token ~~ + let file_aws_access_key_id = match section.get("aws_sudo_access_key_id") { + Some(value) => value, + None => "invalid" + }; + let file_aws_secret_access_key = match section.get("aws_sudo_secret_access_key") { + Some(value) => value, + None => "invalid" + }; + let file_aws_session_token = match section.get("aws_sudo_session_token") { + Some(value) => value, + None => "invalid" + }; + let file_aws_session_expiration_date = match section.get("aws_sudo_session_expiration_date") { + Some(value) => value, + None => "1994-07-03T07:30:00.00Z" + }; + + let now = Utc::now(); + let session_expires_at = match file_aws_session_expiration_date.parse::>() { + Ok(value) => value, + Err(e) => panic!("{:?}", e) + }; + println!("role_arn: {:?}", role_arn); println!("mfa_serial: {:?}", mfa_serial); - // END State Manager - - //TODO Figure where to put this token request interaction... - //TODO Get the MFA token only if necessary - let mut token = String::new(); - if !mfa_serial.is_empty() { - println!("Please type your MFA token for {:}: ", mfa_serial); - match io::stdin().read_line(&mut token) { - Ok(_) => { - token.pop(); //REMOVES THE \n - println!("token: {}", token); + + if file_aws_access_key_id != "invalid" && now <= session_expires_at { + env::set_var("AWS_ACCESS_KEY_ID", file_aws_access_key_id); + env::set_var("AWS_SECRET_ACCESS_KEY", file_aws_secret_access_key); + env::set_var("AWS_SESSION_TOKEN", file_aws_session_token); + } else { + //TODO use a debug flag for those + println!("role_arn: {:?}", role_arn); + println!("mfa_serial: {:?}", mfa_serial); + // END State Manager + + //TODO Figure where to put this token request interaction... + //TODO Get the MFA token only if necessary + let mut token = String::new(); + if !mfa_serial.is_empty() { + println!("Please type your MFA token for {:}: ", mfa_serial); + match io::stdin().read_line(&mut token) { + Ok(_) => { + token.pop(); //REMOVES THE \n + println!("token: {}", token); + } + Err(error) => println!("error: {}", error), } - Err(error) => println!("error: {}", error), } - } - // Token Generator - //TODO Extract this to its own module/file/package... - //TODO use the default - let sts = StsClient::new(Region::EuCentral1); - match sts.assume_role(AssumeRoleRequest{ + // Token Generator + //TODO Extract this to its own module/file/package... + //TODO use the default + let sts = StsClient::new(Region::EuCentral1); + match sts.assume_role(AssumeRoleRequest{ role_arn: role_arn.to_string(), role_session_name: AWS_DEFAULT_SESSION_NAME.to_owned(), serial_number: Some(mfa_serial.to_string()), token_code: Some(token.to_string()), ..Default::default() }).sync() { - Err(e) => panic!("{:?}", e), - Ok(response) => inject_environment_with(response.credentials.unwrap()) - }; - // END Token Generator + Err(e) => panic!("{:?}", e), + Ok(response) => { + let credentials = response.credentials.unwrap(); + let mut another = match Ini::load_from_file(config_file_path.clone()) { + Err(message) => panic!(message), + Ok(value) => value + }; + + another.with_section(Some(profile_name)) + .set("aws_sudo_access_key_id", credentials.access_key_id.clone()) + .set("aws_sudo_secret_access_key", credentials.secret_access_key.clone()) + .set("aws_sudo_session_token", credentials.session_token.clone()) + .set("aws_sudo_session_expiration_date", credentials.expiration.clone()); + + another.write_to_file(config_file_path.clone()).unwrap(); + + env::set_var("AWS_ACCESS_KEY_ID", credentials.access_key_id); + env::set_var("AWS_SECRET_ACCESS_KEY", credentials.secret_access_key); + env::set_var("AWS_SESSION_TOKEN", credentials.session_token); + } - // TODO: Persist token with State Manager - // - // TODO: Where to put the environment stuff? StateManager? + }; + } // Command runner //TODO Extract this to its own module/file/package...