diff --git a/src/config/kube.rs b/src/config/kube.rs index 0d2c5d7..d39c1fe 100644 --- a/src/config/kube.rs +++ b/src/config/kube.rs @@ -27,6 +27,7 @@ use kube::{ClientCertKey, Kluster, KlusterAuth}; use certs::{get_cert, get_cert_from_pem, get_key_from_str, get_private_key}; use super::kubefile::AuthProvider; +use super::kubefile::Exec; #[derive(Debug)] pub struct ClusterConf { @@ -64,6 +65,7 @@ pub enum UserAuth { KeyCertPath(String, String), KeyCertData(String, String), UserPass(String, String), + Exec(Exec), AuthProvider(AuthProvider), } @@ -82,6 +84,9 @@ impl From for UserConf { if let (Some(username), Some(password)) = (conf.username, conf.password) { auth_vec.push(UserAuth::UserPass(username, password)) } + if let Some(exec) = conf.exec { + auth_vec.push(UserAuth::Exec(exec)) + } if let (Some(client_cert_path), Some(key_path)) = (conf.client_cert, conf.client_key) { @@ -336,6 +341,9 @@ impl Config { KlusterAuth::with_userpass(username, password) ) } + &UserAuth::Exec(ref exec) => { + auth = Some(KlusterAuth::with_exec(exec.clone())) + } &UserAuth::AuthProvider(ref provider) => { provider.copy_up(); auth = diff --git a/src/config/kubefile.rs b/src/config/kubefile.rs index 250b76e..54ee939 100644 --- a/src/config/kubefile.rs +++ b/src/config/kubefile.rs @@ -21,9 +21,11 @@ use serde_json::{self, Value}; use serde_yaml; use std::cell::RefCell; +use std::collections::HashMap; use std::error::Error; use std::fs::File; use std::io; +use std::process::Command; //use error::{KubeErrNo, KubeError}; //use kube::{Kluster, KlusterAuth}; @@ -92,6 +94,7 @@ pub struct UserConf { pub username: Option, pub password: Option, + pub exec: Option, #[serde(rename = "auth-provider")] pub auth_provider: Option, } @@ -104,6 +107,91 @@ pub struct ContextConf { pub user: String, } +#[derive(Debug, Deserialize, Clone)] +pub struct Exec { + #[serde(rename = "apiVersion")] + api_version: String, + pub args: Option>, + pub command: Option, + pub env: Option>, + pub token: RefCell>, + pub expiry: RefCell>>, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Env { + name: String, + value: String, +} + +#[derive(Serialize, Deserialize)] +struct Spec {} + +#[derive(Serialize, Deserialize)] +struct Status { + token: String, +} + +#[derive(Serialize, Deserialize)] +struct AWSCredential { + kind: String, + #[serde(rename = "apiVersion")] + api_version: String, + spec: Spec, + status: Status, +} + +impl Exec { + // true if expiry is 10 minutes ago or more + fn check_dt(&self, expiry: DateTime) -> bool { + let etime = expiry.with_timezone(&Utc); + let now = Utc::now(); + let diff = now.signed_duration_since(etime); + return diff.num_minutes() >= 10; + } + + fn is_expired(&self) -> bool { + let expiry = self.expiry.borrow(); + match *expiry { + Some(e) => { + self.check_dt(e) + } + None => { + true + } + } + } + + fn generate_token(&self) -> String { + let mut filtered_env: HashMap = HashMap::new(); + for e in self.env.clone().unwrap() { + filtered_env.insert(e.name, e.value); + } + + let output = Command::new(self.command.clone().unwrap()) + .args(self.args.clone().unwrap()) + .envs(filtered_env) + .output() + .expect("failed to execute process"); + + let out: String = String::from_utf8_lossy(&output.stdout).to_string(); + let v: AWSCredential = serde_json::from_str(&out).unwrap(); + let token = v.status.token; + return token; + } + + /// Checks that we have a valid token, and if not, attempts to update it based on the config + pub fn ensure_token(&self) -> Option { + let mut token = self.token.borrow_mut(); + if self.is_expired() { + let mut expiry = self.expiry.borrow_mut(); + *token = Some(self.generate_token()); + let v = Utc::now(); + *expiry = Some(v) + } + token.clone() + } +} // Classes to hold deserialized data for auth #[derive(Debug, Deserialize, Clone)] diff --git a/src/config/mod.rs b/src/config/mod.rs index 95fc423..6dbb143 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -11,4 +11,5 @@ pub use self::click::EditMode; pub use self::kube::Config; pub use self::kubefile::AuthProvider; -pub use self::kubefile::ContextConf; +pub use self::kubefile::Exec; +pub use self::kubefile::ContextConf; \ No newline at end of file diff --git a/src/kube.rs b/src/kube.rs index 993275a..7c1be8a 100644 --- a/src/kube.rs +++ b/src/kube.rs @@ -38,6 +38,7 @@ use std::sync::Arc; use std::time::Duration; use config::AuthProvider; +use config::Exec; use connector::ClickSslConnector; use error::{KubeErrNo, KubeError}; @@ -345,6 +346,7 @@ pub struct JobList { pub enum KlusterAuth { Token(String), UserPass(String, String), + Exec(Exec), AuthProvider(AuthProvider), } @@ -357,6 +359,10 @@ impl KlusterAuth { KlusterAuth::UserPass(user.to_owned(), pass.to_owned()) } + pub fn with_exec(exec: Exec) -> KlusterAuth { + KlusterAuth::Exec(exec.to_owned()) + } + pub fn with_auth_provider(auth_provider: AuthProvider) -> KlusterAuth { KlusterAuth::AuthProvider(auth_provider) } @@ -487,6 +493,15 @@ impl Kluster { } } } + Some(KlusterAuth::Exec(ref exec)) => { + match exec.ensure_token() { + Some(token) => req.header(Authorization(Bearer { token: token })), + None => { + print_token_err(); + req + } + } + } Some(KlusterAuth::UserPass(ref user, ref pass)) => req.header(Authorization(Basic { username: user.clone(), password: Some(pass.clone()), @@ -578,6 +593,13 @@ impl Kluster { None => print_token_err(), } } + Some(KlusterAuth::Exec(ref _exec)) => { + match _exec.ensure_token() { + Some(token) => headers.set( + Authorization(Bearer { token: token })), + None => print_token_err(), + } + } Some(KlusterAuth::UserPass(ref user, ref pass)) => { headers.set(Authorization(Basic { username: user.clone(),