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) => {
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,
None => {
println!("Passwords do not match");

View file

@ -254,7 +254,7 @@ async fn do_password(
password.to_owned()
}
None => dialoguer::Password::new()
.with_prompt("Enter password: ")
.with_prompt("Enter password")
.interact()
.unwrap_or_else(|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;
fn encode(&mut self, msg: ClientRequest, dst: &mut BytesMut) -> Result<(), Self::Error> {
let data = serde_json::to_vec(&msg).map_err(|e| {
fn encode(&mut self, msg: &ClientRequest, dst: &mut BytesMut) -> Result<(), Self::Error> {
let data = serde_json::to_vec(msg).map_err(|e| {
error!("socket encoding error -> {:?}", e);
IoError::new(ErrorKind::Other, "JSON encode error")
})?;
@ -49,21 +49,36 @@ impl ClientCodec {
}
}
async fn call_daemon_inner(
path: &str,
req: ClientRequest,
) -> Result<ClientResponse, Box<dyn Error>> {
trace!(?path, ?req);
let stream = UnixStream::connect(path).await?;
pub struct DaemonClient {
req_stream: Framed<UnixStream, ClientCodec>,
default_timeout: u64,
}
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");
let mut reqs = Framed::new(stream, ClientCodec::new());
Ok(DaemonClient {
req_stream,
default_timeout,
})
}
reqs.send(req).await?;
reqs.flush().await?;
async fn call_inner(&mut self, req: &ClientRequest) -> Result<ClientResponse, Box<dyn Error>> {
self.req_stream.send(req).await?;
self.req_stream.flush().await?;
trace!("flushed, waiting ...");
match reqs.next().await {
match self.req_stream.next().await {
Some(Ok(res)) => {
debug!("Response -> {:?}", res);
Ok(res)
@ -73,15 +88,14 @@ async fn call_daemon_inner(
Err(Box::new(IoError::new(ErrorKind::Other, "oh no!")))
}
}
}
}
/// Makes a call to kanidm_unixd via a unix socket at `path`
pub async fn call_daemon(
path: &str,
req: ClientRequest,
timeout: u64,
) -> Result<ClientResponse, Box<dyn Error>> {
let sleep = time::sleep(Duration::from_secs(timeout));
pub async fn call(
&mut self,
req: &ClientRequest,
timeout: Option<u64>,
) -> Result<ClientResponse, Box<dyn Error>> {
let sleep = time::sleep(Duration::from_secs(timeout.unwrap_or(self.default_timeout)));
tokio::pin!(sleep);
tokio::select! {
@ -89,8 +103,9 @@ pub async fn call_daemon(
error!(?timeout, "Timed out making request to kanidm_unixd");
Err(Box::new(IoError::new(ErrorKind::Other, "timeout")))
}
res = call_daemon_inner(path, req) => {
res = self.call_inner(req) => {
res
}
}
}
}

View file

@ -16,17 +16,55 @@ extern crate tracing;
use std::process::ExitCode;
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::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{
ClientRequest, ClientResponse, PamAuthRequest, PamAuthResponse, PamServiceInfo,
};
// use std::io;
use std::path::PathBuf;
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")]
async fn main() -> ExitCode {
let opt = KanidmUnixParser::parse();
@ -54,12 +92,7 @@ async fn main() -> ExitCode {
} => {
debug!("Starting PAM auth tester tool ...");
let Ok(cfg) =
KanidmUnixdConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH)
else {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
};
let mut daemon_client = setup_client!();
info!("Sending request for user {}", &account_id);
@ -72,7 +105,7 @@ async fn main() -> ExitCode {
},
};
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 {
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Success) => {
println!("auth success!");
@ -90,7 +123,7 @@ async fn main() -> ExitCode {
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Password) => {
// Prompt for and get the password
let cred = match dialoguer::Password::new()
.with_prompt("Enter Unix password: ")
.with_prompt("Enter Unix password")
.interact()
{
Ok(p) => p,
@ -133,7 +166,7 @@ async fn main() -> ExitCode {
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 {
ClientResponse::PamStatus(Some(true)) => {
println!("account success!");
@ -158,15 +191,7 @@ async fn main() -> ExitCode {
KanidmUnixOpt::CacheClear { debug: _, really } => {
debug!("Starting cache clear tool ...");
let cfg = match KanidmUnixdConfig::new()
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
{
Ok(c) => c,
Err(_e) => {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
}
};
let mut daemon_client = setup_client!();
if !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;
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 {
ClientResponse::Ok => info!("success"),
_ => {
@ -192,19 +217,11 @@ async fn main() -> ExitCode {
KanidmUnixOpt::CacheInvalidate { debug: _ } => {
debug!("Starting cache invalidate tool ...");
let cfg = match KanidmUnixdConfig::new()
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
{
Ok(c) => c,
Err(_e) => {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
}
};
let mut daemon_client = setup_client!();
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 {
ClientResponse::Ok => info!("success"),
_ => {
@ -221,26 +238,10 @@ async fn main() -> ExitCode {
KanidmUnixOpt::Status { debug: _ } => {
trace!("Starting cache status tool ...");
let cfg = match KanidmUnixdConfig::new()
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
{
Ok(c) => c,
Err(_e) => {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;
}
};
let mut daemon_client = setup_client!();
let req = ClientRequest::Status;
let spath = PathBuf::from(cfg.sock_path.as_str());
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 {
match daemon_client.call(&req, None).await {
Ok(r) => match r {
ClientResponse::ProviderStatus(results) => {
for provider in results {
@ -259,7 +260,6 @@ async fn main() -> ExitCode {
error!("Error -> {:?}", e);
}
}
}
ExitCode::SUCCESS
}
KanidmUnixOpt::Version { debug: _ } => {

View file

@ -17,7 +17,7 @@ use std::path::PathBuf;
use std::process::ExitCode;
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::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse};
@ -66,21 +66,37 @@ async fn main() -> ExitCode {
);
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
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 {
Ok(r) => match r {
ClientResponse::SshKeys(sk) => sk.iter().for_each(|k| {
match daemon_client.call(&req, None).await {
Ok(ClientResponse::SshKeys(sk)) => {
sk.iter().for_each(|k| {
println!("{}", k);
}),
_ => {
error!("Error calling kanidm_unixd: unexpected response -> {:?}", r);
});
ExitCode::SUCCESS
}
Ok(r) => {
error!("Error calling kanidm_unixd: unexpected response -> {:?}", r);
ExitCode::FAILURE
}
},
Err(e) => {
error!("Error calling kanidm_unixd -> {:?}", e);
ExitCode::FAILURE
}
}
};
ExitCode::SUCCESS
}