2023-07-28 02:48:56 +02:00
|
|
|
use async_trait::async_trait;
|
2024-06-17 00:21:25 +02:00
|
|
|
use kanidm_unix_common::unix_proto::{
|
|
|
|
DeviceAuthorizationResponse, PamAuthRequest, PamAuthResponse,
|
|
|
|
};
|
2023-11-20 03:46:52 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-08-16 01:54:35 +02:00
|
|
|
use serde_json::Value;
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use std::fmt;
|
|
|
|
use std::time::SystemTime;
|
2024-03-28 02:17:21 +01:00
|
|
|
use tokio::sync::broadcast;
|
2023-07-28 02:48:56 +02:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2024-08-16 01:54:35 +02:00
|
|
|
pub type XKeyId = String;
|
|
|
|
|
2023-11-20 03:46:52 +01:00
|
|
|
pub use kanidm_hsm_crypto as tpm;
|
2023-11-09 04:11:23 +01:00
|
|
|
|
2023-07-28 02:48:56 +02:00
|
|
|
/// Errors that the IdProvider may return. These drive the resolver state machine
|
|
|
|
/// and should be carefully selected to match your expected errors.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum IdpError {
|
|
|
|
/// An error occurred in the underlying communication to the Idp. A timeout or
|
|
|
|
/// or other communication issue exists. The resolver will take this provider
|
|
|
|
/// offline.
|
|
|
|
Transport,
|
|
|
|
/// The provider is online but the provider module is not current authorised with
|
|
|
|
/// the idp. After returning this error the operation will be retried after a
|
|
|
|
/// successful authentication.
|
|
|
|
ProviderUnauthorised,
|
2023-08-28 01:27:29 +02:00
|
|
|
/// The provider made an invalid or illogical request to the idp, and a result
|
|
|
|
/// is not able to be provided to the resolver.
|
2023-07-28 02:48:56 +02:00
|
|
|
BadRequest,
|
|
|
|
/// The idp has indicated that the requested resource does not exist and should
|
|
|
|
/// be considered deleted, removed, or not present.
|
|
|
|
NotFound,
|
2023-11-09 04:11:23 +01:00
|
|
|
/// The idp was unable to perform an operation on the underlying hsm keystorage
|
|
|
|
KeyStore,
|
2023-11-20 03:46:52 +01:00
|
|
|
/// The idp failed to interact with the configured TPM
|
|
|
|
Tpm,
|
2023-07-28 02:48:56 +02:00
|
|
|
}
|
|
|
|
|
2024-08-16 01:54:35 +02:00
|
|
|
pub enum UserTokenState {
|
|
|
|
/// Indicate to the resolver that the cached UserToken should be used, if present.
|
|
|
|
UseCached,
|
|
|
|
/// The requested entity is not found, or has been removed.
|
|
|
|
NotFound,
|
|
|
|
|
|
|
|
/// Update the cache state with the data found in this UserToken.
|
|
|
|
Update(UserToken),
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum GroupTokenState {
|
|
|
|
/// Indicate to the resolver that the cached GroupToken should be used, if present.
|
|
|
|
UseCached,
|
|
|
|
/// The requested entity is not found, or has been removed.
|
|
|
|
NotFound,
|
|
|
|
|
|
|
|
/// Update the cache state with the data found in this GroupToken.
|
|
|
|
Update(GroupToken),
|
|
|
|
}
|
|
|
|
|
2023-07-28 02:48:56 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
pub enum Id {
|
|
|
|
Name(String),
|
|
|
|
Gid(u32),
|
|
|
|
}
|
|
|
|
|
2024-08-16 01:54:35 +02:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, Eq, PartialEq, Hash)]
|
2024-07-25 08:11:14 +02:00
|
|
|
pub enum ProviderOrigin {
|
|
|
|
// To allow transition, we have an ignored type that effectively
|
|
|
|
// causes these items to be nixed.
|
|
|
|
#[default]
|
|
|
|
Ignore,
|
2024-08-16 01:54:35 +02:00
|
|
|
/// Provided by /etc/passwd or /etc/group
|
|
|
|
System,
|
2024-07-25 08:11:14 +02:00
|
|
|
Kanidm,
|
|
|
|
}
|
|
|
|
|
2024-08-16 01:54:35 +02:00
|
|
|
impl fmt::Display for ProviderOrigin {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
ProviderOrigin::Ignore => {
|
|
|
|
write!(f, "Ignored")
|
|
|
|
}
|
|
|
|
ProviderOrigin::System => {
|
|
|
|
write!(f, "System")
|
|
|
|
}
|
|
|
|
ProviderOrigin::Kanidm => {
|
|
|
|
write!(f, "Kanidm")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-28 02:48:56 +02:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
|
|
pub struct GroupToken {
|
2024-07-25 08:11:14 +02:00
|
|
|
#[serde(default)]
|
|
|
|
pub provider: ProviderOrigin,
|
2023-07-28 02:48:56 +02:00
|
|
|
pub name: String,
|
|
|
|
pub spn: String,
|
|
|
|
pub uuid: Uuid,
|
|
|
|
pub gidnumber: u32,
|
2024-08-16 01:54:35 +02:00
|
|
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
pub extra_keys: BTreeMap<XKeyId, Value>,
|
2023-07-28 02:48:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
|
|
pub struct UserToken {
|
2024-07-25 08:11:14 +02:00
|
|
|
#[serde(default)]
|
|
|
|
pub provider: ProviderOrigin,
|
2024-08-16 01:54:35 +02:00
|
|
|
|
2023-07-28 02:48:56 +02:00
|
|
|
pub name: String,
|
|
|
|
pub spn: String,
|
|
|
|
pub uuid: Uuid,
|
|
|
|
pub gidnumber: u32,
|
|
|
|
pub displayname: String,
|
|
|
|
pub shell: Option<String>,
|
|
|
|
pub groups: Vec<GroupToken>,
|
2024-08-16 01:54:35 +02:00
|
|
|
|
2023-07-28 02:48:56 +02:00
|
|
|
// Could there be a better type here?
|
|
|
|
pub sshkeys: Vec<String>,
|
|
|
|
// Defaults to false.
|
|
|
|
pub valid: bool,
|
2024-08-16 01:54:35 +02:00
|
|
|
|
|
|
|
// These are opaque extra keys that the provider can interpret for internal
|
|
|
|
// functions.
|
|
|
|
#[serde(flatten)]
|
|
|
|
pub extra_keys: BTreeMap<XKeyId, Value>,
|
2023-07-28 02:48:56 +02:00
|
|
|
}
|
|
|
|
|
2023-08-28 01:27:29 +02:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum AuthCredHandler {
|
|
|
|
Password,
|
2023-09-12 23:33:46 +02:00
|
|
|
DeviceAuthorizationGrant,
|
2024-03-28 02:17:21 +01:00
|
|
|
/// 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>,
|
|
|
|
},
|
2024-04-05 00:50:37 +02:00
|
|
|
SetupPin,
|
|
|
|
Pin,
|
2023-08-28 01:27:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub enum AuthRequest {
|
|
|
|
Password,
|
2024-03-28 02:17:21 +01:00
|
|
|
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,
|
2024-04-05 00:50:37 +02:00
|
|
|
SetupPin {
|
|
|
|
/// Message to display to the user.
|
|
|
|
msg: String,
|
|
|
|
},
|
|
|
|
Pin,
|
2023-08-28 01:27:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::from_over_into)]
|
|
|
|
impl Into<PamAuthResponse> for AuthRequest {
|
|
|
|
fn into(self) -> PamAuthResponse {
|
|
|
|
match self {
|
|
|
|
AuthRequest::Password => PamAuthResponse::Password,
|
2023-09-12 23:33:46 +02:00
|
|
|
AuthRequest::DeviceAuthorizationGrant { data } => {
|
|
|
|
PamAuthResponse::DeviceAuthorizationGrant { data }
|
|
|
|
}
|
2024-03-28 02:17:21 +01:00
|
|
|
AuthRequest::MFACode { msg } => PamAuthResponse::MFACode { msg },
|
|
|
|
AuthRequest::MFAPoll {
|
|
|
|
msg,
|
|
|
|
polling_interval,
|
|
|
|
} => PamAuthResponse::MFAPoll {
|
|
|
|
msg,
|
|
|
|
polling_interval,
|
|
|
|
},
|
|
|
|
AuthRequest::MFAPollWait => PamAuthResponse::MFAPollWait,
|
2024-04-05 00:50:37 +02:00
|
|
|
AuthRequest::SetupPin { msg } => PamAuthResponse::SetupPin { msg },
|
|
|
|
AuthRequest::Pin => PamAuthResponse::Pin,
|
2023-08-28 01:27:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum AuthResult {
|
|
|
|
Success { token: UserToken },
|
|
|
|
Denied,
|
|
|
|
Next(AuthRequest),
|
|
|
|
}
|
|
|
|
|
2023-07-28 02:48:56 +02:00
|
|
|
#[async_trait]
|
2024-07-15 12:28:23 +02:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2023-07-28 02:48:56 +02:00
|
|
|
pub trait IdProvider {
|
2024-08-16 01:54:35 +02:00
|
|
|
/// Retrieve this providers origin
|
|
|
|
fn origin(&self) -> ProviderOrigin;
|
|
|
|
|
|
|
|
/// Attempt to go online *immediately*
|
|
|
|
async fn attempt_online(&self, _tpm: &mut tpm::BoxedDynTpm, _now: SystemTime) -> bool;
|
|
|
|
|
|
|
|
/// Mark that this provider should attempt to go online next time it
|
|
|
|
/// recieves a request
|
|
|
|
async fn mark_next_check(&self, _now: SystemTime);
|
|
|
|
|
|
|
|
/// Force this provider offline immediately.
|
|
|
|
async fn mark_offline(&self);
|
2023-11-09 04:11:23 +01:00
|
|
|
|
2023-11-27 05:35:59 +01:00
|
|
|
/// This is similar to a "domain join" process. What do we actually need to pass here
|
|
|
|
/// for this to work for kanidm or himmelblau? Should we make it take a generic?
|
|
|
|
/*
|
2024-06-17 00:21:25 +02:00
|
|
|
async fn configure_machine_identity(
|
2023-11-27 05:35:59 +01:00
|
|
|
&self,
|
2024-06-17 00:21:25 +02:00
|
|
|
_keystore: &mut KeyStoreTxn,
|
|
|
|
_tpm: &mut tpm::BoxedDynTpm,
|
2023-11-27 05:35:59 +01:00
|
|
|
_machine_key: &tpm::MachineKey,
|
|
|
|
) -> Result<(), IdpError> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2023-08-16 07:33:28 +02:00
|
|
|
async fn unix_user_get(
|
|
|
|
&self,
|
2023-08-28 01:27:29 +02:00
|
|
|
_id: &Id,
|
|
|
|
_token: Option<&UserToken>,
|
2023-12-03 06:33:25 +01:00
|
|
|
_tpm: &mut tpm::BoxedDynTpm,
|
2024-08-16 01:54:35 +02:00
|
|
|
_now: SystemTime,
|
|
|
|
) -> Result<UserTokenState, IdpError>;
|
2023-07-28 02:48:56 +02:00
|
|
|
|
2024-06-17 00:21:25 +02:00
|
|
|
async fn unix_user_online_auth_init(
|
2023-08-28 01:27:29 +02:00
|
|
|
&self,
|
|
|
|
_account_id: &str,
|
2024-08-16 01:54:35 +02:00
|
|
|
_token: &UserToken,
|
2023-12-03 06:33:25 +01:00
|
|
|
_tpm: &mut tpm::BoxedDynTpm,
|
2024-03-28 02:17:21 +01:00
|
|
|
_shutdown_rx: &broadcast::Receiver<()>,
|
2023-08-28 01:27:29 +02:00
|
|
|
) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
|
|
|
|
|
2024-06-17 00:21:25 +02:00
|
|
|
async fn unix_user_online_auth_step(
|
2023-08-28 01:27:29 +02:00
|
|
|
&self,
|
|
|
|
_account_id: &str,
|
|
|
|
_cred_handler: &mut AuthCredHandler,
|
|
|
|
_pam_next_req: PamAuthRequest,
|
2023-12-03 06:33:25 +01:00
|
|
|
_tpm: &mut tpm::BoxedDynTpm,
|
2024-03-28 02:17:21 +01:00
|
|
|
_shutdown_rx: &broadcast::Receiver<()>,
|
2024-08-16 01:54:35 +02:00
|
|
|
) -> Result<AuthResult, IdpError>;
|
2023-08-28 01:27:29 +02:00
|
|
|
|
2024-08-16 01:54:35 +02:00
|
|
|
async fn unix_unknown_user_online_auth_init(
|
2023-08-28 01:27:29 +02:00
|
|
|
&self,
|
|
|
|
_account_id: &str,
|
2024-08-16 01:54:35 +02:00
|
|
|
_tpm: &mut tpm::BoxedDynTpm,
|
|
|
|
_shutdown_rx: &broadcast::Receiver<()>,
|
|
|
|
) -> Result<Option<(AuthRequest, AuthCredHandler)>, IdpError>;
|
|
|
|
|
|
|
|
async fn unix_user_offline_auth_init(
|
|
|
|
&self,
|
|
|
|
_token: &UserToken,
|
2023-08-28 01:27:29 +02:00
|
|
|
) -> Result<(AuthRequest, AuthCredHandler), IdpError>;
|
|
|
|
|
|
|
|
// I thought about this part of the interface a lot. we could have the
|
|
|
|
// provider actually need to check the password or credentials, but then
|
|
|
|
// we need to rework the tpm/crypto engine to be an argument to pass here
|
|
|
|
// as well the cached credentials.
|
|
|
|
//
|
|
|
|
// As well, since this is "offline auth" the provider isn't really "doing"
|
|
|
|
// anything special here - when you say you want offline password auth, the
|
|
|
|
// resolver can just do it for you for all the possible implementations.
|
|
|
|
// This is similar for offline ctap2 as well, or even offline totp.
|
|
|
|
//
|
|
|
|
// I think in the future we could reconsider this and let the provider be
|
|
|
|
// involved if there is some "custom logic" or similar that is needed but
|
|
|
|
// for now I think making it generic is a good first step and we can change
|
|
|
|
// it later.
|
2024-04-05 00:50:37 +02:00
|
|
|
//
|
|
|
|
// EDIT 04042024: When we're performing an offline PIN auth, the PIN can
|
|
|
|
// unlock the associated TPM key. While we can't perform a full request
|
|
|
|
// for an auth token, we can verify that the PIN successfully unlocks the
|
|
|
|
// TPM key.
|
2024-06-17 00:21:25 +02:00
|
|
|
async fn unix_user_offline_auth_step(
|
2023-07-28 02:48:56 +02:00
|
|
|
&self,
|
2024-04-05 00:50:37 +02:00
|
|
|
_token: &UserToken,
|
2023-08-28 01:27:29 +02:00
|
|
|
_cred_handler: &mut AuthCredHandler,
|
|
|
|
_pam_next_req: PamAuthRequest,
|
2024-04-05 00:50:37 +02:00
|
|
|
_tpm: &mut tpm::BoxedDynTpm,
|
2023-08-28 01:27:29 +02:00
|
|
|
) -> Result<AuthResult, IdpError>;
|
2023-07-28 02:48:56 +02:00
|
|
|
|
2024-10-02 04:12:13 +02:00
|
|
|
async fn unix_user_authorise(&self, _token: &UserToken) -> Result<Option<bool>, IdpError>;
|
|
|
|
|
2023-11-27 05:35:59 +01:00
|
|
|
async fn unix_group_get(
|
|
|
|
&self,
|
|
|
|
id: &Id,
|
2023-12-03 06:33:25 +01:00
|
|
|
_tpm: &mut tpm::BoxedDynTpm,
|
2024-08-16 01:54:35 +02:00
|
|
|
_now: SystemTime,
|
|
|
|
) -> Result<GroupTokenState, IdpError>;
|
2023-07-28 02:48:56 +02:00
|
|
|
}
|