From c2aa2ee7c1a4ca27b0d2a4e582bac41823b5dac2 Mon Sep 17 00:00:00 2001 From: slyoldfox Date: Wed, 5 Jun 2019 15:14:18 +0200 Subject: [PATCH 1/4] #89 updated PR - added caching --- src/config/kube.rs | 30 ++++++++----- src/config/kubefile.rs | 99 +++++++++++++++++++++++++++++++++++++++--- src/config/mod.rs | 16 +++---- src/kube.rs | 38 ++++++++++++---- 4 files changed, 151 insertions(+), 32 deletions(-) diff --git a/src/config/kube.rs b/src/config/kube.rs index 0d2c5d7..42e2e46 100644 --- a/src/config/kube.rs +++ b/src/config/kube.rs @@ -15,20 +15,21 @@ //! Code to represent the data found in .kube/config files after it's deserialized, validated, and //! so on. Data in here is what gets passed around to the rest of Click. -use std::collections::HashMap; -use std::convert::From; -use std::env; -use std::error::Error; -use std::fs::File; -use std::io::{BufReader, Read}; + use std::collections::HashMap; + use std::convert::From; + use std::env; + use std::error::Error; + use std::fs::File; + use std::io::{BufReader, Read}; -use error::{KubeErrNo, KubeError}; -use kube::{ClientCertKey, Kluster, KlusterAuth}; -use certs::{get_cert, get_cert_from_pem, get_key_from_str, get_private_key}; + use certs::{get_cert, get_cert_from_pem, get_key_from_str, get_private_key}; + use error::{KubeErrNo, KubeError}; + use kube::{ClientCertKey, Kluster, KlusterAuth}; -use super::kubefile::AuthProvider; + use super::kubefile::AuthProvider; + use super::kubefile::Exec; -#[derive(Debug)] + #[derive(Debug)] pub struct ClusterConf { pub cert: Option, pub server: String, @@ -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..d0231a8 100644 --- a/src/config/kubefile.rs +++ b/src/config/kubefile.rs @@ -14,17 +14,19 @@ //! Code to handle reading and representing .kube/config files. +use std::cell::RefCell; +use std::collections::HashMap; +use std::error::Error; +use std::fs::File; +use std::io; +use std::process::Command; + use chrono::{DateTime, Local, TimeZone}; use chrono::offset::Utc; use duct::cmd; use serde_json::{self, Value}; use serde_yaml; -use std::cell::RefCell; -use std::error::Error; -use std::fs::File; -use std::io; - //use error::{KubeErrNo, KubeError}; //use kube::{Kluster, KlusterAuth}; //use certs::{get_cert, get_cert_from_pem, get_key_from_str, get_private_key}; @@ -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,92 @@ pub struct ContextConf { pub user: String, } +#[derive(Debug, Deserialize, Clone)] +#[allow(non_snake_case)] +pub struct Exec { + apiVersion: String, + pub args: Option>, + pub command: Option, + pub env: Option>, + //TODO:marc other place ? + 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)] +#[allow(non_snake_case)] +struct AWSCredential { + kind: String, + apiVersion: 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..d36f597 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,14 +1,14 @@ -mod click; -mod kube; -mod kubefile; - - pub use self::click::Alias; -pub use self::click::CompletionType; pub use self::click::ClickConfig; +pub use self::click::CompletionType; 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; + +mod click; +mod kube; +mod kubefile; + + diff --git a/src/kube.rs b/src/kube.rs index 993275a..94568f8 100644 --- a/src/kube.rs +++ b/src/kube.rs @@ -14,6 +14,13 @@ //! Dealing with various kubernetes api calls +use std::fmt; +use std::io::BufReader; +use std::net::IpAddr; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; + use ansi_term::Colour::{Green, Red, Yellow}; use chrono::DateTime; use chrono::offset::Utc; @@ -25,19 +32,13 @@ use hyper::header::{Authorization, Basic, Bearer}; use hyper::method::Method; use hyper::status::StatusCode; use hyper_sync_rustls::TlsClient; +use rustls::{self, Certificate, PrivateKey}; use serde::Deserialize; use serde_json; use serde_json::{Map, Value}; -use rustls::{self, Certificate, PrivateKey}; - -use std::fmt; -use std::io::BufReader; -use std::net::IpAddr; -use std::str::FromStr; -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(), From 14e9816b00a3c51008b3d0d0eaff1e92f5e3fe01 Mon Sep 17 00:00:00 2001 From: slyoldfox Date: Wed, 5 Jun 2019 15:24:35 +0200 Subject: [PATCH 2/4] formatting --- src/config/kube.rs | 24 ++++++++++++------------ src/config/kubefile.rs | 13 ++++++------- src/config/mod.rs | 17 +++++++++-------- src/kube.rs | 16 ++++++++-------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/config/kube.rs b/src/config/kube.rs index 42e2e46..195008f 100644 --- a/src/config/kube.rs +++ b/src/config/kube.rs @@ -15,21 +15,21 @@ //! Code to represent the data found in .kube/config files after it's deserialized, validated, and //! so on. Data in here is what gets passed around to the rest of Click. - use std::collections::HashMap; - use std::convert::From; - use std::env; - use std::error::Error; - use std::fs::File; - use std::io::{BufReader, Read}; +use std::collections::HashMap; +use std::convert::From; +use std::env; +use std::error::Error; +use std::fs::File; +use std::io::{BufReader, Read}; - use certs::{get_cert, get_cert_from_pem, get_key_from_str, get_private_key}; - use error::{KubeErrNo, KubeError}; - use kube::{ClientCertKey, Kluster, KlusterAuth}; +use error::{KubeErrNo, KubeError}; +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; +use super::kubefile::AuthProvider; +use super::kubefile::Exec; - #[derive(Debug)] +#[derive(Debug)] pub struct ClusterConf { pub cert: Option, pub server: String, diff --git a/src/config/kubefile.rs b/src/config/kubefile.rs index d0231a8..b6a0694 100644 --- a/src/config/kubefile.rs +++ b/src/config/kubefile.rs @@ -14,6 +14,12 @@ //! Code to handle reading and representing .kube/config files. +use chrono::{DateTime, Local, TimeZone}; +use chrono::offset::Utc; +use duct::cmd; +use serde_json::{self, Value}; +use serde_yaml; + use std::cell::RefCell; use std::collections::HashMap; use std::error::Error; @@ -21,12 +27,6 @@ use std::fs::File; use std::io; use std::process::Command; -use chrono::{DateTime, Local, TimeZone}; -use chrono::offset::Utc; -use duct::cmd; -use serde_json::{self, Value}; -use serde_yaml; - //use error::{KubeErrNo, KubeError}; //use kube::{Kluster, KlusterAuth}; //use certs::{get_cert, get_cert_from_pem, get_key_from_str, get_private_key}; @@ -114,7 +114,6 @@ pub struct Exec { pub args: Option>, pub command: Option, pub env: Option>, - //TODO:marc other place ? pub token: RefCell>, pub expiry: RefCell>>, } diff --git a/src/config/mod.rs b/src/config/mod.rs index d36f597..6dbb143 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,14 +1,15 @@ +mod click; +mod kube; +mod kubefile; + + pub use self::click::Alias; -pub use self::click::ClickConfig; pub use self::click::CompletionType; +pub use self::click::ClickConfig; 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; - -mod click; -mod kube; -mod kubefile; - - +pub use self::kubefile::ContextConf; \ No newline at end of file diff --git a/src/kube.rs b/src/kube.rs index 94568f8..7c1be8a 100644 --- a/src/kube.rs +++ b/src/kube.rs @@ -14,13 +14,6 @@ //! Dealing with various kubernetes api calls -use std::fmt; -use std::io::BufReader; -use std::net::IpAddr; -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; - use ansi_term::Colour::{Green, Red, Yellow}; use chrono::DateTime; use chrono::offset::Utc; @@ -32,10 +25,17 @@ use hyper::header::{Authorization, Basic, Bearer}; use hyper::method::Method; use hyper::status::StatusCode; use hyper_sync_rustls::TlsClient; -use rustls::{self, Certificate, PrivateKey}; use serde::Deserialize; use serde_json; use serde_json::{Map, Value}; +use rustls::{self, Certificate, PrivateKey}; + +use std::fmt; +use std::io::BufReader; +use std::net::IpAddr; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; use config::AuthProvider; use config::Exec; From 82e66c1834ce682502411974d8bcc9170c785c22 Mon Sep 17 00:00:00 2001 From: slyoldfox Date: Thu, 25 Jul 2019 20:20:18 +0200 Subject: [PATCH 3/4] _exec -> exec --- src/config/kube.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/kube.rs b/src/config/kube.rs index 195008f..d39c1fe 100644 --- a/src/config/kube.rs +++ b/src/config/kube.rs @@ -341,8 +341,8 @@ impl Config { KlusterAuth::with_userpass(username, password) ) } - &UserAuth::Exec(ref _exec) => { - auth = Some(KlusterAuth::with_exec(_exec.clone())) + &UserAuth::Exec(ref exec) => { + auth = Some(KlusterAuth::with_exec(exec.clone())) } &UserAuth::AuthProvider(ref provider) => { provider.copy_up(); From 72b8a87ee8edcbe1c0e6f398e95768372ee8ed43 Mon Sep 17 00:00:00 2001 From: slyoldfox Date: Thu, 25 Jul 2019 20:54:44 +0200 Subject: [PATCH 4/4] apiVersion -> api_version --- src/config/kubefile.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config/kubefile.rs b/src/config/kubefile.rs index b6a0694..54ee939 100644 --- a/src/config/kubefile.rs +++ b/src/config/kubefile.rs @@ -108,9 +108,9 @@ pub struct ContextConf { } #[derive(Debug, Deserialize, Clone)] -#[allow(non_snake_case)] pub struct Exec { - apiVersion: String, + #[serde(rename = "apiVersion")] + api_version: String, pub args: Option>, pub command: Option, pub env: Option>, @@ -133,10 +133,10 @@ struct Status { } #[derive(Serialize, Deserialize)] -#[allow(non_snake_case)] struct AWSCredential { kind: String, - apiVersion: String, + #[serde(rename = "apiVersion")] + api_version: String, spec: Spec, status: Status, }