CLI and kanidm_client changes to handle errors and TLS validation changes (#2127)

* pulling out exitcode, adding hyper dep to handle errors (was already transitively there due to reqwest)
* adding better error handling, more options for client things
This commit is contained in:
James Hodgkinson 2023-09-19 13:31:19 +10:00 committed by GitHub
parent 4aee3365aa
commit 9b2fab7bb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 138 additions and 47 deletions

8
Cargo.lock generated
View file

@ -1456,12 +1456,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "exitcode"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -2849,6 +2843,7 @@ dependencies = [
name = "kanidm_client" name = "kanidm_client"
version = "1.1.0-rc.14-dev" version = "1.1.0-rc.14-dev"
dependencies = [ dependencies = [
"hyper",
"kanidm_proto", "kanidm_proto",
"reqwest", "reqwest",
"serde", "serde",
@ -2917,7 +2912,6 @@ dependencies = [
"compact_jwt", "compact_jwt",
"cursive", "cursive",
"dialoguer", "dialoguer",
"exitcode",
"futures-concurrency", "futures-concurrency",
"kanidm_build_profiles", "kanidm_build_profiles",
"kanidm_client", "kanidm_client",

View file

@ -18,9 +18,16 @@ kanidm_proto = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
time = { workspace = true, features = ["serde", "std"] } time = { workspace = true, features = ["serde", "std"] }
tokio = { workspace = true, features = ["rt", "net", "time", "macros", "sync", "signal"] } tokio = { workspace = true, features = [
"rt",
"net",
"time",
"macros",
"sync",
"signal",
] }
toml = { workspace = true } toml = { workspace = true }
uuid = { workspace = true, features = ["serde", "v4"] } uuid = { workspace = true, features = ["serde", "v4"] }
url = { workspace = true, features = ["serde"] } url = { workspace = true, features = ["serde"] }
webauthn-rs-proto = { workspace = true, features = ["wasm"] } webauthn-rs-proto = { workspace = true, features = ["wasm"] }
hyper = { workspace = true }

View file

@ -66,6 +66,7 @@ pub enum ClientError {
SystemError, SystemError,
ConfigParseIssue(String), ConfigParseIssue(String),
CertParseIssue(String), CertParseIssue(String),
UntrustedCertificate(String),
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
@ -468,6 +469,23 @@ fn test_make_url() {
); );
} }
/// This is probably pretty jank but it works and was pulled from here:
/// <https://github.com/seanmonstar/reqwest/issues/1602#issuecomment-1220996681>
fn find_reqwest_error_source<E: std::error::Error + 'static>(
orig: &dyn std::error::Error,
) -> Option<&E> {
let mut cause = orig.source();
while let Some(err) = cause {
if let Some(typed) = err.downcast_ref::<E>() {
return Some(typed);
}
cause = err.source();
}
// else
None
}
impl KanidmClient { impl KanidmClient {
pub fn get_origin(&self) -> &Url { pub fn get_origin(&self) -> &Url {
&self.origin &self.origin
@ -541,6 +559,28 @@ impl KanidmClient {
*guard = false; *guard = false;
} }
/// You've got the response from a reqwest and you want to turn it into a `ClientError`
fn handle_response_error(&self, error: reqwest::Error) -> ClientError {
if error.is_connect() {
if find_reqwest_error_source::<std::io::Error>(&error).is_some() {
// TODO: one day handle IO errors better
trace!("Got an IO error! {:?}", &error);
return ClientError::Transport(error);
}
if let Some(hyper_error) = find_reqwest_error_source::<hyper::Error>(&error) {
// hyper errors can be *anything* depending on the underlying client libraries
// ref: https://github.com/hyperium/hyper/blob/9feb70e9249d9fb99634ec96f83566e6bb3b3128/src/error.rs#L26C2-L26C2
if format!("{:?}", hyper_error)
.to_lowercase()
.contains("certificate")
{
return ClientError::UntrustedCertificate(format!("{}", hyper_error));
}
}
}
ClientError::Transport(error)
}
async fn perform_simple_post_request<R: Serialize, T: DeserializeOwned>( async fn perform_simple_post_request<R: Serialize, T: DeserializeOwned>(
&self, &self,
dest: &str, dest: &str,
@ -554,7 +594,10 @@ impl KanidmClient {
.body(req_string) .body(req_string)
.header(CONTENT_TYPE, APPLICATION_JSON); .header(CONTENT_TYPE, APPLICATION_JSON);
let response = response.send().await.map_err(ClientError::Transport)?; let response = response
.send()
.await
.map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;
@ -617,7 +660,10 @@ impl KanidmClient {
} }
}; };
let response = response.send().await.map_err(ClientError::Transport)?; let response = response
.send()
.await
.map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;
@ -677,7 +723,10 @@ impl KanidmClient {
} }
}; };
let response = response.send().await.map_err(ClientError::Transport)?; let response = response
.send()
.await
.map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;
@ -730,7 +779,7 @@ impl KanidmClient {
.body(req_string) .body(req_string)
.send() .send()
.await .await
.map_err(ClientError::Transport)?; .map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;
@ -781,7 +830,10 @@ impl KanidmClient {
} }
}; };
let response = response.send().await.map_err(ClientError::Transport)?; let response = response
.send()
.await
.map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;
@ -825,7 +877,10 @@ impl KanidmClient {
} }
}; };
let response = response.send().await.map_err(ClientError::Transport)?; let response = response
.send()
.await
.map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;
@ -870,7 +925,10 @@ impl KanidmClient {
} }
}; };
let response = response.send().await.map_err(ClientError::Transport)?; let response = response
.send()
.await
.map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;
@ -920,7 +978,10 @@ impl KanidmClient {
} }
}; };
let response = response.send().await.map_err(ClientError::Transport)?; let response = response
.send()
.await
.map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;
@ -1390,7 +1451,10 @@ impl KanidmClient {
} }
}; };
let response = response.send().await.map_err(ClientError::Transport)?; let response = response
.send()
.await
.map_err(|err| self.handle_response_error(err))?;
self.expect_version(&response).await; self.expect_version(&response).await;

