mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
kanidm unixd mfa capabilities (#2672)
Improve the support for the resolver to support MFA options with pam. This enables async task spawning and cancelation via the resolver backend as well. Co-authored-by: David Mulder <dmulder@samba.org>
This commit is contained in:
parent
03ce2a0c32
commit
c09daa4643
|
@ -3181,7 +3181,12 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
|
||||||
.route("/v1/group/:id/_unix/_token", get(group_id_unix_token_get))
|
.route("/v1/group/:id/_unix/_token", get(group_id_unix_token_get))
|
||||||
.route("/v1/group/:id/_unix", post(group_id_unix_post))
|
.route("/v1/group/:id/_unix", post(group_id_unix_post))
|
||||||
.route("/v1/group", get(group_get).post(group_post))
|
.route("/v1/group", get(group_get).post(group_post))
|
||||||
.route("/v1/group/:id", get(group_id_get).patch(group_id_patch).delete(group_id_delete))
|
.route(
|
||||||
|
"/v1/group/:id",
|
||||||
|
get(group_id_get)
|
||||||
|
.patch(group_id_patch)
|
||||||
|
.delete(group_id_delete),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/v1/group/:id/_attr/:attr",
|
"/v1/group/:id/_attr/:attr",
|
||||||
delete(group_id_attr_delete)
|
delete(group_id_attr_delete)
|
||||||
|
|
|
@ -53,6 +53,9 @@ use tracing_subscriber::filter::LevelFilter;
|
||||||
use tracing_subscriber::fmt;
|
use tracing_subscriber::fmt;
|
||||||
use tracing_subscriber::prelude::*;
|
use tracing_subscriber::prelude::*;
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn get_cfg() -> Result<KanidmUnixdConfig, PamResultCode> {
|
pub fn get_cfg() -> Result<KanidmUnixdConfig, PamResultCode> {
|
||||||
KanidmUnixdConfig::new()
|
KanidmUnixdConfig::new()
|
||||||
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
|
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
|
||||||
|
@ -106,6 +109,38 @@ pub struct PamKanidm;
|
||||||
|
|
||||||
pam_hooks!(PamKanidm);
|
pam_hooks!(PamKanidm);
|
||||||
|
|
||||||
|
macro_rules! match_sm_auth_client_response {
|
||||||
|
($expr:expr, $opts:ident, $($pat:pat => $result:expr),*) => {
|
||||||
|
match $expr {
|
||||||
|
Ok(r) => match r {
|
||||||
|
$($pat => $result),*
|
||||||
|
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Success) => {
|
||||||
|
return PamResultCode::PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Denied) => {
|
||||||
|
return PamResultCode::PAM_AUTH_ERR;
|
||||||
|
}
|
||||||
|
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Unknown) => {
|
||||||
|
if $opts.ignore_unknown_user {
|
||||||
|
return PamResultCode::PAM_IGNORE;
|
||||||
|
} else {
|
||||||
|
return PamResultCode::PAM_USER_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// unexpected response.
|
||||||
|
error!(err = ?r, "PAM_IGNORE, unexpected resolver response");
|
||||||
|
return PamResultCode::PAM_IGNORE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, "PAM_IGNORE");
|
||||||
|
return PamResultCode::PAM_IGNORE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PamHooks for PamKanidm {
|
impl PamHooks for PamKanidm {
|
||||||
fn acct_mgmt(pamh: &PamHandle, args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {
|
fn acct_mgmt(pamh: &PamHandle, args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {
|
||||||
let opts = match Options::try_from(&args) {
|
let opts = match Options::try_from(&args) {
|
||||||
|
@ -241,85 +276,129 @@ impl PamHooks for PamKanidm {
|
||||||
let mut req = ClientRequest::PamAuthenticateInit(account_id);
|
let mut req = ClientRequest::PamAuthenticateInit(account_id);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match daemon_client.call_and_wait(&req, timeout) {
|
match_sm_auth_client_response!(daemon_client.call_and_wait(&req, timeout), opts,
|
||||||
Ok(r) => match r {
|
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Password) => {
|
||||||
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Success) => {
|
let mut consume_authtok = None;
|
||||||
return PamResultCode::PAM_SUCCESS;
|
// Swap the authtok out with a None, so it can only be consumed once.
|
||||||
}
|
// If it's already been swapped, we are just swapping two null pointers
|
||||||
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Denied) => {
|
// here effectively.
|
||||||
return PamResultCode::PAM_AUTH_ERR;
|
std::mem::swap(&mut authtok, &mut consume_authtok);
|
||||||
}
|
let cred = if let Some(cred) = consume_authtok {
|
||||||
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Unknown) => {
|
cred
|
||||||
if opts.ignore_unknown_user {
|
} else {
|
||||||
return PamResultCode::PAM_IGNORE;
|
match conv.send(PAM_PROMPT_ECHO_OFF, "Password: ") {
|
||||||
} else {
|
Ok(password) => match password {
|
||||||
return PamResultCode::PAM_USER_UNKNOWN;
|
Some(cred) => cred,
|
||||||
}
|
None => {
|
||||||
}
|
debug!("no password");
|
||||||
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Password) => {
|
return PamResultCode::PAM_CRED_INSUFFICIENT;
|
||||||
let mut consume_authtok = None;
|
|
||||||
// Swap the authtok out with a None, so it can only be consumed once.
|
|
||||||
// If it's already been swapped, we are just swapping two null pointers
|
|
||||||
// here effectively.
|
|
||||||
std::mem::swap(&mut authtok, &mut consume_authtok);
|
|
||||||
let cred = if let Some(cred) = consume_authtok {
|
|
||||||
cred
|
|
||||||
} else {
|
|
||||||
match conv.send(PAM_PROMPT_ECHO_OFF, "Password: ") {
|
|
||||||
Ok(password) => match password {
|
|
||||||
Some(cred) => cred,
|
|
||||||
None => {
|
|
||||||
debug!("no password");
|
|
||||||
return PamResultCode::PAM_CRED_INSUFFICIENT;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
debug!("unable to get password");
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
|
||||||
|
|
||||||
// Now setup the request for the next loop.
|
|
||||||
timeout = cfg.unix_sock_timeout;
|
|
||||||
req = ClientRequest::PamAuthenticateStep(PamAuthRequest::Password { cred });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ClientResponse::PamAuthenticateStepResponse(
|
|
||||||
PamAuthResponse::DeviceAuthorizationGrant { data },
|
|
||||||
) => {
|
|
||||||
let msg = match &data.message {
|
|
||||||
Some(msg) => msg.clone(),
|
|
||||||
None => format!("Using a browser on another device, visit:\n{}\nAnd enter the code:\n{}",
|
|
||||||
data.verification_uri, data.user_code)
|
|
||||||
};
|
|
||||||
match conv.send(PAM_TEXT_INFO, &msg) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if opts.debug {
|
debug!("unable to get password");
|
||||||
println!("Message prompt failed");
|
|
||||||
}
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
timeout = u64::from(data.expires_in);
|
// Now setup the request for the next loop.
|
||||||
req = ClientRequest::PamAuthenticateStep(
|
timeout = cfg.unix_sock_timeout;
|
||||||
PamAuthRequest::DeviceAuthorizationGrant { data },
|
req = ClientRequest::PamAuthenticateStep(PamAuthRequest::Password { cred });
|
||||||
);
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// unexpected response.
|
|
||||||
error!(err = ?r, "PAM_IGNORE, unexpected resolver response");
|
|
||||||
return PamResultCode::PAM_IGNORE;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(err) => {
|
ClientResponse::PamAuthenticateStepResponse(
|
||||||
error!(?err, "PAM_IGNORE");
|
PamAuthResponse::DeviceAuthorizationGrant { data },
|
||||||
return PamResultCode::PAM_IGNORE;
|
) => {
|
||||||
|
let msg = match &data.message {
|
||||||
|
Some(msg) => msg.clone(),
|
||||||
|
None => format!("Using a browser on another device, visit:\n{}\nAnd enter the code:\n{}",
|
||||||
|
data.verification_uri, data.user_code)
|
||||||
|
};
|
||||||
|
match conv.send(PAM_TEXT_INFO, &msg) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
if opts.debug {
|
||||||
|
println!("Message prompt failed");
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = u64::from(data.expires_in);
|
||||||
|
req = ClientRequest::PamAuthenticateStep(
|
||||||
|
PamAuthRequest::DeviceAuthorizationGrant { data },
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::MFACode {
|
||||||
|
msg,
|
||||||
|
}) => {
|
||||||
|
match conv.send(PAM_TEXT_INFO, &msg) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
if opts.debug {
|
||||||
|
println!("Message prompt failed");
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let cred = match conv.send(PAM_PROMPT_ECHO_OFF, "Code") {
|
||||||
|
Ok(password) => match password {
|
||||||
|
Some(cred) => cred,
|
||||||
|
None => {
|
||||||
|
debug!("no mfa code");
|
||||||
|
return PamResultCode::PAM_CRED_INSUFFICIENT;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
debug!("unable to get mfa code");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now setup the request for the next loop.
|
||||||
|
timeout = cfg.unix_sock_timeout;
|
||||||
|
req = ClientRequest::PamAuthenticateStep(PamAuthRequest::MFACode {
|
||||||
|
cred,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::MFAPoll {
|
||||||
|
msg,
|
||||||
|
polling_interval,
|
||||||
|
}) => {
|
||||||
|
match conv.send(PAM_TEXT_INFO, &msg) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
if opts.debug {
|
||||||
|
println!("Message prompt failed");
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_secs(polling_interval.into()));
|
||||||
|
timeout = cfg.unix_sock_timeout;
|
||||||
|
req = ClientRequest::PamAuthenticateStep(PamAuthRequest::MFAPoll);
|
||||||
|
|
||||||
|
// Counter intuitive, but we don't need a max poll attempts here because
|
||||||
|
// if the resolver goes away, then this will error on the sock and
|
||||||
|
// will shutdown. This allows the resolver to dynamically extend the
|
||||||
|
// timeout if needed, and removes logic from the front end.
|
||||||
|
match_sm_auth_client_response!(
|
||||||
|
daemon_client.call_and_wait(&req, timeout), opts,
|
||||||
|
ClientResponse::PamAuthenticateStepResponse(
|
||||||
|
PamAuthResponse::MFAPollWait,
|
||||||
|
) => {
|
||||||
|
// Continue polling if the daemon says to wait
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
} // while true, continue calling PamAuthenticateStep until we get a decision.
|
} // while true, continue calling PamAuthenticateStep until we get a decision.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -202,6 +202,10 @@ async fn handle_client(
|
||||||
let mut reqs = Framed::new(sock, ClientCodec);
|
let mut reqs = Framed::new(sock, ClientCodec);
|
||||||
let mut pam_auth_session_state = None;
|
let mut pam_auth_session_state = None;
|
||||||
|
|
||||||
|
// Setup a broadcast channel so that if we have an unexpected disconnection, we can
|
||||||
|
// tell consumers to stop work.
|
||||||
|
let (shutdown_tx, _shutdown_rx) = broadcast::channel(1);
|
||||||
|
|
||||||
trace!("Waiting for requests ...");
|
trace!("Waiting for requests ...");
|
||||||
while let Some(Ok(req)) = reqs.next().await {
|
while let Some(Ok(req)) = reqs.next().await {
|
||||||
let resp = match req {
|
let resp = match req {
|
||||||
|
@ -295,7 +299,10 @@ async fn handle_client(
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
match cachelayer
|
match cachelayer
|
||||||
.pam_account_authenticate_init(account_id.as_str())
|
.pam_account_authenticate_init(
|
||||||
|
account_id.as_str(),
|
||||||
|
shutdown_tx.subscribe(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok((auth_session, pam_auth_response)) => {
|
Ok((auth_session, pam_auth_response)) => {
|
||||||
|
@ -407,6 +414,14 @@ async fn handle_client(
|
||||||
debug!("flushed response!");
|
debug!("flushed response!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Signal any tasks that they need to stop.
|
||||||
|
if let Err(shutdown_err) = shutdown_tx.send(()) {
|
||||||
|
warn!(
|
||||||
|
?shutdown_err,
|
||||||
|
"Unable to signal tasks to stop, they will naturally timeout instead."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Disconnect them
|
// Disconnect them
|
||||||
debug!("Disconnecting client ...");
|
debug!("Disconnecting client ...");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::db::KeyStoreTxn;
|
||||||
use crate::unix_proto::{DeviceAuthorizationResponse, PamAuthRequest, PamAuthResponse};
|
use crate::unix_proto::{DeviceAuthorizationResponse, PamAuthRequest, PamAuthResponse};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::broadcast;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use kanidm_hsm_crypto as tpm;
|
pub use kanidm_hsm_crypto as tpm;
|
||||||
|
@ -63,11 +64,35 @@ pub struct UserToken {
|
||||||
pub enum AuthCredHandler {
|
pub enum AuthCredHandler {
|
||||||
Password,
|
Password,
|
||||||
DeviceAuthorizationGrant,
|
DeviceAuthorizationGrant,
|
||||||
|
/// Additional data required by the provider to complete the
|
||||||
|
/// authentication, but not required by PAM
|
||||||
|
///
|
||||||
|
/// Sadly due to how this is passed around we can't make this a
|
||||||
|
/// generic associated type, else it would have to leak up to the
|
||||||
|
/// daemon.
|
||||||
|
///
|
||||||
|
/// ⚠️ TODO: Optimally this should actually be a tokio oneshot receiver
|
||||||
|
/// with the decision from a task that is spawned.
|
||||||
|
MFA {
|
||||||
|
data: Vec<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AuthRequest {
|
pub enum AuthRequest {
|
||||||
Password,
|
Password,
|
||||||
DeviceAuthorizationGrant { data: DeviceAuthorizationResponse },
|
DeviceAuthorizationGrant {
|
||||||
|
data: DeviceAuthorizationResponse,
|
||||||
|
},
|
||||||
|
MFACode {
|
||||||
|
msg: String,
|
||||||
|
},
|
||||||
|
MFAPoll {
|
||||||
|
/// Message to display to the user.
|
||||||
|
msg: String,
|
||||||
|
/// Interval in seconds between poll attemts.
|
||||||
|
polling_interval: u32,
|
||||||
|
},
|
||||||
|
MFAPollWait,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::from_over_into)]
|
#[allow(clippy::from_over_into)]
|
||||||
|
@ -78,6 +103,15 @@ impl Into<PamAuthResponse> for AuthRequest {
|
||||||
AuthRequest::DeviceAuthorizationGrant { data } => {
|
AuthRequest::DeviceAuthorizationGrant { data } => {
|
||||||
PamAuthResponse::DeviceAuthorizationGrant { data }
|
PamAuthResponse::DeviceAuthorizationGrant { data }
|
||||||
}
|
}
|
||||||
|
AuthRequest::MFACode { msg } => PamAuthResponse::MFACode { msg },
|
||||||
|
AuthRequest::MFAPoll {
|
||||||
|
msg,
|
||||||
|
polling_interval,
|
||||||
|
} => PamAuthResponse::MFAPoll {
|
||||||
|
msg,
|
||||||
|
polling_interval,
|
||||||
|
},
|
||||||
|
AuthRequest::MFAPollWait => PamAuthResponse::MFAPollWait,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,6 +167,7 @@ pub trait IdProvider {
|
||||||
_token: Option<&UserToken>,
|
_token: Option<&UserToken>,
|
||||||
_tpm: &mut tpm::BoxedDynTpm,
|
_tpm: &mut tpm::BoxedDynTpm,
|
||||||
_machine_key: &tpm::MachineKey,
|
_machine_key: &tpm::MachineKey,
|
||||||
|
_shutdown_rx: &broadcast::Receiver<()>,
|
||||||
) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
|
) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
|
||||||
|
|
||||||
async fn unix_user_online_auth_step<D: KeyStoreTxn + Send>(
|
async fn unix_user_online_auth_step<D: KeyStoreTxn + Send>(
|
||||||
|
@ -143,6 +178,7 @@ pub trait IdProvider {
|
||||||
_keystore: &mut D,
|
_keystore: &mut D,
|
||||||
_tpm: &mut tpm::BoxedDynTpm,
|
_tpm: &mut tpm::BoxedDynTpm,
|
||||||
_machine_key: &tpm::MachineKey,
|
_machine_key: &tpm::MachineKey,
|
||||||
|
_shutdown_rx: &broadcast::Receiver<()>,
|
||||||
) -> Result<(AuthResult, AuthCacheAction), IdpError>;
|
) -> Result<(AuthResult, AuthCacheAction), IdpError>;
|
||||||
|
|
||||||
async fn unix_user_offline_auth_init(
|
async fn unix_user_offline_auth_init(
|
||||||
|
|
|
@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||||
use kanidm_client::{ClientError, KanidmClient, StatusCode};
|
use kanidm_client::{ClientError, KanidmClient, StatusCode};
|
||||||
use kanidm_proto::internal::OperationError;
|
use kanidm_proto::internal::OperationError;
|
||||||
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
|
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::{broadcast, RwLock};
|
||||||
|
|
||||||
use super::interface::{
|
use super::interface::{
|
||||||
// KeyStore,
|
// KeyStore,
|
||||||
|
@ -197,6 +197,7 @@ impl IdProvider for KanidmProvider {
|
||||||
_token: Option<&UserToken>,
|
_token: Option<&UserToken>,
|
||||||
_tpm: &mut tpm::BoxedDynTpm,
|
_tpm: &mut tpm::BoxedDynTpm,
|
||||||
_machine_key: &tpm::MachineKey,
|
_machine_key: &tpm::MachineKey,
|
||||||
|
_shutdown_rx: &broadcast::Receiver<()>,
|
||||||
) -> Result<(AuthRequest, AuthCredHandler), IdpError> {
|
) -> Result<(AuthRequest, AuthCredHandler), IdpError> {
|
||||||
// Not sure that I need to do much here?
|
// Not sure that I need to do much here?
|
||||||
Ok((AuthRequest::Password, AuthCredHandler::Password))
|
Ok((AuthRequest::Password, AuthCredHandler::Password))
|
||||||
|
@ -210,6 +211,7 @@ impl IdProvider for KanidmProvider {
|
||||||
_keystore: &mut D,
|
_keystore: &mut D,
|
||||||
_tpm: &mut tpm::BoxedDynTpm,
|
_tpm: &mut tpm::BoxedDynTpm,
|
||||||
_machine_key: &tpm::MachineKey,
|
_machine_key: &tpm::MachineKey,
|
||||||
|
_shutdown_rx: &broadcast::Receiver<()>,
|
||||||
) -> Result<(AuthResult, AuthCacheAction), IdpError> {
|
) -> Result<(AuthResult, AuthCacheAction), IdpError> {
|
||||||
match (cred_handler, pam_next_req) {
|
match (cred_handler, pam_next_req) {
|
||||||
(AuthCredHandler::Password, PamAuthRequest::Password { cred }) => {
|
(AuthCredHandler::Password, PamAuthRequest::Password { cred }) => {
|
||||||
|
|
|
@ -28,6 +28,8 @@ use crate::unix_proto::{HomeDirectoryInfo, NssGroup, NssUser, PamAuthRequest, Pa
|
||||||
|
|
||||||
use kanidm_hsm_crypto::{BoxedDynTpm, HmacKey, MachineKey, Tpm};
|
use kanidm_hsm_crypto::{BoxedDynTpm, HmacKey, MachineKey, Tpm};
|
||||||
|
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(128) };
|
const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(128) };
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -44,9 +46,12 @@ pub enum AuthSession {
|
||||||
id: Id,
|
id: Id,
|
||||||
token: Option<Box<UserToken>>,
|
token: Option<Box<UserToken>>,
|
||||||
online_at_init: bool,
|
online_at_init: bool,
|
||||||
// cred_type: AuthCredType,
|
|
||||||
// next_cred: AuthNextCred,
|
|
||||||
cred_handler: AuthCredHandler,
|
cred_handler: AuthCredHandler,
|
||||||
|
/// Some authentication operations may need to spawn background tasks. These tasks need
|
||||||
|
/// to know when to stop as the caller has disconnected. This reciever allows that, so
|
||||||
|
/// that tasks which .resubscribe() to this channel can then select! on it and be notified
|
||||||
|
/// when they need to stop.
|
||||||
|
shutdown_rx: broadcast::Receiver<()>,
|
||||||
},
|
},
|
||||||
Success,
|
Success,
|
||||||
Denied,
|
Denied,
|
||||||
|
@ -867,6 +872,7 @@ where
|
||||||
pub async fn pam_account_authenticate_init(
|
pub async fn pam_account_authenticate_init(
|
||||||
&self,
|
&self,
|
||||||
account_id: &str,
|
account_id: &str,
|
||||||
|
shutdown_rx: broadcast::Receiver<()>,
|
||||||
) -> Result<(AuthSession, PamAuthResponse), ()> {
|
) -> Result<(AuthSession, PamAuthResponse), ()> {
|
||||||
// Setup an auth session. If possible bring the resolver online.
|
// Setup an auth session. If possible bring the resolver online.
|
||||||
// Further steps won't attempt to bring the cache online to prevent
|
// Further steps won't attempt to bring the cache online to prevent
|
||||||
|
@ -893,6 +899,7 @@ where
|
||||||
token.as_ref(),
|
token.as_ref(),
|
||||||
hsm_lock.deref_mut(),
|
hsm_lock.deref_mut(),
|
||||||
&self.machine_key,
|
&self.machine_key,
|
||||||
|
&shutdown_rx,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
|
@ -910,6 +917,7 @@ where
|
||||||
token: token.map(Box::new),
|
token: token.map(Box::new),
|
||||||
online_at_init,
|
online_at_init,
|
||||||
cred_handler,
|
cred_handler,
|
||||||
|
shutdown_rx,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Now identify what credentials are needed next. The auth session tells
|
// Now identify what credentials are needed next. The auth session tells
|
||||||
|
@ -945,6 +953,7 @@ where
|
||||||
token: _,
|
token: _,
|
||||||
online_at_init: true,
|
online_at_init: true,
|
||||||
ref mut cred_handler,
|
ref mut cred_handler,
|
||||||
|
ref shutdown_rx,
|
||||||
},
|
},
|
||||||
CacheState::Online,
|
CacheState::Online,
|
||||||
) => {
|
) => {
|
||||||
|
@ -960,6 +969,7 @@ where
|
||||||
&mut dbtxn,
|
&mut dbtxn,
|
||||||
hsm_lock.deref_mut(),
|
hsm_lock.deref_mut(),
|
||||||
&self.machine_key,
|
&self.machine_key,
|
||||||
|
shutdown_rx,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -1008,6 +1018,8 @@ where
|
||||||
token: Some(ref token),
|
token: Some(ref token),
|
||||||
online_at_init: _,
|
online_at_init: _,
|
||||||
ref mut cred_handler,
|
ref mut cred_handler,
|
||||||
|
// Only need in online auth.
|
||||||
|
shutdown_rx: _,
|
||||||
},
|
},
|
||||||
_,
|
_,
|
||||||
) => {
|
) => {
|
||||||
|
@ -1038,6 +1050,10 @@ where
|
||||||
// AuthCredHandler::DeviceAuthorizationGrant is invalid for offline auth
|
// AuthCredHandler::DeviceAuthorizationGrant is invalid for offline auth
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
(AuthCredHandler::MFA { .. }, _) => {
|
||||||
|
// AuthCredHandler::MFA is invalid for offline auth
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1102,7 +1118,12 @@ where
|
||||||
account_id: &str,
|
account_id: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
) -> Result<Option<bool>, ()> {
|
) -> Result<Option<bool>, ()> {
|
||||||
let mut auth_session = match self.pam_account_authenticate_init(account_id).await? {
|
let (_shutdown_tx, shutdown_rx) = broadcast::channel(1);
|
||||||
|
|
||||||
|
let mut auth_session = match self
|
||||||
|
.pam_account_authenticate_init(account_id, shutdown_rx)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
(auth_session, PamAuthResponse::Password) => {
|
(auth_session, PamAuthResponse::Password) => {
|
||||||
// Can continue!
|
// Can continue!
|
||||||
auth_session
|
auth_session
|
||||||
|
@ -1111,6 +1132,18 @@ where
|
||||||
// Can continue!
|
// Can continue!
|
||||||
auth_session
|
auth_session
|
||||||
}
|
}
|
||||||
|
(auth_session, PamAuthResponse::MFACode { .. }) => {
|
||||||
|
// Can continue!
|
||||||
|
auth_session
|
||||||
|
}
|
||||||
|
(auth_session, PamAuthResponse::MFAPoll { .. }) => {
|
||||||
|
// Can continue!
|
||||||
|
auth_session
|
||||||
|
}
|
||||||
|
(auth_session, PamAuthResponse::MFAPollWait) => {
|
||||||
|
// Can continue!
|
||||||
|
auth_session
|
||||||
|
}
|
||||||
(_, PamAuthResponse::Unknown) => return Ok(None),
|
(_, PamAuthResponse::Unknown) => return Ok(None),
|
||||||
(_, PamAuthResponse::Denied) => return Ok(Some(false)),
|
(_, PamAuthResponse::Denied) => return Ok(Some(false)),
|
||||||
(_, PamAuthResponse::Success) => {
|
(_, PamAuthResponse::Success) => {
|
||||||
|
|
|
@ -36,22 +36,30 @@ pub enum PamAuthResponse {
|
||||||
Success,
|
Success,
|
||||||
Denied,
|
Denied,
|
||||||
Password,
|
Password,
|
||||||
DeviceAuthorizationGrant { data: DeviceAuthorizationResponse },
|
DeviceAuthorizationGrant {
|
||||||
/*
|
data: DeviceAuthorizationResponse,
|
||||||
MFACode {
|
|
||||||
},
|
},
|
||||||
*/
|
/// PAM must prompt for an authentication code
|
||||||
|
MFACode {
|
||||||
|
msg: String,
|
||||||
|
},
|
||||||
|
/// PAM will poll for an external response
|
||||||
|
MFAPoll {
|
||||||
|
/// Initial message to display as the polling begins.
|
||||||
|
msg: String,
|
||||||
|
/// Seconds between polling attempts.
|
||||||
|
polling_interval: u32,
|
||||||
|
},
|
||||||
|
MFAPollWait,
|
||||||
// CTAP2
|
// CTAP2
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum PamAuthRequest {
|
pub enum PamAuthRequest {
|
||||||
Password { cred: String },
|
Password { cred: String },
|
||||||
DeviceAuthorizationGrant { data: DeviceAuthorizationResponse }, /*
|
DeviceAuthorizationGrant { data: DeviceAuthorizationResponse },
|
||||||
MFACode {
|
MFACode { cred: String },
|
||||||
cred: Option<PamCred>
|
MFAPoll,
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
Loading…
Reference in a new issue