mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
20230727 unix int modularity (#1907)
This commit is contained in:
parent
0293234d3c
commit
99b761c966
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2674,7 +2674,6 @@ dependencies = [
|
|||
"lru 0.8.1",
|
||||
"notify-debouncer-full",
|
||||
"profiles",
|
||||
"reqwest",
|
||||
"rpassword 7.2.0",
|
||||
"rusqlite",
|
||||
"selinux",
|
||||
|
@ -2687,6 +2686,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tss-esapi",
|
||||
"users",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
|
|
@ -556,7 +556,7 @@ impl fmt::Display for RadiusAuthToken {
|
|||
pub struct UnixGroupToken {
|
||||
pub name: String,
|
||||
pub spn: String,
|
||||
pub uuid: String,
|
||||
pub uuid: Uuid,
|
||||
pub gidnumber: u32,
|
||||
}
|
||||
|
||||
|
@ -580,7 +580,7 @@ pub struct UnixUserToken {
|
|||
pub spn: String,
|
||||
pub displayname: String,
|
||||
pub gidnumber: u32,
|
||||
pub uuid: String,
|
||||
pub uuid: Uuid,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub shell: Option<String>,
|
||||
pub groups: Vec<UnixGroupToken>,
|
||||
|
|
|
@ -150,7 +150,7 @@ impl UnixUserAccount {
|
|||
spn: self.spn.clone(),
|
||||
displayname: self.displayname.clone(),
|
||||
gidnumber: self.gidnumber,
|
||||
uuid: self.uuid.as_hyphenated().to_string(),
|
||||
uuid: self.uuid,
|
||||
shell: self.shell.clone(),
|
||||
groups,
|
||||
sshkeys: self.sshkeys.clone(),
|
||||
|
@ -449,7 +449,7 @@ impl UnixGroup {
|
|||
Ok(UnixGroupToken {
|
||||
name: self.name.clone(),
|
||||
spn: self.spn.clone(),
|
||||
uuid: self.uuid.as_hyphenated().to_string(),
|
||||
uuid: self.uuid,
|
||||
gidnumber: self.gidnumber,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -563,7 +563,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
|
|||
.unwrap();
|
||||
// get the account by uuid
|
||||
let r3 = rsclient
|
||||
.idm_account_unix_token_get(r.uuid.as_str())
|
||||
.idm_account_unix_token_get(&r.uuid.hyphenated().to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -590,7 +590,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
|
|||
.unwrap();
|
||||
// get the group by uuid
|
||||
let r3 = rsclient
|
||||
.idm_group_unix_token_get(r.uuid.as_str())
|
||||
.idm_group_unix_token_get(&r.uuid.hyphenated().to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ tokio = { workspace = true, features = ["rt", "fs", "macros", "sync", "time", "n
|
|||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
tracing = { workspace = true }
|
||||
tss-esapi = { workspace = true, optional = true }
|
||||
reqwest = { workspace = true, default-features = false }
|
||||
uuid = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_family = "windows"))'.dependencies]
|
||||
|
|
|
@ -5,6 +5,7 @@ use bytes::{BufMut, BytesMut};
|
|||
use futures::{SinkExt, StreamExt};
|
||||
use tokio::net::UnixStream;
|
||||
// use tokio::runtime::Builder;
|
||||
use tokio::time::{self, Duration};
|
||||
use tokio_util::codec::Framed;
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
|
@ -48,8 +49,10 @@ impl ClientCodec {
|
|||
}
|
||||
}
|
||||
|
||||
/// Makes a call to kanidm_unixd via a unix socket at `path`
|
||||
pub async fn call_daemon(path: &str, req: ClientRequest) -> Result<ClientResponse, Box<dyn Error>> {
|
||||
async fn call_daemon_inner(
|
||||
path: &str,
|
||||
req: ClientRequest,
|
||||
) -> Result<ClientResponse, Box<dyn Error>> {
|
||||
trace!(?path, ?req);
|
||||
let stream = UnixStream::connect(path).await?;
|
||||
trace!("connected");
|
||||
|
@ -71,3 +74,23 @@ pub async fn call_daemon(path: &str, req: ClientRequest) -> Result<ClientRespons
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_millis(timeout));
|
||||
tokio::pin!(sleep);
|
||||
|
||||
tokio::select! {
|
||||
_ = &mut sleep => {
|
||||
error!("Timed out making request to kanidm_unixd");
|
||||
Err(Box::new(IoError::new(ErrorKind::Other, "timeout")))
|
||||
}
|
||||
res = call_daemon_inner(path, req) => {
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ use kanidm_client::KanidmClientBuilder;
|
|||
use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
|
||||
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
|
||||
use kanidm_unix_common::db::Db;
|
||||
use kanidm_unix_common::idprovider::kanidm::KanidmProvider;
|
||||
use kanidm_unix_common::resolver::Resolver;
|
||||
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
|
||||
use kanidm_unix_common::unix_passwd::{parse_etc_group, parse_etc_passwd};
|
||||
|
@ -182,7 +183,7 @@ async fn handle_task_client(
|
|||
|
||||
async fn handle_client(
|
||||
sock: UnixStream,
|
||||
cachelayer: Arc<Resolver>,
|
||||
cachelayer: Arc<Resolver<KanidmProvider>>,
|
||||
task_channel_tx: &Sender<AsyncTaskRequest>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
debug!("Accepted connection");
|
||||
|
@ -372,7 +373,9 @@ async fn handle_client(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_etc_passwd_group(cachelayer: &Resolver) -> Result<(), Box<dyn Error>> {
|
||||
async fn process_etc_passwd_group(
|
||||
cachelayer: &Resolver<KanidmProvider>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut file = File::open("/etc/passwd").await?;
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).await?;
|
||||
|
@ -661,6 +664,8 @@ async fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
let idprovider = KanidmProvider::new(rsclient);
|
||||
|
||||
let db = match Db::new(cfg.db_path.as_str(), &cfg.tpm_policy) {
|
||||
Ok(db) => db,
|
||||
Err(_e) => {
|
||||
|
@ -671,8 +676,8 @@ async fn main() -> ExitCode {
|
|||
|
||||
let cl_inner = match Resolver::new(
|
||||
db,
|
||||
idprovider,
|
||||
cfg.cache_timeout,
|
||||
rsclient,
|
||||
cfg.pam_allowed_login_groups.clone(),
|
||||
cfg.default_shell.clone(),
|
||||
cfg.home_prefix.clone(),
|
||||
|
|
|
@ -2,17 +2,16 @@ use std::convert::TryFrom;
|
|||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::idprovider::interface::{GroupToken, Id, UserToken};
|
||||
use crate::unix_config::TpmPolicy;
|
||||
use async_trait::async_trait;
|
||||
use kanidm_lib_crypto::CryptoPolicy;
|
||||
use kanidm_lib_crypto::DbPasswordV1;
|
||||
use kanidm_lib_crypto::Password;
|
||||
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
|
||||
use libc::umask;
|
||||
use rusqlite::Connection;
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
|
||||
use crate::resolver::Id;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Cache {
|
||||
|
@ -43,27 +42,27 @@ pub trait CacheTxn {
|
|||
|
||||
fn clear(&self) -> Result<(), CacheError>;
|
||||
|
||||
fn get_account(&self, account_id: &Id) -> Result<Option<(UnixUserToken, u64)>, CacheError>;
|
||||
fn get_account(&self, account_id: &Id) -> Result<Option<(UserToken, u64)>, CacheError>;
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<UnixUserToken>, CacheError>;
|
||||
fn get_accounts(&self) -> Result<Vec<UserToken>, CacheError>;
|
||||
|
||||
fn update_account(&self, account: &UnixUserToken, expire: u64) -> Result<(), CacheError>;
|
||||
fn update_account(&self, account: &UserToken, expire: u64) -> Result<(), CacheError>;
|
||||
|
||||
fn delete_account(&self, a_uuid: &str) -> Result<(), CacheError>;
|
||||
fn delete_account(&self, a_uuid: Uuid) -> Result<(), CacheError>;
|
||||
|
||||
fn update_account_password(&self, a_uuid: &str, cred: &str) -> Result<(), CacheError>;
|
||||
fn update_account_password(&self, a_uuid: Uuid, cred: &str) -> Result<(), CacheError>;
|
||||
|
||||
fn check_account_password(&self, a_uuid: &str, cred: &str) -> Result<bool, CacheError>;
|
||||
fn check_account_password(&self, a_uuid: Uuid, cred: &str) -> Result<bool, CacheError>;
|
||||
|
||||
fn get_group(&self, grp_id: &Id) -> Result<Option<(UnixGroupToken, u64)>, CacheError>;
|
||||
fn get_group(&self, grp_id: &Id) -> Result<Option<(GroupToken, u64)>, CacheError>;
|
||||
|
||||
fn get_group_members(&self, g_uuid: &str) -> Result<Vec<UnixUserToken>, CacheError>;
|
||||
fn get_group_members(&self, g_uuid: Uuid) -> Result<Vec<UserToken>, CacheError>;
|
||||
|
||||
fn get_groups(&self) -> Result<Vec<UnixGroupToken>, CacheError>;
|
||||
fn get_groups(&self) -> Result<Vec<GroupToken>, CacheError>;
|
||||
|
||||
fn update_group(&self, grp: &UnixGroupToken, expire: u64) -> Result<(), CacheError>;
|
||||
fn update_group(&self, grp: &GroupToken, expire: u64) -> Result<(), CacheError>;
|
||||
|
||||
fn delete_group(&self, g_uuid: &str) -> Result<(), CacheError>;
|
||||
fn delete_group(&self, g_uuid: Uuid) -> Result<(), CacheError>;
|
||||
}
|
||||
|
||||
pub struct Db {
|
||||
|
@ -347,7 +346,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_account(&self, account_id: &Id) -> Result<Option<(UnixUserToken, u64)>, CacheError> {
|
||||
fn get_account(&self, account_id: &Id) -> Result<Option<(UserToken, u64)>, CacheError> {
|
||||
let data = match account_id {
|
||||
Id::Name(n) => self.get_account_data_name(n.as_str()),
|
||||
Id::Gid(g) => self.get_account_data_gid(*g),
|
||||
|
@ -381,7 +380,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<UnixUserToken>, CacheError> {
|
||||
fn get_accounts(&self) -> Result<Vec<UserToken>, CacheError> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT token FROM account_t")
|
||||
|
@ -410,7 +409,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
.collect())
|
||||
}
|
||||
|
||||
fn update_account(&self, account: &UnixUserToken, expire: u64) -> Result<(), CacheError> {
|
||||
fn update_account(&self, account: &UserToken, expire: u64) -> Result<(), CacheError> {
|
||||
let data = serde_json::to_vec(account).map_err(|e| {
|
||||
error!("update_account json error -> {:?}", e);
|
||||
CacheError::SerdeJson
|
||||
|
@ -423,11 +422,12 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
// This is needed because sqlites 'insert or replace into', will null the password field
|
||||
// if present, and upsert MUST match the exact conflicting column, so that means we have
|
||||
// to manually manage the update or insert :( :(
|
||||
let account_uuid = account.uuid.as_hyphenated().to_string();
|
||||
|
||||
// Find anything conflicting and purge it.
|
||||
self.conn.execute("DELETE FROM account_t WHERE NOT uuid = :uuid AND (name = :name OR spn = :spn OR gidnumber = :gidnumber)",
|
||||
named_params!{
|
||||
":uuid": &account.uuid,
|
||||
":uuid": &account_uuid,
|
||||
":name": &account.name,
|
||||
":spn": &account.spn,
|
||||
":gidnumber": &account.gidnumber,
|
||||
|
@ -441,7 +441,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
let updated = self.conn.execute(
|
||||
"UPDATE account_t SET name=:name, spn=:spn, gidnumber=:gidnumber, token=:token, expiry=:expiry WHERE uuid = :uuid",
|
||||
named_params!{
|
||||
":uuid": &account.uuid,
|
||||
":uuid": &account_uuid,
|
||||
":name": &account.name,
|
||||
":spn": &account.spn,
|
||||
":gidnumber": &account.gidnumber,
|
||||
|
@ -461,7 +461,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
})?;
|
||||
|
||||
stmt.execute(named_params! {
|
||||
":uuid": &account.uuid,
|
||||
":uuid": &account_uuid,
|
||||
":name": &account.name,
|
||||
":spn": &account.spn,
|
||||
":gidnumber": &account.gidnumber,
|
||||
|
@ -482,7 +482,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
.prepare("DELETE FROM memberof_t WHERE a_uuid = :a_uuid")
|
||||
.map_err(|e| self.sqlite_error("prepare", &e))?;
|
||||
|
||||
stmt.execute([&account.uuid])
|
||||
stmt.execute([&account_uuid])
|
||||
.map(|r| {
|
||||
debug!("delete memberships -> {:?}", r);
|
||||
})
|
||||
|
@ -495,8 +495,8 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
// Now for each group, add the relation.
|
||||
account.groups.iter().try_for_each(|g| {
|
||||
stmt.execute(named_params! {
|
||||
":a_uuid": &account.uuid,
|
||||
":g_uuid": &g.uuid,
|
||||
":a_uuid": &account_uuid,
|
||||
":g_uuid": &g.uuid.as_hyphenated().to_string(),
|
||||
})
|
||||
.map(|r| {
|
||||
debug!("insert membership -> {:?}", r);
|
||||
|
@ -505,11 +505,13 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
fn delete_account(&self, a_uuid: &str) -> Result<(), CacheError> {
|
||||
fn delete_account(&self, a_uuid: Uuid) -> Result<(), CacheError> {
|
||||
let account_uuid = a_uuid.as_hyphenated().to_string();
|
||||
|
||||
self.conn
|
||||
.execute(
|
||||
"DELETE FROM memberof_t WHERE a_uuid = :a_uuid",
|
||||
params![a_uuid],
|
||||
params![&account_uuid],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| self.sqlite_error("account_t memberof_t cascade delete", &e))?;
|
||||
|
@ -517,13 +519,13 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
self.conn
|
||||
.execute(
|
||||
"DELETE FROM account_t WHERE uuid = :a_uuid",
|
||||
params![a_uuid],
|
||||
params![&account_uuid],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| self.sqlite_error("account_t delete", &e))
|
||||
}
|
||||
|
||||
fn update_account_password(&self, a_uuid: &str, cred: &str) -> Result<(), CacheError> {
|
||||
fn update_account_password(&self, a_uuid: Uuid, cred: &str) -> Result<(), CacheError> {
|
||||
#[allow(unused_variables)]
|
||||
let pw = if let Some(tcti_str) = self.require_tpm {
|
||||
// Do nothing.
|
||||
|
@ -552,7 +554,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
.execute(
|
||||
"UPDATE account_t SET password = :data WHERE uuid = :a_uuid",
|
||||
named_params! {
|
||||
":a_uuid": &a_uuid,
|
||||
":a_uuid": &a_uuid.as_hyphenated().to_string(),
|
||||
":data": &data,
|
||||
},
|
||||
)
|
||||
|
@ -560,7 +562,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn check_account_password(&self, a_uuid: &str, cred: &str) -> Result<bool, CacheError> {
|
||||
fn check_account_password(&self, a_uuid: Uuid, cred: &str) -> Result<bool, CacheError> {
|
||||
#[cfg(not(feature = "tpm"))]
|
||||
if self.require_tpm.is_some() {
|
||||
return Ok(false);
|
||||
|
@ -573,7 +575,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
|
||||
// Makes tuple (token, expiry)
|
||||
let data_iter = stmt
|
||||
.query_map([a_uuid], |row| row.get(0))
|
||||
.query_map([a_uuid.as_hyphenated().to_string()], |row| row.get(0))
|
||||
.map_err(|e| self.sqlite_error("query_map", &e))?;
|
||||
let data: Result<Vec<Vec<u8>>, _> = data_iter
|
||||
.map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
|
||||
|
@ -622,7 +624,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_group(&self, grp_id: &Id) -> Result<Option<(UnixGroupToken, u64)>, CacheError> {
|
||||
fn get_group(&self, grp_id: &Id) -> Result<Option<(GroupToken, u64)>, CacheError> {
|
||||
let data = match grp_id {
|
||||
Id::Name(n) => self.get_group_data_name(n.as_str()),
|
||||
Id::Gid(g) => self.get_group_data_gid(*g),
|
||||
|
@ -656,7 +658,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_group_members(&self, g_uuid: &str) -> Result<Vec<UnixUserToken>, CacheError> {
|
||||
fn get_group_members(&self, g_uuid: Uuid) -> Result<Vec<UserToken>, CacheError> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT account_t.token FROM (account_t, memberof_t) WHERE account_t.uuid = memberof_t.a_uuid AND memberof_t.g_uuid = :g_uuid")
|
||||
|
@ -665,7 +667,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
})?;
|
||||
|
||||
let data_iter = stmt
|
||||
.query_map([g_uuid], |row| row.get(0))
|
||||
.query_map([g_uuid.as_hyphenated().to_string()], |row| row.get(0))
|
||||
.map_err(|e| self.sqlite_error("query_map", &e))?;
|
||||
let data: Result<Vec<Vec<u8>>, _> = data_iter
|
||||
.map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
|
||||
|
@ -685,7 +687,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn get_groups(&self) -> Result<Vec<UnixGroupToken>, CacheError> {
|
||||
fn get_groups(&self) -> Result<Vec<GroupToken>, CacheError> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT token FROM group_t")
|
||||
|
@ -714,7 +716,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
.collect())
|
||||
}
|
||||
|
||||
fn update_group(&self, grp: &UnixGroupToken, expire: u64) -> Result<(), CacheError> {
|
||||
fn update_group(&self, grp: &GroupToken, expire: u64) -> Result<(), CacheError> {
|
||||
let data = serde_json::to_vec(grp).map_err(|e| {
|
||||
error!("json error -> {:?}", e);
|
||||
CacheError::SerdeJson
|
||||
|
@ -730,8 +732,9 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
self.sqlite_error("prepare", &e)
|
||||
})?;
|
||||
|
||||
// We have to to-str uuid as the sqlite impl makes it a blob which breaks our selects in get.
|
||||
stmt.execute(named_params! {
|
||||
":uuid": &grp.uuid,
|
||||
":uuid": &grp.uuid.as_hyphenated().to_string(),
|
||||
":name": &grp.name,
|
||||
":spn": &grp.spn,
|
||||
":gidnumber": &grp.gidnumber,
|
||||
|
@ -744,13 +747,17 @@ impl<'a> CacheTxn for DbTxn<'a> {
|
|||
.map_err(|e| self.sqlite_error("execute", &e))
|
||||
}
|
||||
|
||||
fn delete_group(&self, g_uuid: &str) -> Result<(), CacheError> {
|
||||
fn delete_group(&self, g_uuid: Uuid) -> Result<(), CacheError> {
|
||||
let group_uuid = g_uuid.as_hyphenated().to_string();
|
||||
self.conn
|
||||
.execute("DELETE FROM memberof_t WHERE g_uuid = :g_uuid", [g_uuid])
|
||||
.execute(
|
||||
"DELETE FROM memberof_t WHERE g_uuid = :g_uuid",
|
||||
[&group_uuid],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| self.sqlite_error("group_t memberof_t cascade delete", &e))?;
|
||||
self.conn
|
||||
.execute("DELETE FROM group_t WHERE uuid = :g_uuid", [g_uuid])
|
||||
.execute("DELETE FROM group_t WHERE uuid = :g_uuid", [&group_uuid])
|
||||
.map(|_| ())
|
||||
.map_err(|e| self.sqlite_error("group_t delete", &e))
|
||||
}
|
||||
|
@ -1129,11 +1136,9 @@ pub(crate) mod tpm {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
|
||||
// use std::assert_matches::assert_matches;
|
||||
|
||||
use super::{Cache, CacheTxn, Db};
|
||||
use crate::resolver::Id;
|
||||
use crate::idprovider::interface::{GroupToken, Id, UserToken};
|
||||
use crate::unix_config::TpmPolicy;
|
||||
|
||||
const TESTACCOUNT1_PASSWORD_A: &str = "password a for account1 test";
|
||||
|
@ -1146,12 +1151,12 @@ mod tests {
|
|||
let dbtxn = db.write().await;
|
||||
assert!(dbtxn.migrate().is_ok());
|
||||
|
||||
let mut ut1 = UnixUserToken {
|
||||
let mut ut1 = UserToken {
|
||||
name: "testuser".to_string(),
|
||||
spn: "testuser@example.com".to_string(),
|
||||
displayname: "Test User".to_string(),
|
||||
gidnumber: 2000,
|
||||
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(),
|
||||
uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
|
||||
shell: None,
|
||||
groups: Vec::new(),
|
||||
sshkeys: vec!["key-a".to_string()],
|
||||
|
@ -1230,11 +1235,11 @@ mod tests {
|
|||
let dbtxn = db.write().await;
|
||||
assert!(dbtxn.migrate().is_ok());
|
||||
|
||||
let mut gt1 = UnixGroupToken {
|
||||
let mut gt1 = GroupToken {
|
||||
name: "testgroup".to_string(),
|
||||
spn: "testgroup@example.com".to_string(),
|
||||
gidnumber: 2000,
|
||||
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(),
|
||||
uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
|
||||
};
|
||||
|
||||
let id_name = Id::Name("testgroup".to_string());
|
||||
|
@ -1305,26 +1310,26 @@ mod tests {
|
|||
let dbtxn = db.write().await;
|
||||
assert!(dbtxn.migrate().is_ok());
|
||||
|
||||
let gt1 = UnixGroupToken {
|
||||
let gt1 = GroupToken {
|
||||
name: "testuser".to_string(),
|
||||
spn: "testuser@example.com".to_string(),
|
||||
gidnumber: 2000,
|
||||
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(),
|
||||
uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
|
||||
};
|
||||
|
||||
let gt2 = UnixGroupToken {
|
||||
let gt2 = GroupToken {
|
||||
name: "testgroup".to_string(),
|
||||
spn: "testgroup@example.com".to_string(),
|
||||
gidnumber: 2001,
|
||||
uuid: "b500be97-8552-42a5-aca0-668bc5625705".to_string(),
|
||||
uuid: uuid::uuid!("b500be97-8552-42a5-aca0-668bc5625705"),
|
||||
};
|
||||
|
||||
let mut ut1 = UnixUserToken {
|
||||
let mut ut1 = UserToken {
|
||||
name: "testuser".to_string(),
|
||||
spn: "testuser@example.com".to_string(),
|
||||
displayname: "Test User".to_string(),
|
||||
gidnumber: 2000,
|
||||
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(),
|
||||
uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
|
||||
shell: None,
|
||||
groups: vec![gt1.clone(), gt2],
|
||||
sshkeys: vec!["key-a".to_string()],
|
||||
|
@ -1341,10 +1346,10 @@ mod tests {
|
|||
|
||||
// Now, get the memberships of the two groups.
|
||||
let m1 = dbtxn
|
||||
.get_group_members("0302b99c-f0f6-41ab-9492-852692b0fd16")
|
||||
.get_group_members(uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"))
|
||||
.unwrap();
|
||||
let m2 = dbtxn
|
||||
.get_group_members("b500be97-8552-42a5-aca0-668bc5625705")
|
||||
.get_group_members(uuid::uuid!("b500be97-8552-42a5-aca0-668bc5625705"))
|
||||
.unwrap();
|
||||
assert!(m1[0].name == "testuser");
|
||||
assert!(m2[0].name == "testuser");
|
||||
|
@ -1355,10 +1360,10 @@ mod tests {
|
|||
|
||||
// Check that the memberships have updated correctly.
|
||||
let m1 = dbtxn
|
||||
.get_group_members("0302b99c-f0f6-41ab-9492-852692b0fd16")
|
||||
.get_group_members(uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"))
|
||||
.unwrap();
|
||||
let m2 = dbtxn
|
||||
.get_group_members("b500be97-8552-42a5-aca0-668bc5625705")
|
||||
.get_group_members(uuid::uuid!("b500be97-8552-42a5-aca0-668bc5625705"))
|
||||
.unwrap();
|
||||
assert!(m1[0].name == "testuser");
|
||||
assert!(m2.is_empty());
|
||||
|
@ -1381,13 +1386,13 @@ mod tests {
|
|||
let dbtxn = db.write().await;
|
||||
assert!(dbtxn.migrate().is_ok());
|
||||
|
||||
let uuid1 = "0302b99c-f0f6-41ab-9492-852692b0fd16";
|
||||
let mut ut1 = UnixUserToken {
|
||||
let uuid1 = uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16");
|
||||
let mut ut1 = UserToken {
|
||||
name: "testuser".to_string(),
|
||||
spn: "testuser@example.com".to_string(),
|
||||
displayname: "Test User".to_string(),
|
||||
gidnumber: 2000,
|
||||
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(),
|
||||
uuid: uuid1,
|
||||
shell: None,
|
||||
groups: Vec::new(),
|
||||
sshkeys: vec!["key-a".to_string()],
|
||||
|
@ -1451,18 +1456,18 @@ mod tests {
|
|||
let dbtxn = db.write().await;
|
||||
assert!(dbtxn.migrate().is_ok());
|
||||
|
||||
let mut gt1 = UnixGroupToken {
|
||||
let mut gt1 = GroupToken {
|
||||
name: "testgroup".to_string(),
|
||||
spn: "testgroup@example.com".to_string(),
|
||||
gidnumber: 2000,
|
||||
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(),
|
||||
uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
|
||||
};
|
||||
|
||||
let gt2 = UnixGroupToken {
|
||||
let gt2 = GroupToken {
|
||||
name: "testgroup".to_string(),
|
||||
spn: "testgroup@example.com".to_string(),
|
||||
gidnumber: 2001,
|
||||
uuid: "799123b2-3802-4b19-b0b8-1ffae2aa9a4b".to_string(),
|
||||
uuid: uuid::uuid!("799123b2-3802-4b19-b0b8-1ffae2aa9a4b"),
|
||||
};
|
||||
|
||||
let id_name = Id::Name("testgroup".to_string());
|
||||
|
@ -1475,7 +1480,7 @@ mod tests {
|
|||
// test adding a group
|
||||
dbtxn.update_group(>1, 0).unwrap();
|
||||
let r0 = dbtxn.get_group(&id_name).unwrap();
|
||||
assert!(r0.unwrap().0.uuid == "0302b99c-f0f6-41ab-9492-852692b0fd16");
|
||||
assert!(r0.unwrap().0.uuid == uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"));
|
||||
|
||||
// Do the "rename" of gt1 which is what would allow gt2 to be valid.
|
||||
gt1.name = "testgroup2".to_string();
|
||||
|
@ -1483,7 +1488,7 @@ mod tests {
|
|||
// Now, add gt2 which dups on gt1 name/spn.
|
||||
dbtxn.update_group(>2, 0).unwrap();
|
||||
let r2 = dbtxn.get_group(&id_name).unwrap();
|
||||
assert!(r2.unwrap().0.uuid == "799123b2-3802-4b19-b0b8-1ffae2aa9a4b");
|
||||
assert!(r2.unwrap().0.uuid == uuid::uuid!("799123b2-3802-4b19-b0b8-1ffae2aa9a4b"));
|
||||
let r3 = dbtxn.get_group(&id_name2).unwrap();
|
||||
assert!(r3.is_none());
|
||||
|
||||
|
@ -1492,9 +1497,9 @@ mod tests {
|
|||
|
||||
// Both now coexist
|
||||
let r4 = dbtxn.get_group(&id_name).unwrap();
|
||||
assert!(r4.unwrap().0.uuid == "799123b2-3802-4b19-b0b8-1ffae2aa9a4b");
|
||||
assert!(r4.unwrap().0.uuid == uuid::uuid!("799123b2-3802-4b19-b0b8-1ffae2aa9a4b"));
|
||||
let r5 = dbtxn.get_group(&id_name2).unwrap();
|
||||
assert!(r5.unwrap().0.uuid == "0302b99c-f0f6-41ab-9492-852692b0fd16");
|
||||
assert!(r5.unwrap().0.uuid == uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"));
|
||||
|
||||
assert!(dbtxn.commit().is_ok());
|
||||
}
|
||||
|
@ -1506,24 +1511,24 @@ mod tests {
|
|||
let dbtxn = db.write().await;
|
||||
assert!(dbtxn.migrate().is_ok());
|
||||
|
||||
let mut ut1 = UnixUserToken {
|
||||
let mut ut1 = UserToken {
|
||||
name: "testuser".to_string(),
|
||||
spn: "testuser@example.com".to_string(),
|
||||
displayname: "Test User".to_string(),
|
||||
gidnumber: 2000,
|
||||
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(),
|
||||
uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
|
||||
shell: None,
|
||||
groups: Vec::new(),
|
||||
sshkeys: vec!["key-a".to_string()],
|
||||
valid: true,
|
||||
};
|
||||
|
||||
let ut2 = UnixUserToken {
|
||||
let ut2 = UserToken {
|
||||
name: "testuser".to_string(),
|
||||
spn: "testuser@example.com".to_string(),
|
||||
displayname: "Test User".to_string(),
|
||||
gidnumber: 2001,
|
||||
uuid: "799123b2-3802-4b19-b0b8-1ffae2aa9a4b".to_string(),
|
||||
uuid: uuid::uuid!("799123b2-3802-4b19-b0b8-1ffae2aa9a4b"),
|
||||
shell: None,
|
||||
groups: Vec::new(),
|
||||
sshkeys: vec!["key-a".to_string()],
|
||||
|
@ -1540,7 +1545,7 @@ mod tests {
|
|||
// test adding an account
|
||||
dbtxn.update_account(&ut1, 0).unwrap();
|
||||
let r0 = dbtxn.get_account(&id_name).unwrap();
|
||||
assert!(r0.unwrap().0.uuid == "0302b99c-f0f6-41ab-9492-852692b0fd16");
|
||||
assert!(r0.unwrap().0.uuid == uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"));
|
||||
|
||||
// Do the "rename" of gt1 which is what would allow gt2 to be valid.
|
||||
ut1.name = "testuser2".to_string();
|
||||
|
@ -1548,7 +1553,7 @@ mod tests {
|
|||
// Now, add gt2 which dups on gt1 name/spn.
|
||||
dbtxn.update_account(&ut2, 0).unwrap();
|
||||
let r2 = dbtxn.get_account(&id_name).unwrap();
|
||||
assert!(r2.unwrap().0.uuid == "799123b2-3802-4b19-b0b8-1ffae2aa9a4b");
|
||||
assert!(r2.unwrap().0.uuid == uuid::uuid!("799123b2-3802-4b19-b0b8-1ffae2aa9a4b"));
|
||||
let r3 = dbtxn.get_account(&id_name2).unwrap();
|
||||
assert!(r3.is_none());
|
||||
|
||||
|
@ -1557,9 +1562,9 @@ mod tests {
|
|||
|
||||
// Both now coexist
|
||||
let r4 = dbtxn.get_account(&id_name).unwrap();
|
||||
assert!(r4.unwrap().0.uuid == "799123b2-3802-4b19-b0b8-1ffae2aa9a4b");
|
||||
assert!(r4.unwrap().0.uuid == uuid::uuid!("799123b2-3802-4b19-b0b8-1ffae2aa9a4b"));
|
||||
let r5 = dbtxn.get_account(&id_name2).unwrap();
|
||||
assert!(r5.unwrap().0.uuid == "0302b99c-f0f6-41ab-9492-852692b0fd16");
|
||||
assert!(r5.unwrap().0.uuid == uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"));
|
||||
|
||||
assert!(dbtxn.commit().is_ok());
|
||||
}
|
||||
|
|
67
unix_integration/src/idprovider/interface.rs
Normal file
67
unix_integration/src/idprovider/interface.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// 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,
|
||||
/// The provider made an invalid request to the idp, and the result is not able to
|
||||
/// be used by the resolver.
|
||||
BadRequest,
|
||||
/// The idp has indicated that the requested resource does not exist and should
|
||||
/// be considered deleted, removed, or not present.
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Id {
|
||||
Name(String),
|
||||
Gid(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct GroupToken {
|
||||
pub name: String,
|
||||
pub spn: String,
|
||||
pub uuid: Uuid,
|
||||
pub gidnumber: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UserToken {
|
||||
pub name: String,
|
||||
pub spn: String,
|
||||
pub uuid: Uuid,
|
||||
pub gidnumber: u32,
|
||||
pub displayname: String,
|
||||
pub shell: Option<String>,
|
||||
pub groups: Vec<GroupToken>,
|
||||
// Could there be a better type here?
|
||||
pub sshkeys: Vec<String>,
|
||||
// Defaults to false.
|
||||
pub valid: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait IdProvider {
|
||||
async fn provider_authenticate(&self) -> Result<(), IdpError>;
|
||||
|
||||
async fn unix_user_get(&self, id: &Id) -> Result<UserToken, IdpError>;
|
||||
|
||||
async fn unix_user_authenticate(
|
||||
&self,
|
||||
id: &Id,
|
||||
cred: &str,
|
||||
) -> Result<Option<UserToken>, IdpError>;
|
||||
|
||||
async fn unix_group_get(&self, id: &Id) -> Result<GroupToken, IdpError>;
|
||||
}
|
264
unix_integration/src/idprovider/kanidm.rs
Normal file
264
unix_integration/src/idprovider/kanidm.rs
Normal file
|
@ -0,0 +1,264 @@
|
|||
use async_trait::async_trait;
|
||||
use kanidm_client::{ClientError, KanidmClient, StatusCode};
|
||||
use kanidm_proto::v1::{OperationError, UnixGroupToken, UnixUserToken};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use super::interface::{GroupToken, Id, IdProvider, IdpError, UserToken};
|
||||
|
||||
pub struct KanidmProvider {
|
||||
client: RwLock<KanidmClient>,
|
||||
}
|
||||
|
||||
impl KanidmProvider {
|
||||
pub fn new(client: KanidmClient) -> Self {
|
||||
KanidmProvider {
|
||||
client: RwLock::new(client),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnixUserToken> for UserToken {
|
||||
fn from(value: UnixUserToken) -> UserToken {
|
||||
let UnixUserToken {
|
||||
name,
|
||||
spn,
|
||||
displayname,
|
||||
gidnumber,
|
||||
uuid,
|
||||
shell,
|
||||
groups,
|
||||
sshkeys,
|
||||
valid,
|
||||
} = value;
|
||||
|
||||
let groups = groups
|
||||
.into_iter()
|
||||
.map(GroupToken::from)
|
||||
.collect();
|
||||
|
||||
UserToken {
|
||||
name,
|
||||
spn,
|
||||
uuid,
|
||||
gidnumber,
|
||||
displayname,
|
||||
shell,
|
||||
groups,
|
||||
sshkeys,
|
||||
valid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnixGroupToken> for GroupToken {
|
||||
fn from(value: UnixGroupToken) -> GroupToken {
|
||||
let UnixGroupToken {
|
||||
name,
|
||||
spn,
|
||||
uuid,
|
||||
gidnumber,
|
||||
} = value;
|
||||
|
||||
GroupToken {
|
||||
name,
|
||||
spn,
|
||||
uuid,
|
||||
gidnumber,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl IdProvider for KanidmProvider {
|
||||
// Needs .read on all types except re-auth.
|
||||
|
||||
async fn provider_authenticate(&self) -> Result<(), IdpError> {
|
||||
match self.client.write().await.auth_anonymous().await {
|
||||
Ok(_uat) => Ok(()),
|
||||
Err(err) => {
|
||||
error!(?err, "Provider authentication failed");
|
||||
Err(IdpError::ProviderUnauthorised)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn unix_user_get(&self, id: &Id) -> Result<UserToken, IdpError> {
|
||||
match self
|
||||
.client
|
||||
.read()
|
||||
.await
|
||||
.idm_account_unix_token_get(id.to_string().as_str())
|
||||
.await
|
||||
{
|
||||
Ok(tok) => Ok(UserToken::from(tok)),
|
||||
Err(ClientError::Transport(err)) => {
|
||||
error!(?err);
|
||||
Err(IdpError::Transport)
|
||||
}
|
||||
Err(ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid)) => {
|
||||
match reason {
|
||||
Some(OperationError::NotAuthenticated) => warn!(
|
||||
"session not authenticated - attempting reauthentication - eventid {}",
|
||||
opid
|
||||
),
|
||||
Some(OperationError::SessionExpired) => warn!(
|
||||
"session expired - attempting reauthentication - eventid {}",
|
||||
opid
|
||||
),
|
||||
e => error!(
|
||||
"authentication error {:?}, moving to offline - eventid {}",
|
||||
e, opid
|
||||
),
|
||||
};
|
||||
Err(IdpError::ProviderUnauthorised)
|
||||
}
|
||||
Err(ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
))
|
||||
| Err(ClientError::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
))
|
||||
| Err(ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::InvalidAccountState(_)),
|
||||
opid,
|
||||
)) => {
|
||||
debug!(
|
||||
?opid,
|
||||
"entry has been removed or is no longer a valid posix account"
|
||||
);
|
||||
Err(IdpError::NotFound)
|
||||
}
|
||||
Err(err) => {
|
||||
error!(?err, "client error");
|
||||
Err(IdpError::BadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn unix_user_authenticate(
|
||||
&self,
|
||||
id: &Id,
|
||||
cred: &str,
|
||||
) -> Result<Option<UserToken>, IdpError> {
|
||||
match self
|
||||
.client
|
||||
.read()
|
||||
.await
|
||||
.idm_account_unix_cred_verify(id.to_string().as_str(), cred)
|
||||
.await
|
||||
{
|
||||
Ok(Some(n_tok)) => Ok(Some(UserToken::from(n_tok))),
|
||||
Ok(None) => Ok(None),
|
||||
Err(ClientError::Transport(err)) => {
|
||||
error!(?err);
|
||||
Err(IdpError::Transport)
|
||||
}
|
||||
Err(ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid)) => {
|
||||
match reason {
|
||||
Some(OperationError::NotAuthenticated) => warn!(
|
||||
"session not authenticated - attempting reauthentication - eventid {}",
|
||||
opid
|
||||
),
|
||||
Some(OperationError::SessionExpired) => warn!(
|
||||
"session expired - attempting reauthentication - eventid {}",
|
||||
opid
|
||||
),
|
||||
e => error!(
|
||||
"authentication error {:?}, moving to offline - eventid {}",
|
||||
e, opid
|
||||
),
|
||||
};
|
||||
Err(IdpError::ProviderUnauthorised)
|
||||
}
|
||||
Err(ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
))
|
||||
| Err(ClientError::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
))
|
||||
| Err(ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::InvalidAccountState(_)),
|
||||
opid,
|
||||
)) => {
|
||||
error!(
|
||||
"unknown account or is not a valid posix account - eventid {}",
|
||||
opid
|
||||
);
|
||||
Err(IdpError::NotFound)
|
||||
}
|
||||
Err(err) => {
|
||||
error!(?err, "client error");
|
||||
// Some other unknown processing error?
|
||||
Err(IdpError::BadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn unix_group_get(&self, id: &Id) -> Result<GroupToken, IdpError> {
|
||||
match self
|
||||
.client
|
||||
.read()
|
||||
.await
|
||||
.idm_group_unix_token_get(id.to_string().as_str())
|
||||
.await
|
||||
{
|
||||
Ok(tok) => Ok(GroupToken::from(tok)),
|
||||
Err(ClientError::Transport(err)) => {
|
||||
error!(?err);
|
||||
Err(IdpError::Transport)
|
||||
}
|
||||
Err(ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid)) => {
|
||||
match reason {
|
||||
Some(OperationError::NotAuthenticated) => warn!(
|
||||
"session not authenticated - attempting reauthentication - eventid {}",
|
||||
opid
|
||||
),
|
||||
Some(OperationError::SessionExpired) => warn!(
|
||||
"session expired - attempting reauthentication - eventid {}",
|
||||
opid
|
||||
),
|
||||
e => error!(
|
||||
"authentication error {:?}, moving to offline - eventid {}",
|
||||
e, opid
|
||||
),
|
||||
};
|
||||
Err(IdpError::ProviderUnauthorised)
|
||||
}
|
||||
Err(ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
))
|
||||
| Err(ClientError::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
))
|
||||
| Err(ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::InvalidAccountState(_)),
|
||||
opid,
|
||||
)) => {
|
||||
debug!(
|
||||
?opid,
|
||||
"entry has been removed or is no longer a valid posix group"
|
||||
);
|
||||
Err(IdpError::NotFound)
|
||||
}
|
||||
Err(err) => {
|
||||
error!(?err, "client error");
|
||||
Err(IdpError::BadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
unix_integration/src/idprovider/mod.rs
Normal file
2
unix_integration/src/idprovider/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod interface;
|
||||
pub mod kanidm;
|
|
@ -26,6 +26,8 @@ pub mod constants;
|
|||
#[cfg(target_family = "unix")]
|
||||
pub mod db;
|
||||
#[cfg(target_family = "unix")]
|
||||
pub mod idprovider;
|
||||
#[cfg(target_family = "unix")]
|
||||
pub mod resolver;
|
||||
#[cfg(all(target_family = "unix", feature = "selinux"))]
|
||||
pub mod selinux_util;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// use async_trait::async_trait;
|
||||
use hashbrown::HashSet;
|
||||
use std::collections::BTreeSet;
|
||||
use std::num::NonZeroUsize;
|
||||
|
@ -6,26 +7,18 @@ use std::path::Path;
|
|||
use std::string::ToString;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use kanidm_client::{ClientError, KanidmClient};
|
||||
use kanidm_proto::v1::{OperationError, UnixGroupToken, UnixUserToken};
|
||||
use lru::LruCache;
|
||||
use reqwest::StatusCode;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tokio::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db::{Cache, CacheTxn, Db};
|
||||
|
||||
use crate::idprovider::interface::{GroupToken, Id, IdProvider, IdpError, UserToken};
|
||||
use crate::unix_config::{HomeAttr, UidAttr};
|
||||
use crate::unix_proto::{HomeDirectoryInfo, NssGroup, NssUser};
|
||||
|
||||
// use crate::unix_passwd::{EtcUser, EtcGroup};
|
||||
|
||||
const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(2048) };
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Id {
|
||||
Name(String),
|
||||
Gid(u32),
|
||||
}
|
||||
const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(128) };
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum CacheState {
|
||||
|
@ -35,9 +28,14 @@ enum CacheState {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Resolver {
|
||||
pub struct Resolver<I>
|
||||
where
|
||||
I: IdProvider,
|
||||
{
|
||||
// Generic / modular types.
|
||||
db: Db,
|
||||
client: RwLock<KanidmClient>,
|
||||
client: I,
|
||||
// Types to update still.
|
||||
state: Mutex<CacheState>,
|
||||
pam_allow_groups: BTreeSet<String>,
|
||||
timeout_seconds: u64,
|
||||
|
@ -61,18 +59,16 @@ impl ToString for Id {
|
|||
}
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
impl<I> Resolver<I>
|
||||
where
|
||||
I: IdProvider,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn new(
|
||||
db: Db,
|
||||
// need db path
|
||||
// path: &str,
|
||||
// tpm_policy: &TpmPolicy,
|
||||
|
||||
client: I,
|
||||
// cache timeout
|
||||
timeout_seconds: u64,
|
||||
//
|
||||
client: KanidmClient,
|
||||
pam_allow_groups: Vec<String>,
|
||||
default_shell: String,
|
||||
home_prefix: String,
|
||||
|
@ -97,7 +93,7 @@ impl Resolver {
|
|||
// being valid from "now".
|
||||
Ok(Resolver {
|
||||
db,
|
||||
client: RwLock::new(client),
|
||||
client,
|
||||
state: Mutex::new(CacheState::OfflineNextCheck(SystemTime::now())),
|
||||
timeout_seconds,
|
||||
pam_allow_groups: pam_allow_groups.into_iter().collect(),
|
||||
|
@ -150,12 +146,12 @@ impl Resolver {
|
|||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn get_cached_usertokens(&self) -> Result<Vec<UnixUserToken>, ()> {
|
||||
async fn get_cached_usertokens(&self) -> Result<Vec<UserToken>, ()> {
|
||||
let dbtxn = self.db.write().await;
|
||||
dbtxn.get_accounts().map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn get_cached_grouptokens(&self) -> Result<Vec<UnixGroupToken>, ()> {
|
||||
async fn get_cached_grouptokens(&self) -> Result<Vec<GroupToken>, ()> {
|
||||
let dbtxn = self.db.write().await;
|
||||
dbtxn.get_groups().map_err(|_| ())
|
||||
}
|
||||
|
@ -193,10 +189,7 @@ impl Resolver {
|
|||
nxset_txn.contains(&Id::Gid(idnumber)) || nxset_txn.contains(&Id::Name(name.to_string()))
|
||||
}
|
||||
|
||||
async fn get_cached_usertoken(
|
||||
&self,
|
||||
account_id: &Id,
|
||||
) -> Result<(bool, Option<UnixUserToken>), ()> {
|
||||
async fn get_cached_usertoken(&self, account_id: &Id) -> Result<(bool, Option<UserToken>), ()> {
|
||||
// Account_id could be:
|
||||
// * gidnumber
|
||||
// * name
|
||||
|
@ -244,10 +237,7 @@ impl Resolver {
|
|||
} // end match r
|
||||
}
|
||||
|
||||
async fn get_cached_grouptoken(
|
||||
&self,
|
||||
grp_id: &Id,
|
||||
) -> Result<(bool, Option<UnixGroupToken>), ()> {
|
||||
async fn get_cached_grouptoken(&self, grp_id: &Id) -> Result<(bool, Option<GroupToken>), ()> {
|
||||
// grp_id could be:
|
||||
// * gidnumber
|
||||
// * name
|
||||
|
@ -295,7 +285,7 @@ impl Resolver {
|
|||
}
|
||||
}
|
||||
|
||||
async fn set_cache_usertoken(&self, token: &mut UnixUserToken) -> Result<(), ()> {
|
||||
async fn set_cache_usertoken(&self, token: &mut UserToken) -> Result<(), ()> {
|
||||
// Set an expiry
|
||||
let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
|
||||
let offset = ex_time
|
||||
|
@ -350,7 +340,7 @@ impl Resolver {
|
|||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn set_cache_grouptoken(&self, token: &UnixGroupToken) -> Result<(), ()> {
|
||||
async fn set_cache_grouptoken(&self, token: &GroupToken) -> Result<(), ()> {
|
||||
// Set an expiry
|
||||
let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
|
||||
let offset = ex_time
|
||||
|
@ -366,7 +356,7 @@ impl Resolver {
|
|||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_cache_usertoken(&self, a_uuid: &str) -> Result<(), ()> {
|
||||
async fn delete_cache_usertoken(&self, a_uuid: Uuid) -> Result<(), ()> {
|
||||
let dbtxn = self.db.write().await;
|
||||
dbtxn
|
||||
.delete_account(a_uuid)
|
||||
|
@ -374,7 +364,7 @@ impl Resolver {
|
|||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_cache_grouptoken(&self, g_uuid: &str) -> Result<(), ()> {
|
||||
async fn delete_cache_grouptoken(&self, g_uuid: Uuid) -> Result<(), ()> {
|
||||
let dbtxn = self.db.write().await;
|
||||
dbtxn
|
||||
.delete_group(g_uuid)
|
||||
|
@ -382,7 +372,7 @@ impl Resolver {
|
|||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn set_cache_userpassword(&self, a_uuid: &str, cred: &str) -> Result<(), ()> {
|
||||
async fn set_cache_userpassword(&self, a_uuid: Uuid, cred: &str) -> Result<(), ()> {
|
||||
let dbtxn = self.db.write().await;
|
||||
dbtxn
|
||||
.update_account_password(a_uuid, cred)
|
||||
|
@ -390,7 +380,7 @@ impl Resolver {
|
|||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn check_cache_userpassword(&self, a_uuid: &str, cred: &str) -> Result<bool, ()> {
|
||||
async fn check_cache_userpassword(&self, a_uuid: Uuid, cred: &str) -> Result<bool, ()> {
|
||||
let dbtxn = self.db.write().await;
|
||||
dbtxn
|
||||
.check_account_password(a_uuid, cred)
|
||||
|
@ -401,19 +391,13 @@ impl Resolver {
|
|||
async fn refresh_usertoken(
|
||||
&self,
|
||||
account_id: &Id,
|
||||
token: Option<UnixUserToken>,
|
||||
) -> Result<Option<UnixUserToken>, ()> {
|
||||
match self
|
||||
.client
|
||||
.read()
|
||||
.await
|
||||
.idm_account_unix_token_get(account_id.to_string().as_str())
|
||||
.await
|
||||
{
|
||||
token: Option<UserToken>,
|
||||
) -> Result<Option<UserToken>, ()> {
|
||||
match self.client.unix_user_get(account_id).await {
|
||||
Ok(mut n_tok) => {
|
||||
if self.check_nxset(&n_tok.name, n_tok.gidnumber).await {
|
||||
// Refuse to release the token, it's in the denied set.
|
||||
self.delete_cache_usertoken(&n_tok.uuid).await?;
|
||||
self.delete_cache_usertoken(n_tok.uuid).await?;
|
||||
Ok(None)
|
||||
} else {
|
||||
// We have the token!
|
||||
|
@ -421,85 +405,49 @@ impl Resolver {
|
|||
Ok(Some(n_tok))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
match e {
|
||||
ClientError::Transport(er) => {
|
||||
error!("transport error, moving to offline -> {:?}", er);
|
||||
Err(IdpError::Transport) => {
|
||||
error!("transport error, moving to offline");
|
||||
// Something went wrong, mark offline.
|
||||
let time = SystemTime::now().add(Duration::from_secs(15));
|
||||
self.set_cachestate(CacheState::OfflineNextCheck(time))
|
||||
.await;
|
||||
Ok(token)
|
||||
}
|
||||
ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid) => {
|
||||
match reason {
|
||||
Some(OperationError::NotAuthenticated) =>
|
||||
warn!("session not authenticated - attempting reauthentication - eventid {}", opid),
|
||||
Some(OperationError::SessionExpired) =>
|
||||
warn!("session expired - attempting reauthentication - eventid {}", opid),
|
||||
e =>
|
||||
error!(
|
||||
"authentication error {:?}, moving to offline - eventid {}", e, opid
|
||||
),
|
||||
};
|
||||
Err(IdpError::ProviderUnauthorised) => {
|
||||
// Something went wrong, mark offline to force a re-auth ASAP.
|
||||
let time = SystemTime::now().sub(Duration::from_secs(1));
|
||||
self.set_cachestate(CacheState::OfflineNextCheck(time))
|
||||
.await;
|
||||
Ok(token)
|
||||
}
|
||||
ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
)
|
||||
| ClientError::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
)
|
||||
| ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::InvalidAccountState(_)),
|
||||
opid,
|
||||
) => {
|
||||
// We wele able to contact the server but the entry has been removed, or
|
||||
Err(IdpError::NotFound) => {
|
||||
// We were able to contact the server but the entry has been removed, or
|
||||
// is not longer a valid posix account.
|
||||
debug!("entry has been removed or is no longer a valid posix account, clearing from cache ... - eventid {}", opid);
|
||||
if let Some(tok) = token {
|
||||
self.delete_cache_usertoken(&tok.uuid).await?;
|
||||
self.delete_cache_usertoken(tok.uuid).await?;
|
||||
};
|
||||
// Cache the NX here.
|
||||
self.set_nxcache(account_id).await;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
er => {
|
||||
error!("client error -> {:?}", er);
|
||||
Err(IdpError::BadRequest) => {
|
||||
// Some other transient error, continue with the token.
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn refresh_grouptoken(
|
||||
&self,
|
||||
grp_id: &Id,
|
||||
token: Option<UnixGroupToken>,
|
||||
) -> Result<Option<UnixGroupToken>, ()> {
|
||||
match self
|
||||
.client
|
||||
.read()
|
||||
.await
|
||||
.idm_group_unix_token_get(grp_id.to_string().as_str())
|
||||
.await
|
||||
{
|
||||
token: Option<GroupToken>,
|
||||
) -> Result<Option<GroupToken>, ()> {
|
||||
match self.client.unix_group_get(grp_id).await {
|
||||
Ok(n_tok) => {
|
||||
if self.check_nxset(&n_tok.name, n_tok.gidnumber).await {
|
||||
// Refuse to release the token, it's in the denied set.
|
||||
self.delete_cache_grouptoken(&n_tok.uuid).await?;
|
||||
self.delete_cache_grouptoken(n_tok.uuid).await?;
|
||||
Ok(None)
|
||||
} else {
|
||||
// We have the token!
|
||||
|
@ -507,66 +455,37 @@ impl Resolver {
|
|||
Ok(Some(n_tok))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
match e {
|
||||
ClientError::Transport(er) => {
|
||||
error!("transport error, moving to offline -> {:?}", er);
|
||||
Err(IdpError::Transport) => {
|
||||
error!("transport error, moving to offline");
|
||||
// Something went wrong, mark offline.
|
||||
let time = SystemTime::now().add(Duration::from_secs(15));
|
||||
self.set_cachestate(CacheState::OfflineNextCheck(time))
|
||||
.await;
|
||||
Ok(token)
|
||||
}
|
||||
ClientError::Http(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Some(OperationError::NotAuthenticated),
|
||||
opid,
|
||||
) => {
|
||||
error!(
|
||||
"transport unauthenticated, moving to offline - eventid {}",
|
||||
opid
|
||||
);
|
||||
Err(IdpError::ProviderUnauthorised) => {
|
||||
// Something went wrong, mark offline.
|
||||
let time = SystemTime::now().add(Duration::from_secs(15));
|
||||
self.set_cachestate(CacheState::OfflineNextCheck(time))
|
||||
.await;
|
||||
Ok(token)
|
||||
}
|
||||
ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
)
|
||||
| ClientError::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
)
|
||||
| ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::InvalidAccountState(_)),
|
||||
opid,
|
||||
) => {
|
||||
debug!("entry has been removed or is no longer a valid posix group, clearing from cache ... - eventid {}", opid);
|
||||
Err(IdpError::NotFound) => {
|
||||
if let Some(tok) = token {
|
||||
self.delete_cache_grouptoken(&tok.uuid).await?;
|
||||
self.delete_cache_grouptoken(tok.uuid).await?;
|
||||
};
|
||||
// Cache the NX here.
|
||||
self.set_nxcache(grp_id).await;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
er => {
|
||||
error!("client error -> {:?}", er);
|
||||
Err(IdpError::BadRequest) => {
|
||||
// Some other transient error, continue with the token.
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_usertoken(&self, account_id: Id) -> Result<Option<UnixUserToken>, ()> {
|
||||
async fn get_usertoken(&self, account_id: Id) -> Result<Option<UserToken>, ()> {
|
||||
debug!("get_usertoken");
|
||||
// get the item from the cache
|
||||
let (expired, item) = self.get_cached_usertoken(&account_id).await.map_err(|e| {
|
||||
|
@ -618,7 +537,7 @@ impl Resolver {
|
|||
})
|
||||
}
|
||||
|
||||
async fn get_grouptoken(&self, grp_id: Id) -> Result<Option<UnixGroupToken>, ()> {
|
||||
async fn get_grouptoken(&self, grp_id: Id) -> Result<Option<GroupToken>, ()> {
|
||||
debug!("get_grouptoken");
|
||||
let (expired, item) = self.get_cached_grouptoken(&grp_id).await.map_err(|e| {
|
||||
debug!("get_grouptoken error -> {:?}", e);
|
||||
|
@ -665,11 +584,11 @@ impl Resolver {
|
|||
}
|
||||
}
|
||||
|
||||
async fn get_groupmembers(&self, uuid: &str) -> Vec<String> {
|
||||
async fn get_groupmembers(&self, g_uuid: Uuid) -> Vec<String> {
|
||||
let dbtxn = self.db.write().await;
|
||||
|
||||
dbtxn
|
||||
.get_group_members(uuid)
|
||||
.get_group_members(g_uuid)
|
||||
.unwrap_or_else(|_| Vec::new())
|
||||
.into_iter()
|
||||
.map(|ut| self.token_uidattr(&ut))
|
||||
|
@ -692,37 +611,37 @@ impl Resolver {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn token_homedirectory_alias(&self, token: &UnixUserToken) -> Option<String> {
|
||||
fn token_homedirectory_alias(&self, token: &UserToken) -> Option<String> {
|
||||
self.home_alias.map(|t| match t {
|
||||
// If we have an alias. use it.
|
||||
HomeAttr::Uuid => token.uuid.as_str().to_string(),
|
||||
HomeAttr::Uuid => token.uuid.hyphenated().to_string(),
|
||||
HomeAttr::Spn => token.spn.as_str().to_string(),
|
||||
HomeAttr::Name => token.name.as_str().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn token_homedirectory_attr(&self, token: &UnixUserToken) -> String {
|
||||
fn token_homedirectory_attr(&self, token: &UserToken) -> String {
|
||||
match self.home_attr {
|
||||
HomeAttr::Uuid => token.uuid.as_str().to_string(),
|
||||
HomeAttr::Uuid => token.uuid.hyphenated().to_string(),
|
||||
HomeAttr::Spn => token.spn.as_str().to_string(),
|
||||
HomeAttr::Name => token.name.as_str().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn token_homedirectory(&self, token: &UnixUserToken) -> String {
|
||||
fn token_homedirectory(&self, token: &UserToken) -> String {
|
||||
self.token_homedirectory_alias(token)
|
||||
.unwrap_or_else(|| self.token_homedirectory_attr(token))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn token_abs_homedirectory(&self, token: &UnixUserToken) -> String {
|
||||
fn token_abs_homedirectory(&self, token: &UserToken) -> String {
|
||||
format!("{}{}", self.home_prefix, self.token_homedirectory(token))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn token_uidattr(&self, token: &UnixUserToken) -> String {
|
||||
fn token_uidattr(&self, token: &UserToken) -> String {
|
||||
match self.uid_attr_map {
|
||||
UidAttr::Spn => token.spn.as_str(),
|
||||
UidAttr::Name => token.name.as_str(),
|
||||
|
@ -764,7 +683,7 @@ impl Resolver {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn token_gidattr(&self, token: &UnixGroupToken) -> String {
|
||||
fn token_gidattr(&self, token: &GroupToken) -> String {
|
||||
match self.gid_attr_map {
|
||||
UidAttr::Spn => token.spn.as_str(),
|
||||
UidAttr::Name => token.name.as_str(),
|
||||
|
@ -776,7 +695,7 @@ impl Resolver {
|
|||
let l = self.get_cached_grouptokens().await?;
|
||||
let mut r: Vec<_> = Vec::with_capacity(l.len());
|
||||
for tok in l.into_iter() {
|
||||
let members = self.get_groupmembers(&tok.uuid).await;
|
||||
let members = self.get_groupmembers(tok.uuid).await;
|
||||
r.push(NssGroup {
|
||||
name: self.token_gidattr(&tok),
|
||||
gid: tok.gidnumber,
|
||||
|
@ -791,7 +710,7 @@ impl Resolver {
|
|||
// Get members set.
|
||||
match token {
|
||||
Some(tok) => {
|
||||
let members = self.get_groupmembers(&tok.uuid).await;
|
||||
let members = self.get_groupmembers(tok.uuid).await;
|
||||
Ok(Some(NssGroup {
|
||||
name: self.token_gidattr(&tok),
|
||||
gid: tok.gidnumber,
|
||||
|
@ -812,28 +731,22 @@ impl Resolver {
|
|||
|
||||
async fn online_account_authenticate(
|
||||
&self,
|
||||
token: &Option<UnixUserToken>,
|
||||
account_id: &str,
|
||||
token: &Option<UserToken>,
|
||||
account_id: &Id,
|
||||
cred: &str,
|
||||
) -> Result<Option<bool>, ()> {
|
||||
debug!("Attempt online password check");
|
||||
// We are online, attempt the pw to the server.
|
||||
match self
|
||||
.client
|
||||
.read()
|
||||
.await
|
||||
.idm_account_unix_cred_verify(account_id, cred)
|
||||
.await
|
||||
{
|
||||
match self.client.unix_user_authenticate(account_id, cred).await {
|
||||
Ok(Some(mut n_tok)) => {
|
||||
if self.check_nxset(&n_tok.name, n_tok.gidnumber).await {
|
||||
// Refuse to release the token, it's in the denied set.
|
||||
self.delete_cache_usertoken(&n_tok.uuid).await?;
|
||||
self.delete_cache_usertoken(n_tok.uuid).await?;
|
||||
Ok(None)
|
||||
} else {
|
||||
debug!("online password check success.");
|
||||
self.set_cache_usertoken(&mut n_tok).await?;
|
||||
self.set_cache_userpassword(&n_tok.uuid, cred).await?;
|
||||
self.set_cache_userpassword(n_tok.uuid, cred).await?;
|
||||
Ok(Some(true))
|
||||
}
|
||||
}
|
||||
|
@ -842,80 +755,46 @@ impl Resolver {
|
|||
// PW failed the check.
|
||||
Ok(Some(false))
|
||||
}
|
||||
Err(e) => {
|
||||
match e {
|
||||
ClientError::Transport(er) => {
|
||||
error!("transport error, moving to offline -> {:?}", er);
|
||||
Err(IdpError::Transport) => {
|
||||
error!("transport error, moving to offline");
|
||||
// Something went wrong, mark offline.
|
||||
let time = SystemTime::now().add(Duration::from_secs(15));
|
||||
self.set_cachestate(CacheState::OfflineNextCheck(time))
|
||||
.await;
|
||||
match token.as_ref() {
|
||||
Some(t) => self.check_cache_userpassword(&t.uuid, cred).await.map(Some),
|
||||
Some(t) => self.check_cache_userpassword(t.uuid, cred).await.map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid) => {
|
||||
match reason {
|
||||
Some(OperationError::NotAuthenticated) =>
|
||||
warn!("session not authenticated - attempting reauthentication - eventid {}", opid),
|
||||
Some(OperationError::SessionExpired) =>
|
||||
warn!("session expired - attempting reauthentication - eventid {}", opid),
|
||||
e =>
|
||||
error!(
|
||||
"authentication error {:?}, moving to offline - eventid {}", e, opid
|
||||
),
|
||||
};
|
||||
|
||||
Err(IdpError::ProviderUnauthorised) => {
|
||||
// Something went wrong, mark offline to force a re-auth ASAP.
|
||||
let time = SystemTime::now().sub(Duration::from_secs(1));
|
||||
self.set_cachestate(CacheState::OfflineNextCheck(time))
|
||||
.await;
|
||||
match token.as_ref() {
|
||||
Some(t) => self.check_cache_userpassword(&t.uuid, cred).await.map(Some),
|
||||
Some(t) => self.check_cache_userpassword(t.uuid, cred).await.map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
)
|
||||
| ClientError::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
Some(OperationError::NoMatchingEntries),
|
||||
opid,
|
||||
)
|
||||
| ClientError::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Some(OperationError::InvalidAccountState(_)),
|
||||
opid,
|
||||
) => {
|
||||
error!(
|
||||
"unknown account or is not a valid posix account - eventid {}",
|
||||
opid
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
er => {
|
||||
error!("client error -> {:?}", er);
|
||||
Err(IdpError::NotFound) => Ok(None),
|
||||
Err(IdpError::BadRequest) => {
|
||||
// Some other unknown processing error?
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn offline_account_authenticate(
|
||||
&self,
|
||||
token: &Option<UnixUserToken>,
|
||||
token: &Option<UserToken>,
|
||||
cred: &str,
|
||||
) -> Result<Option<bool>, ()> {
|
||||
debug!("Attempt offline password check");
|
||||
match token.as_ref() {
|
||||
Some(t) => {
|
||||
if t.valid {
|
||||
self.check_cache_userpassword(&t.uuid, cred).await.map(Some)
|
||||
self.check_cache_userpassword(t.uuid, cred).await.map(Some)
|
||||
} else {
|
||||
Ok(Some(false))
|
||||
}
|
||||
|
@ -942,7 +821,7 @@ impl Resolver {
|
|||
let user_set: BTreeSet<_> = tok
|
||||
.groups
|
||||
.iter()
|
||||
.flat_map(|g| [g.name.clone(), g.spn.clone(), g.uuid.clone()])
|
||||
.flat_map(|g| [g.name.clone(), g.uuid.hyphenated().to_string()])
|
||||
.collect();
|
||||
|
||||
debug!(
|
||||
|
@ -963,22 +842,18 @@ impl Resolver {
|
|||
account_id: &str,
|
||||
cred: &str,
|
||||
) -> Result<Option<bool>, ()> {
|
||||
let id = Id::Name(account_id.to_string());
|
||||
|
||||
let state = self.get_cachestate().await;
|
||||
let (_expired, token) = self
|
||||
.get_cached_usertoken(&Id::Name(account_id.to_string()))
|
||||
.await?;
|
||||
let (_expired, token) = self.get_cached_usertoken(&id).await?;
|
||||
|
||||
match state {
|
||||
CacheState::Online => {
|
||||
self.online_account_authenticate(&token, account_id, cred)
|
||||
.await
|
||||
}
|
||||
CacheState::Online => self.online_account_authenticate(&token, &id, cred).await,
|
||||
CacheState::OfflineNextCheck(_time) => {
|
||||
// Always attempt to go online to attempt the authentication.
|
||||
if self.test_connection().await {
|
||||
// Brought ourselves online, lets check.
|
||||
self.online_account_authenticate(&token, account_id, cred)
|
||||
.await
|
||||
self.online_account_authenticate(&token, &id, cred).await
|
||||
} else {
|
||||
// We are offline, check from the cache if possible.
|
||||
self.offline_account_authenticate(&token, cred).await
|
||||
|
@ -1014,8 +889,8 @@ impl Resolver {
|
|||
false
|
||||
}
|
||||
CacheState::OfflineNextCheck(_time) => {
|
||||
match self.client.write().await.auth_anonymous().await {
|
||||
Ok(_uat) => {
|
||||
match self.client.provider_authenticate().await {
|
||||
Ok(()) => {
|
||||
debug!("OfflineNextCheck -> authenticated");
|
||||
self.set_cachestate(CacheState::Online).await;
|
||||
true
|
||||
|
|
|
@ -65,7 +65,7 @@ async fn main() -> ExitCode {
|
|||
}
|
||||
let req = ClientRequest::SshKey(opt.account_id);
|
||||
|
||||
match call_daemon(cfg.sock_path.as_str(), req).await {
|
||||
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| {
|
||||
println!("{}", k);
|
||||
|
|
|
@ -17,7 +17,6 @@ use std::process::ExitCode;
|
|||
|
||||
use clap::Parser;
|
||||
use kanidm_unix_common::client::call_daemon;
|
||||
use kanidm_unix_common::client_sync::call_daemon_blocking;
|
||||
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
|
||||
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
|
||||
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse};
|
||||
|
@ -70,7 +69,7 @@ async fn main() -> ExitCode {
|
|||
let req = ClientRequest::PamAuthenticate(account_id.clone(), password);
|
||||
let sereq = ClientRequest::PamAccountAllowed(account_id);
|
||||
|
||||
match call_daemon(cfg.sock_path.as_str(), req).await {
|
||||
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await {
|
||||
Ok(r) => match r {
|
||||
ClientResponse::PamStatus(Some(true)) => {
|
||||
println!("auth success!");
|
||||
|
@ -91,7 +90,7 @@ async fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
match call_daemon(cfg.sock_path.as_str(), sereq).await {
|
||||
match call_daemon(cfg.sock_path.as_str(), sereq, cfg.unix_sock_timeout).await {
|
||||
Ok(r) => match r {
|
||||
ClientResponse::PamStatus(Some(true)) => {
|
||||
println!("account success!");
|
||||
|
@ -133,7 +132,7 @@ async fn main() -> ExitCode {
|
|||
|
||||
let req = ClientRequest::ClearCache;
|
||||
|
||||
match call_daemon(cfg.sock_path.as_str(), req).await {
|
||||
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await {
|
||||
Ok(r) => match r {
|
||||
ClientResponse::Ok => info!("success"),
|
||||
_ => {
|
||||
|
@ -162,7 +161,7 @@ async fn main() -> ExitCode {
|
|||
|
||||
let req = ClientRequest::InvalidateCache;
|
||||
|
||||
match call_daemon(cfg.sock_path.as_str(), req).await {
|
||||
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await {
|
||||
Ok(r) => match r {
|
||||
ClientResponse::Ok => info!("success"),
|
||||
_ => {
|
||||
|
@ -198,7 +197,7 @@ async fn main() -> ExitCode {
|
|||
cfg.sock_path
|
||||
)
|
||||
} else {
|
||||
match call_daemon_blocking(cfg.sock_path.as_str(), &req, cfg.unix_sock_timeout) {
|
||||
match call_daemon(cfg.sock_path.as_str(), req, cfg.unix_sock_timeout).await {
|
||||
Ok(r) => match r {
|
||||
ClientResponse::Ok => println!("working!"),
|
||||
_ => {
|
||||
|
|
|
@ -11,7 +11,9 @@ use kanidm_unix_common::constants::{
|
|||
DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP,
|
||||
};
|
||||
use kanidm_unix_common::db::Db;
|
||||
use kanidm_unix_common::resolver::{Id, Resolver};
|
||||
use kanidm_unix_common::idprovider::interface::Id;
|
||||
use kanidm_unix_common::idprovider::kanidm::KanidmProvider;
|
||||
use kanidm_unix_common::resolver::Resolver;
|
||||
use kanidm_unix_common::unix_config::TpmPolicy;
|
||||
use kanidmd_core::config::{Configuration, IntegrationTestConfig, ServerRole};
|
||||
use kanidmd_core::create_server_core;
|
||||
|
@ -42,7 +44,7 @@ where
|
|||
Box::new(move |n| Box::pin(f(n)))
|
||||
}
|
||||
|
||||
async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
|
||||
async fn setup_test(fix_fn: Fixture) -> (Resolver<KanidmProvider>, KanidmClient) {
|
||||
sketching::test_init();
|
||||
|
||||
let mut counter = 0;
|
||||
|
@ -100,6 +102,8 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
|
|||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let idprovider = KanidmProvider::new(rsclient);
|
||||
|
||||
let db = Db::new(
|
||||
"", // The sqlite db path, this is in memory.
|
||||
&TpmPolicy::default(),
|
||||
|
@ -108,8 +112,8 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
|
|||
|
||||
let cachelayer = Resolver::new(
|
||||
db,
|
||||
idprovider,
|
||||
300,
|
||||
rsclient,
|
||||
vec!["allowed_group".to_string()],
|
||||
DEFAULT_SHELL.to_string(),
|
||||
DEFAULT_HOME_PREFIX.to_string(),
|
||||
|
|
Loading…
Reference in a new issue