View file

@ -4,13 +4,13 @@ default-run = "kanidm"
description = "Kanidm Client Tools" description = "Kanidm Client Tools"
documentation = "https://kanidm.github.io/kanidm/stable/" documentation = "https://kanidm.github.io/kanidm/stable/"
version = { workspace=true } version = { workspace = true }
authors = { workspace=true } authors = { workspace = true }
rust-version = { workspace=true } rust-version = { workspace = true }
edition = { workspace=true } edition = { workspace = true }
license = { workspace=true } license = { workspace = true }
homepage = { workspace=true } homepage = { workspace = true }
repository = { workspace=true } repository = { workspace = true }
[features] [features]
default = ["unix"] default = ["unix"]
@ -34,26 +34,25 @@ path = "src/ssh_authorizedkeys.rs"
async-recursion = { workspace = true } async-recursion = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] } clap = { workspace = true, features = ["derive", "env"] }
compact_jwt = { workspace = true, features = ["openssl"] } compact_jwt = { workspace = true, features = ["openssl"] }
dialoguer = { workspace=true } dialoguer = { workspace = true }
futures-concurrency = { workspace=true } futures-concurrency = { workspace = true }
libc = { workspace=true } libc = { workspace = true }
kanidm_client = { workspace=true } kanidm_client = { workspace = true }
kanidm_proto = { workspace=true } kanidm_proto = { workspace = true }
qrcode = { workspace = true } qrcode = { workspace = true }
rpassword = { workspace=true } rpassword = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace=true } serde_json = { workspace = true }
shellexpand = { workspace=true } shellexpand = { workspace = true }
time = { workspace = true, features = ["serde", "std"] } time = { workspace = true, features = ["serde", "std"] }
tracing = { workspace=true } tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
tokio = { workspace = true, features = ["rt", "macros"] } tokio = { workspace = true, features = ["rt", "macros"] }
url = { workspace = true, features = ["serde"] } url = { workspace = true, features = ["serde"] }
uuid = { workspace=true } uuid = { workspace = true }
zxcvbn = { workspace=true } zxcvbn = { workspace = true }
lazy_static.workspace = true lazy_static.workspace = true
regex.workspace = true regex.workspace = true
exitcode = "1.1.2"
[dependencies.cursive] [dependencies.cursive]
version = "0.20.0" version = "0.20.0"
@ -63,9 +62,9 @@ features = ["crossterm-backend"]
[build-dependencies] [build-dependencies]
clap = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive"] }
clap_complete = { workspace=true } clap_complete = { workspace = true }
kanidm_build_profiles = { workspace = true } kanidm_build_profiles = { workspace = true }
uuid = { workspace=true } uuid = { workspace = true }
url = { workspace = true } url = { workspace = true }
[target."cfg(target_os = \"windows\")".dependencies.webauthn-authenticator-rs] [target."cfg(target_os = \"windows\")".dependencies.webauthn-authenticator-rs]

