Resolve kanidm-unix auth-test bug (#3405)

* Resolve kanidm-unix auth-test bug

When reworking the unix daemon, we missed changing the auth-test
tool to handle the new challenge-response flow correctly which
would cause the session to disconnect.

* Cleanup
This commit is contained in:
Firstyear 2025-02-09 12:49:54 +10:00 committed by GitHub
parent f68906bf1b
commit 1f5ce2617d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 149 additions and 118 deletions

View file

@ -162,7 +162,7 @@ impl PersonOpt {
} }
PersonPosix::SetPassword(aopt) => { PersonPosix::SetPassword(aopt) => {
let client = aopt.copt.to_client(OpType::Write).await; let client = aopt.copt.to_client(OpType::Write).await;
let password = match password_prompt("Enter new posix (sudo) password: ") { let password = match password_prompt("Enter new posix (sudo) password") {
Some(v) => v, Some(v) => v,
None => { None => {
println!("Passwords do not match"); println!("Passwords do not match");

View file

@ -254,7 +254,7 @@ async fn do_password(
password.to_owned() password.to_owned()
} }
None => dialoguer::Password::new() None => dialoguer::Password::new()
.with_prompt("Enter password: ") .with_prompt("Enter password")
.interact() .interact()
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
error!("Failed to create password prompt -- {:?}", e); error!("Failed to create password prompt -- {:?}", e);

View file

@ -29,11 +29,11 @@ impl Decoder for ClientCodec {
} }
} }
impl Encoder<ClientRequest> for ClientCodec { impl Encoder<&ClientRequest> for ClientCodec {
type Error = IoError; type Error = IoError;
fn encode(&mut self, msg: ClientRequest, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, msg: &ClientRequest, dst: &mut BytesMut) -> Result<(), Self::Error> {
let data = serde_json::to_vec(&msg).map_err(|e| { let data = serde_json::to_vec(msg).map_err(|e| {
error!("socket encoding error -> {:?}", e); error!("socket encoding error -> {:?}", e);
IoError::new(ErrorKind::Other, "JSON encode error") IoError::new(ErrorKind::Other, "JSON encode error")
})?; })?;
@ -49,21 +49,36 @@ impl ClientCodec {
} }
} }
async fn call_daemon_inner( pub struct DaemonClient {
path: &str, req_stream: Framed<UnixStream, ClientCodec>,
req: ClientRequest, default_timeout: u64,
) -> Result<ClientResponse, Box<dyn Error>> { }
trace!(?path, ?req);
let stream = UnixStream::connect(path).await?; impl DaemonClient {
pub async fn new(path: &str, default_timeout: u64) -> Result<Self, Box<dyn Error>> {
trace!(?path);
let stream = UnixStream::connect(path).await.inspect_err(|e| {
error!(
"Unix socket stream setup error while connecting to {} -> {:?}",
path, e
);
})?;
let req_stream = Framed::new(stream, ClientCodec::new());
trace!("connected"); trace!("connected");
let mut reqs = Framed::new(stream, ClientCodec::new()); Ok(DaemonClient {
req_stream,
default_timeout,
})
}
reqs.send(req).await?; async fn call_inner(&mut self, req: &ClientRequest) -> Result<ClientResponse, Box<dyn Error>> {
reqs.flush().await?; self.req_stream.send(req).await?;
self.req_stream.flush().await?;
trace!("flushed, waiting ..."); trace!("flushed, waiting ...");
match self.req_stream.next().await {
match reqs.next().await {
Some(Ok(res)) => { Some(Ok(res)) => {
debug!("Response -> {:?}", res); debug!("Response -> {:?}", res);
Ok(res) Ok(res)
@ -75,13 +90,12 @@ async fn call_daemon_inner(
} }
} }
/// Makes a call to kanidm_unixd via a unix socket at `path` pub async fn call(
pub async fn call_daemon( &mut self,
path: &str, req: &ClientRequest,
req: ClientRequest, timeout: Option<u64>,
timeout: u64,
) -> Result<ClientResponse, Box<dyn Error>> { ) -> Result<ClientResponse, Box<dyn Error>> {
let sleep = time::sleep(Duration::from_secs(timeout)); let sleep = time::sleep(Duration::from_secs(timeout.unwrap_or(self.default_timeout)));
tokio::pin!(sleep); tokio::pin!(sleep);
tokio::select! { tokio::select! {
@ -89,8 +103,9 @@ pub async fn call_daemon(
error!(?timeout, "Timed out making request to kanidm_unixd"); error!(?timeout, "Timed out making request to kanidm_unixd");
Err(Box::new(IoError::new(ErrorKind::Other, "timeout"))) Err(Box::new(IoError::new(ErrorKind::Other, "timeout")))
} }
res = call_daemon_inner(path, req) => { res = self.call_inner(req) => {
res res
} }
} }
} }
}

View file

@ -16,17 +16,55 @@ extern crate tracing;
use std::process::ExitCode; use std::process::ExitCode;
use clap::Parser; use clap::Parser;
use kanidm_unix_common::client::call_daemon; use kanidm_unix_common::client::DaemonClient;
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::unix_config::KanidmUnixdConfig; use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{ use kanidm_unix_common::unix_proto::{
ClientRequest, ClientResponse, PamAuthRequest, PamAuthResponse, PamServiceInfo, ClientRequest, ClientResponse, PamAuthRequest, PamAuthResponse, PamServiceInfo,
}; };
// use std::io;
use std::path::PathBuf; use std::path::PathBuf;
include!("../opt/tool.rs"); include!("../opt/tool.rs");
macro_rules! setup_client {
() => {{
let Ok(cfg) =
KanidmUnixdConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH)
else {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
};
debug!("Connecting to resolver ...");
debug!(
"Using kanidm_unixd socket path: {:?}",
cfg.sock_path.as_str()
);
// see if the kanidm_unixd socket exists and quit if not
if !PathBuf::from(&cfg.sock_path).exists() {
error!(
"Failed to find unix socket at {}, quitting!",
cfg.sock_path.as_str()
);
return ExitCode::FAILURE;
}
match DaemonClient::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout).await {
Ok(dc) => dc,
Err(err) => {
error!(
"Failed to connect to resolver at {}-> {:?}",
cfg.sock_path.as_str(),
err
);
return ExitCode::FAILURE;
}
}
}};
}
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> ExitCode { async fn main() -> ExitCode {
let opt = KanidmUnixParser::parse(); let opt = KanidmUnixParser::parse();
@ -54,12 +92,7 @@ async fn main() -> ExitCode {
} => { } => {
debug!("Starting PAM auth tester tool ..."); debug!("Starting PAM auth tester tool ...");
let Ok(cfg) = let mut daemon_client = setup_client!();
KanidmUnixdConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH)
else {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
};
info!("Sending request for user {}", &account_id); info!("Sending request for user {}", &account_id);
@ -72,7 +105,7 @@ async fn main() -> ExitCode {
}, },
}; };
loop { loop {
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await { match daemon_client.call(&req, None).await {
Ok(r) => match r { Ok(r) => match r {
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Success) => { ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Success) => {
println!("auth success!"); println!("auth success!");
@ -90,7 +123,7 @@ async fn main() -> ExitCode {
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Password) => { ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Password) => {
// Prompt for and get the password // Prompt for and get the password
let cred = match dialoguer::Password::new() let cred = match dialoguer::Password::new()
.with_prompt("Enter Unix password: ") .with_prompt("Enter Unix password")
.interact() .interact()
{ {
Ok(p) => p, Ok(p) => p,
@ -133,7 +166,7 @@ async fn main() -> ExitCode {
let sereq = ClientRequest::PamAccountAllowed(account_id); let sereq = ClientRequest::PamAccountAllowed(account_id);
match call_daemon(cfg.sock_path.as_str(), sereq, cfg.unix_sock_timeout).await { match daemon_client.call(&sereq, None).await {
Ok(r) => match r { Ok(r) => match r {
ClientResponse::PamStatus(Some(true)) => { ClientResponse::PamStatus(Some(true)) => {
println!("account success!"); println!("account success!");
@ -158,15 +191,7 @@ async fn main() -> ExitCode {
KanidmUnixOpt::CacheClear { debug: _, really } => { KanidmUnixOpt::CacheClear { debug: _, really } => {
debug!("Starting cache clear tool ..."); debug!("Starting cache clear tool ...");
let cfg = match KanidmUnixdConfig::new() let mut daemon_client = setup_client!();
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
{
Ok(c) => c,
Err(_e) => {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
}
};
if !really { if !really {
error!("Are you sure you want to proceed? If so use --really"); error!("Are you sure you want to proceed? If so use --really");
@ -175,7 +200,7 @@ async fn main() -> ExitCode {
let req = ClientRequest::ClearCache; let req = ClientRequest::ClearCache;
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await { match daemon_client.call(&req, None).await {
Ok(r) => match r { Ok(r) => match r {
ClientResponse::Ok => info!("success"), ClientResponse::Ok => info!("success"),
_ => { _ => {
@ -192,19 +217,11 @@ async fn main() -> ExitCode {
KanidmUnixOpt::CacheInvalidate { debug: _ } => { KanidmUnixOpt::CacheInvalidate { debug: _ } => {
debug!("Starting cache invalidate tool ..."); debug!("Starting cache invalidate tool ...");
let cfg = match KanidmUnixdConfig::new() let mut daemon_client = setup_client!();
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
{
Ok(c) => c,
Err(_e) => {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
}
};
let req = ClientRequest::InvalidateCache; let req = ClientRequest::InvalidateCache;
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await { match daemon_client.call(&req, None).await {
Ok(r) => match r { Ok(r) => match r {
ClientResponse::Ok => info!("success"), ClientResponse::Ok => info!("success"),
_ => { _ => {
@ -221,26 +238,10 @@ async fn main() -> ExitCode {
KanidmUnixOpt::Status { debug: _ } => { KanidmUnixOpt::Status { debug: _ } => {
trace!("Starting cache status tool ..."); trace!("Starting cache status tool ...");
let cfg = match KanidmUnixdConfig::new() let mut daemon_client = setup_client!();
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
{
Ok(c) => c,
Err(_e) => {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
}
};
let req = ClientRequest::Status; let req = ClientRequest::Status;
let spath = PathBuf::from(cfg.sock_path.as_str()); match daemon_client.call(&req, None).await {
if !spath.exists() {
error!(
"kanidm_unixd socket {} does not exist - is the service running?",
cfg.sock_path
)
} else {
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await {
Ok(r) => match r { Ok(r) => match r {
ClientResponse::ProviderStatus(results) => { ClientResponse::ProviderStatus(results) => {
for provider in results { for provider in results {
@ -259,7 +260,6 @@ async fn main() -> ExitCode {
error!("Error -> {:?}", e); error!("Error -> {:?}", e);
} }
} }
}
ExitCode::SUCCESS ExitCode::SUCCESS
} }
KanidmUnixOpt::Version { debug: _ } => { KanidmUnixOpt::Version { debug: _ } => {

View file

@ -17,7 +17,7 @@ use std::path::PathBuf;
use std::process::ExitCode; use std::process::ExitCode;
use clap::Parser; use clap::Parser;
use kanidm_unix_common::client::call_daemon; use kanidm_unix_common::client::DaemonClient;
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::unix_config::KanidmUnixdConfig; use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse}; use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse};
@ -66,21 +66,37 @@ async fn main() -> ExitCode {
); );
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
let mut daemon_client =
match DaemonClient::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout).await {
Ok(dc) => dc,
Err(err) => {
error!(
"Failed to connect to resolver at {}-> {:?}",
cfg.sock_path.as_str(),
err
);
return ExitCode::FAILURE;
}
};
// safe because we've already thrown an error if it's not there // safe because we've already thrown an error if it's not there
let req = ClientRequest::SshKey(opt.account_id.unwrap_or("".to_string())); let req = ClientRequest::SshKey(opt.account_id.unwrap_or("".to_string()));
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await { match daemon_client.call(&req, None).await {
Ok(r) => match r { Ok(ClientResponse::SshKeys(sk)) => {
ClientResponse::SshKeys(sk) => sk.iter().for_each(|k| { sk.iter().for_each(|k| {
println!("{}", k); println!("{}", k);
}), });
_ => {
error!("Error calling kanidm_unixd: unexpected response -> {:?}", r);
}
},
Err(e) => {
error!("Error calling kanidm_unixd -> {:?}", e);
}
};
ExitCode::SUCCESS ExitCode::SUCCESS
} }
Ok(r) => {
error!("Error calling kanidm_unixd: unexpected response -> {:?}", r);
ExitCode::FAILURE
}
Err(e) => {
error!("Error calling kanidm_unixd -> {:?}", e);
ExitCode::FAILURE
}
}
}