View file

@ -62,20 +62,32 @@ impl CommonOpt {
let client_builder = match ca_path { let client_builder = match ca_path {
Some(p) => { Some(p) => {
debug!("Adding trusted CA cert {:?}", p); debug!("Adding trusted CA cert {:?}", p);
client_builder let client_builder = client_builder
.add_root_certificate_filepath(p) .add_root_certificate_filepath(p)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
error!("Failed to add ca certificate -- {:?}", e); error!("Failed to add ca certificate -- {:?}", e);
std::process::exit(1); std::process::exit(1);
}) });
debug!(
"After attempting to add trusted CA cert, client builder state: {:?}",
client_builder
);
client_builder
} }
None => client_builder, None => client_builder,
}; };
debug!( let client_builder = match self.skip_hostname_verification {
"Post attempting to add trusted CA cert, client builder state: {:?}", true => {
client_builder warn!(
); "Accepting invalid hostnames on the certificate for {:?}",
&self.addr
);
client_builder.danger_accept_invalid_hostnames(true)
}
false => client_builder,
};
client_builder.build().unwrap_or_else(|e| { client_builder.build().unwrap_or_else(|e| {
error!("Failed to build client instance -- {:?}", e); error!("Failed to build client instance -- {:?}", e);
@ -176,7 +188,7 @@ impl CommonOpt {
match prompt_for_username_get_values() { match prompt_for_username_get_values() {
Ok(tuple) => tuple, Ok(tuple) => tuple,
Err(msg) => { Err(msg) => {
error!("{}", msg); error!("Error: {}", msg);
std::process::exit(1); std::process::exit(1);
} }
} }

View file

@ -56,13 +56,21 @@ pub(crate) fn handle_client_error(response: ClientError, _output_mode: &OutputMo
"Internal Server Error in response:{:?} {:?}", "Internal Server Error in response:{:?} {:?}",
error_msg, message error_msg, message
); );
std::process::exit(exitcode::SOFTWARE); std::process::exit(1);
} else if status == StatusCode::NOT_FOUND { } else if status == StatusCode::NOT_FOUND {
error!("Item not found:{:?} {:?}", error_msg, message) error!("Item not found:{:?} {:?}", error_msg, message)
} else { } else {
error!("HTTP Error: {}{} {:?}", status, error_msg, message); error!("HTTP Error: {}{} {:?}", status, error_msg, message);
} }
} }
ClientError::Transport(e) => {
error!("HTTP-Transport Related Error: {:?}", e);
std::process::exit(1);
}
ClientError::UntrustedCertificate(e) => {
error!("Untrusted Certificate Error: {:?}", e);
std::process::exit(1);
}
_ => { _ => {
eprintln!("{:?}", response); eprintln!("{:?}", response);
} }

View file

@ -49,6 +49,13 @@ pub struct CommonOpt {
/// Log format (still in very early development) /// Log format (still in very early development)
#[clap(short, long = "output", env = "KANIDM_OUTPUT", default_value = "text")] #[clap(short, long = "output", env = "KANIDM_OUTPUT", default_value = "text")]
output_mode: OutputMode, output_mode: OutputMode,
/// Skip hostname verification
#[clap(
long = "skip-hostname-verification",
env = "KANIDM_SKIP_HOSTNAME_VERIFICATION",
default_value_t = false
)]
skip_hostname_verification: bool,
} }
#[derive(Debug, Args)] #[derive(Debug, Args)]