20230727 unix int modularity (#1907)

This commit is contained in:
Firstyear 2023-07-28 10:48:56 +10:00 committed by GitHub
parent 0293234d3c
commit 99b761c966
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 614 additions and 368 deletions

2
Cargo.lock generated
View file

@ -2674,7 +2674,6 @@ dependencies = [
"lru 0.8.1", "lru 0.8.1",
"notify-debouncer-full", "notify-debouncer-full",
"profiles", "profiles",
"reqwest",
"rpassword 7.2.0", "rpassword 7.2.0",
"rusqlite", "rusqlite",
"selinux", "selinux",
@ -2687,6 +2686,7 @@ dependencies = [
"tracing", "tracing",
"tss-esapi", "tss-esapi",
"users", "users",
"uuid",
"walkdir", "walkdir",
] ]

View file

@ -556,7 +556,7 @@ impl fmt::Display for RadiusAuthToken {
pub struct UnixGroupToken { pub struct UnixGroupToken {
pub name: String, pub name: String,
pub spn: String, pub spn: String,
pub uuid: String, pub uuid: Uuid,
pub gidnumber: u32, pub gidnumber: u32,
} }
@ -580,7 +580,7 @@ pub struct UnixUserToken {
pub spn: String, pub spn: String,
pub displayname: String, pub displayname: String,
pub gidnumber: u32, pub gidnumber: u32,
pub uuid: String, pub uuid: Uuid,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub shell: Option<String>, pub shell: Option<String>,
pub groups: Vec<UnixGroupToken>, pub groups: Vec<UnixGroupToken>,

View file

@ -150,7 +150,7 @@ impl UnixUserAccount {
spn: self.spn.clone(), spn: self.spn.clone(),
displayname: self.displayname.clone(), displayname: self.displayname.clone(),
gidnumber: self.gidnumber, gidnumber: self.gidnumber,
uuid: self.uuid.as_hyphenated().to_string(), uuid: self.uuid,
shell: self.shell.clone(), shell: self.shell.clone(),
groups, groups,
sshkeys: self.sshkeys.clone(), sshkeys: self.sshkeys.clone(),
@ -449,7 +449,7 @@ impl UnixGroup {
Ok(UnixGroupToken { Ok(UnixGroupToken {
name: self.name.clone(), name: self.name.clone(),
spn: self.spn.clone(), spn: self.spn.clone(),
uuid: self.uuid.as_hyphenated().to_string(), uuid: self.uuid,
gidnumber: self.gidnumber, gidnumber: self.gidnumber,
}) })
} }

View file

@ -563,7 +563,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
.unwrap(); .unwrap();
// get the account by uuid // get the account by uuid
let r3 = rsclient let r3 = rsclient
.idm_account_unix_token_get(r.uuid.as_str()) .idm_account_unix_token_get(&r.uuid.hyphenated().to_string())
.await .await
.unwrap(); .unwrap();
@ -590,7 +590,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
.unwrap(); .unwrap();
// get the group by uuid // get the group by uuid
let r3 = rsclient let r3 = rsclient
.idm_group_unix_token_get(r.uuid.as_str()) .idm_group_unix_token_get(&r.uuid.hyphenated().to_string())
.await .await
.unwrap(); .unwrap();

View file

@ -69,7 +69,7 @@ tokio = { workspace = true, features = ["rt", "fs", "macros", "sync", "time", "n
tokio-util = { workspace = true, features = ["codec"] } tokio-util = { workspace = true, features = ["codec"] }
tracing = { workspace = true } tracing = { workspace = true }
tss-esapi = { workspace = true, optional = true } tss-esapi = { workspace = true, optional = true }
reqwest = { workspace = true, default-features = false } uuid = { workspace = true }
walkdir = { workspace = true } walkdir = { workspace = true }
[target.'cfg(not(target_family = "windows"))'.dependencies] [target.'cfg(not(target_family = "windows"))'.dependencies]

View file

@ -5,6 +5,7 @@ use bytes::{BufMut, BytesMut};
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use tokio::net::UnixStream; use tokio::net::UnixStream;
// use tokio::runtime::Builder; // use tokio::runtime::Builder;
use tokio::time::{self, Duration};
use tokio_util::codec::Framed; use tokio_util::codec::Framed;
use tokio_util::codec::{Decoder, Encoder}; use tokio_util::codec::{Decoder, Encoder};
@ -48,8 +49,10 @@ impl ClientCodec {
} }
} }
/// Makes a call to kanidm_unixd via a unix socket at `path` async fn call_daemon_inner(
pub async fn call_daemon(path: &str, req: ClientRequest) -> Result<ClientResponse, Box<dyn Error>> { path: &str,
req: ClientRequest,
) -> Result<ClientResponse, Box<dyn Error>> {
trace!(?path, ?req); trace!(?path, ?req);
let stream = UnixStream::connect(path).await?; let stream = UnixStream::connect(path).await?;
trace!("connected"); 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
}
}
}

View file

@ -27,6 +27,7 @@ use kanidm_client::KanidmClientBuilder;
use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH; use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH; use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::db::Db; use kanidm_unix_common::db::Db;
use kanidm_unix_common::idprovider::kanidm::KanidmProvider;
use kanidm_unix_common::resolver::Resolver; use kanidm_unix_common::resolver::Resolver;
use kanidm_unix_common::unix_config::KanidmUnixdConfig; use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_passwd::{parse_etc_group, parse_etc_passwd}; 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( async fn handle_client(
sock: UnixStream, sock: UnixStream,
cachelayer: Arc<Resolver>, cachelayer: Arc<Resolver<KanidmProvider>>,
task_channel_tx: &Sender<AsyncTaskRequest>, task_channel_tx: &Sender<AsyncTaskRequest>,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
debug!("Accepted connection"); debug!("Accepted connection");
@ -372,7 +373,9 @@ async fn handle_client(
Ok(()) 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 file = File::open("/etc/passwd").await?;
let mut contents = vec![]; let mut contents = vec![];
file.read_to_end(&mut contents).await?; 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) { let db = match Db::new(cfg.db_path.as_str(), &cfg.tpm_policy) {
Ok(db) => db, Ok(db) => db,
Err(_e) => { Err(_e) => {
@ -671,8 +676,8 @@ async fn main() -> ExitCode {
let cl_inner = match Resolver::new( let cl_inner = match Resolver::new(
db, db,
idprovider,
cfg.cache_timeout, cfg.cache_timeout,
rsclient,
cfg.pam_allowed_login_groups.clone(), cfg.pam_allowed_login_groups.clone(),
cfg.default_shell.clone(), cfg.default_shell.clone(),
cfg.home_prefix.clone(), cfg.home_prefix.clone(),

View file

@ -2,17 +2,16 @@ use std::convert::TryFrom;
use std::fmt; use std::fmt;
use std::time::Duration; use std::time::Duration;
use crate::idprovider::interface::{GroupToken, Id, UserToken};
use crate::unix_config::TpmPolicy; use crate::unix_config::TpmPolicy;
use async_trait::async_trait; use async_trait::async_trait;
use kanidm_lib_crypto::CryptoPolicy; use kanidm_lib_crypto::CryptoPolicy;
use kanidm_lib_crypto::DbPasswordV1; use kanidm_lib_crypto::DbPasswordV1;
use kanidm_lib_crypto::Password; use kanidm_lib_crypto::Password;
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
use libc::umask; use libc::umask;
use rusqlite::Connection; use rusqlite::Connection;
use tokio::sync::{Mutex, MutexGuard}; use tokio::sync::{Mutex, MutexGuard};
use uuid::Uuid;
use crate::resolver::Id;
#[async_trait] #[async_trait]
pub trait Cache { pub trait Cache {
@ -43,27 +42,27 @@ pub trait CacheTxn {
fn clear(&self) -> Result<(), CacheError>; 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 { pub struct Db {
@ -347,7 +346,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
Ok(()) 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 { let data = match account_id {
Id::Name(n) => self.get_account_data_name(n.as_str()), Id::Name(n) => self.get_account_data_name(n.as_str()),
Id::Gid(g) => self.get_account_data_gid(*g), 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 let mut stmt = self
.conn .conn
.prepare("SELECT token FROM account_t") .prepare("SELECT token FROM account_t")
@ -410,7 +409,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
.collect()) .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| { let data = serde_json::to_vec(account).map_err(|e| {
error!("update_account json error -> {:?}", e); error!("update_account json error -> {:?}", e);
CacheError::SerdeJson 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 // 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 // if present, and upsert MUST match the exact conflicting column, so that means we have
// to manually manage the update or insert :( :( // to manually manage the update or insert :( :(
let account_uuid = account.uuid.as_hyphenated().to_string();
// Find anything conflicting and purge it. // 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)", self.conn.execute("DELETE FROM account_t WHERE NOT uuid = :uuid AND (name = :name OR spn = :spn OR gidnumber = :gidnumber)",
named_params!{ named_params!{
":uuid": &account.uuid, ":uuid": &account_uuid,
":name": &account.name, ":name": &account.name,
":spn": &account.spn, ":spn": &account.spn,
":gidnumber": &account.gidnumber, ":gidnumber": &account.gidnumber,
@ -441,7 +441,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
let updated = self.conn.execute( let updated = self.conn.execute(
"UPDATE account_t SET name=:name, spn=:spn, gidnumber=:gidnumber, token=:token, expiry=:expiry WHERE uuid = :uuid", "UPDATE account_t SET name=:name, spn=:spn, gidnumber=:gidnumber, token=:token, expiry=:expiry WHERE uuid = :uuid",
named_params!{ named_params!{
":uuid": &account.uuid, ":uuid": &account_uuid,
":name": &account.name, ":name": &account.name,
":spn": &account.spn, ":spn": &account.spn,
":gidnumber": &account.gidnumber, ":gidnumber": &account.gidnumber,
@ -461,7 +461,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
})?; })?;
stmt.execute(named_params! { stmt.execute(named_params! {
":uuid": &account.uuid, ":uuid": &account_uuid,
":name": &account.name, ":name": &account.name,
":spn": &account.spn, ":spn": &account.spn,
":gidnumber": &account.gidnumber, ":gidnumber": &account.gidnumber,
@ -482,7 +482,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
.prepare("DELETE FROM memberof_t WHERE a_uuid = :a_uuid") .prepare("DELETE FROM memberof_t WHERE a_uuid = :a_uuid")
.map_err(|e| self.sqlite_error("prepare", &e))?; .map_err(|e| self.sqlite_error("prepare", &e))?;
stmt.execute([&account.uuid]) stmt.execute([&account_uuid])
.map(|r| { .map(|r| {
debug!("delete memberships -> {:?}", r); debug!("delete memberships -> {:?}", r);
}) })
@ -495,8 +495,8 @@ impl<'a> CacheTxn for DbTxn<'a> {
// Now for each group, add the relation. // Now for each group, add the relation.
account.groups.iter().try_for_each(|g| { account.groups.iter().try_for_each(|g| {
stmt.execute(named_params! { stmt.execute(named_params! {
":a_uuid": &account.uuid, ":a_uuid": &account_uuid,
":g_uuid": &g.uuid, ":g_uuid": &g.uuid.as_hyphenated().to_string(),
}) })
.map(|r| { .map(|r| {
debug!("insert membership -> {:?}", 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 self.conn
.execute( .execute(
"DELETE FROM memberof_t WHERE a_uuid = :a_uuid", "DELETE FROM memberof_t WHERE a_uuid = :a_uuid",
params![a_uuid], params![&account_uuid],
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| self.sqlite_error("account_t memberof_t cascade delete", &e))?; .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 self.conn
.execute( .execute(
"DELETE FROM account_t WHERE uuid = :a_uuid", "DELETE FROM account_t WHERE uuid = :a_uuid",
params![a_uuid], params![&account_uuid],
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| self.sqlite_error("account_t delete", &e)) .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)] #[allow(unused_variables)]
let pw = if let Some(tcti_str) = self.require_tpm { let pw = if let Some(tcti_str) = self.require_tpm {
// Do nothing. // Do nothing.
@ -552,7 +554,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
.execute( .execute(
"UPDATE account_t SET password = :data WHERE uuid = :a_uuid", "UPDATE account_t SET password = :data WHERE uuid = :a_uuid",
named_params! { named_params! {
":a_uuid": &a_uuid, ":a_uuid": &a_uuid.as_hyphenated().to_string(),
":data": &data, ":data": &data,
}, },
) )
@ -560,7 +562,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
.map(|_| ()) .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"))] #[cfg(not(feature = "tpm"))]
if self.require_tpm.is_some() { if self.require_tpm.is_some() {
return Ok(false); return Ok(false);
@ -573,7 +575,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
// Makes tuple (token, expiry) // Makes tuple (token, expiry)
let data_iter = stmt 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))?; .map_err(|e| self.sqlite_error("query_map", &e))?;
let data: Result<Vec<Vec<u8>>, _> = data_iter let data: Result<Vec<Vec<u8>>, _> = data_iter
.map(|v| v.map_err(|e| self.sqlite_error("map", &e))) .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 { let data = match grp_id {
Id::Name(n) => self.get_group_data_name(n.as_str()), Id::Name(n) => self.get_group_data_name(n.as_str()),
Id::Gid(g) => self.get_group_data_gid(*g), 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 let mut stmt = self
.conn .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") .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 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))?; .map_err(|e| self.sqlite_error("query_map", &e))?;
let data: Result<Vec<Vec<u8>>, _> = data_iter let data: Result<Vec<Vec<u8>>, _> = data_iter
.map(|v| v.map_err(|e| self.sqlite_error("map", &e))) .map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
@ -685,7 +687,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
.collect() .collect()
} }
fn get_groups(&self) -> Result<Vec<UnixGroupToken>, CacheError> { fn get_groups(&self) -> Result<Vec<GroupToken>, CacheError> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT token FROM group_t") .prepare("SELECT token FROM group_t")
@ -714,7 +716,7 @@ impl<'a> CacheTxn for DbTxn<'a> {
.collect()) .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| { let data = serde_json::to_vec(grp).map_err(|e| {
error!("json error -> {:?}", e); error!("json error -> {:?}", e);
CacheError::SerdeJson CacheError::SerdeJson
@ -730,8 +732,9 @@ impl<'a> CacheTxn for DbTxn<'a> {
self.sqlite_error("prepare", &e) 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! { stmt.execute(named_params! {
":uuid": &grp.uuid, ":uuid": &grp.uuid.as_hyphenated().to_string(),
":name": &grp.name, ":name": &grp.name,
":spn": &grp.spn, ":spn": &grp.spn,
":gidnumber": &grp.gidnumber, ":gidnumber": &grp.gidnumber,
@ -744,13 +747,17 @@ impl<'a> CacheTxn for DbTxn<'a> {
.map_err(|e| self.sqlite_error("execute", &e)) .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 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(|_| ())
.map_err(|e| self.sqlite_error("group_t memberof_t cascade delete", &e))?; .map_err(|e| self.sqlite_error("group_t memberof_t cascade delete", &e))?;
self.conn 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(|_| ())
.map_err(|e| self.sqlite_error("group_t delete", &e)) .map_err(|e| self.sqlite_error("group_t delete", &e))
} }
@ -1129,11 +1136,9 @@ pub(crate) mod tpm {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
// use std::assert_matches::assert_matches; // use std::assert_matches::assert_matches;
use super::{Cache, CacheTxn, Db}; use super::{Cache, CacheTxn, Db};
use crate::resolver::Id; use crate::idprovider::interface::{GroupToken, Id, UserToken};
use crate::unix_config::TpmPolicy; use crate::unix_config::TpmPolicy;
const TESTACCOUNT1_PASSWORD_A: &str = "password a for account1 test"; const TESTACCOUNT1_PASSWORD_A: &str = "password a for account1 test";
@ -1146,12 +1151,12 @@ mod tests {
let dbtxn = db.write().await; let dbtxn = db.write().await;
assert!(dbtxn.migrate().is_ok()); assert!(dbtxn.migrate().is_ok());
let mut ut1 = UnixUserToken { let mut ut1 = UserToken {
name: "testuser".to_string(), name: "testuser".to_string(),
spn: "testuser@example.com".to_string(), spn: "testuser@example.com".to_string(),
displayname: "Test User".to_string(), displayname: "Test User".to_string(),
gidnumber: 2000, gidnumber: 2000,
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(), uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
shell: None, shell: None,
groups: Vec::new(), groups: Vec::new(),
sshkeys: vec!["key-a".to_string()], sshkeys: vec!["key-a".to_string()],
@ -1230,11 +1235,11 @@ mod tests {
let dbtxn = db.write().await; let dbtxn = db.write().await;
assert!(dbtxn.migrate().is_ok()); assert!(dbtxn.migrate().is_ok());
let mut gt1 = UnixGroupToken { let mut gt1 = GroupToken {
name: "testgroup".to_string(), name: "testgroup".to_string(),
spn: "testgroup@example.com".to_string(), spn: "testgroup@example.com".to_string(),
gidnumber: 2000, 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()); let id_name = Id::Name("testgroup".to_string());
@ -1305,26 +1310,26 @@ mod tests {
let dbtxn = db.write().await; let dbtxn = db.write().await;
assert!(dbtxn.migrate().is_ok()); assert!(dbtxn.migrate().is_ok());
let gt1 = UnixGroupToken { let gt1 = GroupToken {
name: "testuser".to_string(), name: "testuser".to_string(),
spn: "testuser@example.com".to_string(), spn: "testuser@example.com".to_string(),
gidnumber: 2000, 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(), name: "testgroup".to_string(),
spn: "testgroup@example.com".to_string(), spn: "testgroup@example.com".to_string(),
gidnumber: 2001, 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(), name: "testuser".to_string(),
spn: "testuser@example.com".to_string(), spn: "testuser@example.com".to_string(),
displayname: "Test User".to_string(), displayname: "Test User".to_string(),
gidnumber: 2000, gidnumber: 2000,
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(), uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
shell: None, shell: None,
groups: vec![gt1.clone(), gt2], groups: vec![gt1.clone(), gt2],
sshkeys: vec!["key-a".to_string()], sshkeys: vec!["key-a".to_string()],
@ -1341,10 +1346,10 @@ mod tests {
// Now, get the memberships of the two groups. // Now, get the memberships of the two groups.
let m1 = dbtxn let m1 = dbtxn
.get_group_members("0302b99c-f0f6-41ab-9492-852692b0fd16") .get_group_members(uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"))
.unwrap(); .unwrap();
let m2 = dbtxn let m2 = dbtxn
.get_group_members("b500be97-8552-42a5-aca0-668bc5625705") .get_group_members(uuid::uuid!("b500be97-8552-42a5-aca0-668bc5625705"))
.unwrap(); .unwrap();
assert!(m1[0].name == "testuser"); assert!(m1[0].name == "testuser");
assert!(m2[0].name == "testuser"); assert!(m2[0].name == "testuser");
@ -1355,10 +1360,10 @@ mod tests {
// Check that the memberships have updated correctly. // Check that the memberships have updated correctly.
let m1 = dbtxn let m1 = dbtxn
.get_group_members("0302b99c-f0f6-41ab-9492-852692b0fd16") .get_group_members(uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"))
.unwrap(); .unwrap();
let m2 = dbtxn let m2 = dbtxn
.get_group_members("b500be97-8552-42a5-aca0-668bc5625705") .get_group_members(uuid::uuid!("b500be97-8552-42a5-aca0-668bc5625705"))
.unwrap(); .unwrap();
assert!(m1[0].name == "testuser"); assert!(m1[0].name == "testuser");
assert!(m2.is_empty()); assert!(m2.is_empty());
@ -1381,13 +1386,13 @@ mod tests {
let dbtxn = db.write().await; let dbtxn = db.write().await;
assert!(dbtxn.migrate().is_ok()); assert!(dbtxn.migrate().is_ok());
let uuid1 = "0302b99c-f0f6-41ab-9492-852692b0fd16"; let uuid1 = uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16");
let mut ut1 = UnixUserToken { let mut ut1 = UserToken {
name: "testuser".to_string(), name: "testuser".to_string(),
spn: "testuser@example.com".to_string(), spn: "testuser@example.com".to_string(),
displayname: "Test User".to_string(), displayname: "Test User".to_string(),
gidnumber: 2000, gidnumber: 2000,
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(), uuid: uuid1,
shell: None, shell: None,
groups: Vec::new(), groups: Vec::new(),
sshkeys: vec!["key-a".to_string()], sshkeys: vec!["key-a".to_string()],
@ -1451,18 +1456,18 @@ mod tests {
let dbtxn = db.write().await; let dbtxn = db.write().await;
assert!(dbtxn.migrate().is_ok()); assert!(dbtxn.migrate().is_ok());
let mut gt1 = UnixGroupToken { let mut gt1 = GroupToken {
name: "testgroup".to_string(), name: "testgroup".to_string(),
spn: "testgroup@example.com".to_string(), spn: "testgroup@example.com".to_string(),
gidnumber: 2000, 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(), name: "testgroup".to_string(),
spn: "testgroup@example.com".to_string(), spn: "testgroup@example.com".to_string(),
gidnumber: 2001, 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()); let id_name = Id::Name("testgroup".to_string());
@ -1475,7 +1480,7 @@ mod tests {
// test adding a group // test adding a group
dbtxn.update_group(&gt1, 0).unwrap(); dbtxn.update_group(&gt1, 0).unwrap();
let r0 = dbtxn.get_group(&id_name).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. // Do the "rename" of gt1 which is what would allow gt2 to be valid.
gt1.name = "testgroup2".to_string(); gt1.name = "testgroup2".to_string();
@ -1483,7 +1488,7 @@ mod tests {
// Now, add gt2 which dups on gt1 name/spn. // Now, add gt2 which dups on gt1 name/spn.
dbtxn.update_group(&gt2, 0).unwrap(); dbtxn.update_group(&gt2, 0).unwrap();
let r2 = dbtxn.get_group(&id_name).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(); let r3 = dbtxn.get_group(&id_name2).unwrap();
assert!(r3.is_none()); assert!(r3.is_none());
@ -1492,9 +1497,9 @@ mod tests {
// Both now coexist // Both now coexist
let r4 = dbtxn.get_group(&id_name).unwrap(); 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(); 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()); assert!(dbtxn.commit().is_ok());
} }
@ -1506,24 +1511,24 @@ mod tests {
let dbtxn = db.write().await; let dbtxn = db.write().await;
assert!(dbtxn.migrate().is_ok()); assert!(dbtxn.migrate().is_ok());
let mut ut1 = UnixUserToken { let mut ut1 = UserToken {
name: "testuser".to_string(), name: "testuser".to_string(),
spn: "testuser@example.com".to_string(), spn: "testuser@example.com".to_string(),
displayname: "Test User".to_string(), displayname: "Test User".to_string(),
gidnumber: 2000, gidnumber: 2000,
uuid: "0302b99c-f0f6-41ab-9492-852692b0fd16".to_string(), uuid: uuid::uuid!("0302b99c-f0f6-41ab-9492-852692b0fd16"),
shell: None, shell: None,
groups: Vec::new(), groups: Vec::new(),
sshkeys: vec!["key-a".to_string()], sshkeys: vec!["key-a".to_string()],
valid: true, valid: true,
}; };
let ut2 = UnixUserToken { let ut2 = UserToken {
name: "testuser".to_string(), name: "testuser".to_string(),
spn: "testuser@example.com".to_string(), spn: "testuser@example.com".to_string(),
displayname: "Test User".to_string(), displayname: "Test User".to_string(),
gidnumber: 2001, gidnumber: 2001,
uuid: "799123b2-3802-4b19-b0b8-1ffae2aa9a4b".to_string(), uuid: uuid::uuid!("799123b2-3802-4b19-b0b8-1ffae2aa9a4b"),
shell: None, shell: None,
groups: Vec::new(), groups: Vec::new(),
sshkeys: vec!["key-a".to_string()], sshkeys: vec!["key-a".to_string()],
@ -1540,7 +1545,7 @@ mod tests {
// test adding an account // test adding an account
dbtxn.update_account(&ut1, 0).unwrap(); dbtxn.update_account(&ut1, 0).unwrap();
let r0 = dbtxn.get_account(&id_name).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. // Do the "rename" of gt1 which is what would allow gt2 to be valid.
ut1.name = "testuser2".to_string(); ut1.name = "testuser2".to_string();
@ -1548,7 +1553,7 @@ mod tests {
// Now, add gt2 which dups on gt1 name/spn. // Now, add gt2 which dups on gt1 name/spn.
dbtxn.update_account(&ut2, 0).unwrap(); dbtxn.update_account(&ut2, 0).unwrap();
let r2 = dbtxn.get_account(&id_name).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(); let r3 = dbtxn.get_account(&id_name2).unwrap();
assert!(r3.is_none()); assert!(r3.is_none());
@ -1557,9 +1562,9 @@ mod tests {
// Both now coexist // Both now coexist
let r4 = dbtxn.get_account(&id_name).unwrap(); 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(); 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()); assert!(dbtxn.commit().is_ok());
} }

View 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>;
}

View 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)
}
}
}
}

View file

@ -0,0 +1,2 @@
pub mod interface;
pub mod kanidm;

View file

@ -26,6 +26,8 @@ pub mod constants;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub mod db; pub mod db;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub mod idprovider;
#[cfg(target_family = "unix")]
pub mod resolver; pub mod resolver;
#[cfg(all(target_family = "unix", feature = "selinux"))] #[cfg(all(target_family = "unix", feature = "selinux"))]
pub mod selinux_util; pub mod selinux_util;

View file

@ -1,3 +1,4 @@
// use async_trait::async_trait;
use hashbrown::HashSet; use hashbrown::HashSet;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
@ -6,26 +7,18 @@ use std::path::Path;
use std::string::ToString; use std::string::ToString;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use kanidm_client::{ClientError, KanidmClient};
use kanidm_proto::v1::{OperationError, UnixGroupToken, UnixUserToken};
use lru::LruCache; use lru::LruCache;
use reqwest::StatusCode; use tokio::sync::Mutex;
use tokio::sync::{Mutex, RwLock}; use uuid::Uuid;
use crate::db::{Cache, CacheTxn, Db}; use crate::db::{Cache, CacheTxn, Db};
use crate::idprovider::interface::{GroupToken, Id, IdProvider, IdpError, UserToken};
use crate::unix_config::{HomeAttr, UidAttr}; use crate::unix_config::{HomeAttr, UidAttr};
use crate::unix_proto::{HomeDirectoryInfo, NssGroup, NssUser}; use crate::unix_proto::{HomeDirectoryInfo, NssGroup, NssUser};
// use crate::unix_passwd::{EtcUser, EtcGroup}; // use crate::unix_passwd::{EtcUser, EtcGroup};
const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(2048) }; const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(128) };
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Id {
Name(String),
Gid(u32),
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum CacheState { enum CacheState {
@ -35,9 +28,14 @@ enum CacheState {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Resolver { pub struct Resolver<I>
where
I: IdProvider,
{
// Generic / modular types.
db: Db, db: Db,
client: RwLock<KanidmClient>, client: I,
// Types to update still.
state: Mutex<CacheState>, state: Mutex<CacheState>,
pam_allow_groups: BTreeSet<String>, pam_allow_groups: BTreeSet<String>,
timeout_seconds: u64, 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)] #[allow(clippy::too_many_arguments)]
pub async fn new( pub async fn new(
db: Db, db: Db,
// need db path client: I,
// path: &str,
// tpm_policy: &TpmPolicy,
// cache timeout // cache timeout
timeout_seconds: u64, timeout_seconds: u64,
//
client: KanidmClient,
pam_allow_groups: Vec<String>, pam_allow_groups: Vec<String>,
default_shell: String, default_shell: String,
home_prefix: String, home_prefix: String,
@ -97,7 +93,7 @@ impl Resolver {
// being valid from "now". // being valid from "now".
Ok(Resolver { Ok(Resolver {
db, db,
client: RwLock::new(client), client,
state: Mutex::new(CacheState::OfflineNextCheck(SystemTime::now())), state: Mutex::new(CacheState::OfflineNextCheck(SystemTime::now())),
timeout_seconds, timeout_seconds,
pam_allow_groups: pam_allow_groups.into_iter().collect(), pam_allow_groups: pam_allow_groups.into_iter().collect(),
@ -150,12 +146,12 @@ impl Resolver {
.map_err(|_| ()) .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; let dbtxn = self.db.write().await;
dbtxn.get_accounts().map_err(|_| ()) 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; let dbtxn = self.db.write().await;
dbtxn.get_groups().map_err(|_| ()) 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())) nxset_txn.contains(&Id::Gid(idnumber)) || nxset_txn.contains(&Id::Name(name.to_string()))
} }
async fn get_cached_usertoken( async fn get_cached_usertoken(&self, account_id: &Id) -> Result<(bool, Option<UserToken>), ()> {
&self,
account_id: &Id,
) -> Result<(bool, Option<UnixUserToken>), ()> {
// Account_id could be: // Account_id could be:
// * gidnumber // * gidnumber
// * name // * name
@ -244,10 +237,7 @@ impl Resolver {
} // end match r } // end match r
} }
async fn get_cached_grouptoken( async fn get_cached_grouptoken(&self, grp_id: &Id) -> Result<(bool, Option<GroupToken>), ()> {
&self,
grp_id: &Id,
) -> Result<(bool, Option<UnixGroupToken>), ()> {
// grp_id could be: // grp_id could be:
// * gidnumber // * gidnumber
// * name // * 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 // Set an expiry
let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds); let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
let offset = ex_time let offset = ex_time
@ -350,7 +340,7 @@ impl Resolver {
.map_err(|_| ()) .map_err(|_| ())
} }
async fn set_cache_grouptoken(&self, token: &UnixGroupToken) -> Result<(), ()> { async fn set_cache_grouptoken(&self, token: &GroupToken) -> Result<(), ()> {
// Set an expiry // Set an expiry
let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds); let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
let offset = ex_time let offset = ex_time
@ -366,7 +356,7 @@ impl Resolver {
.map_err(|_| ()) .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; let dbtxn = self.db.write().await;
dbtxn dbtxn
.delete_account(a_uuid) .delete_account(a_uuid)
@ -374,7 +364,7 @@ impl Resolver {
.map_err(|_| ()) .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; let dbtxn = self.db.write().await;
dbtxn dbtxn
.delete_group(g_uuid) .delete_group(g_uuid)
@ -382,7 +372,7 @@ impl Resolver {
.map_err(|_| ()) .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; let dbtxn = self.db.write().await;
dbtxn dbtxn
.update_account_password(a_uuid, cred) .update_account_password(a_uuid, cred)
@ -390,7 +380,7 @@ impl Resolver {
.map_err(|_| ()) .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; let dbtxn = self.db.write().await;
dbtxn dbtxn
.check_account_password(a_uuid, cred) .check_account_password(a_uuid, cred)
@ -401,19 +391,13 @@ impl Resolver {
async fn refresh_usertoken( async fn refresh_usertoken(
&self, &self,
account_id: &Id, account_id: &Id,
token: Option<UnixUserToken>, token: Option<UserToken>,
) -> Result<Option<UnixUserToken>, ()> { ) -> Result<Option<UserToken>, ()> {
match self match self.client.unix_user_get(account_id).await {
.client
.read()
.await
.idm_account_unix_token_get(account_id.to_string().as_str())
.await
{
Ok(mut n_tok) => { Ok(mut n_tok) => {
if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { if self.check_nxset(&n_tok.name, n_tok.gidnumber).await {
// Refuse to release the token, it's in the denied set. // 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) Ok(None)
} else { } else {
// We have the token! // We have the token!
@ -421,85 +405,49 @@ impl Resolver {
Ok(Some(n_tok)) Ok(Some(n_tok))
} }
} }
Err(e) => { Err(IdpError::Transport) => {
match e { error!("transport error, moving to offline");
ClientError::Transport(er) => {
error!("transport error, moving to offline -> {:?}", er);
// Something went wrong, mark offline. // Something went wrong, mark offline.
let time = SystemTime::now().add(Duration::from_secs(15)); let time = SystemTime::now().add(Duration::from_secs(15));
self.set_cachestate(CacheState::OfflineNextCheck(time)) self.set_cachestate(CacheState::OfflineNextCheck(time))
.await; .await;
Ok(token) Ok(token)
} }
ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid) => { Err(IdpError::ProviderUnauthorised) => {
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
),
};
// Something went wrong, mark offline to force a re-auth ASAP. // Something went wrong, mark offline to force a re-auth ASAP.
let time = SystemTime::now().sub(Duration::from_secs(1)); let time = SystemTime::now().sub(Duration::from_secs(1));
self.set_cachestate(CacheState::OfflineNextCheck(time)) self.set_cachestate(CacheState::OfflineNextCheck(time))
.await; .await;
Ok(token) Ok(token)
} }
ClientError::Http( Err(IdpError::NotFound) => {
StatusCode::BAD_REQUEST, // We were able to contact the server but the entry has been removed, or
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
// is not longer a valid posix account. // 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 { if let Some(tok) = token {
self.delete_cache_usertoken(&tok.uuid).await?; self.delete_cache_usertoken(tok.uuid).await?;
}; };
// Cache the NX here. // Cache the NX here.
self.set_nxcache(account_id).await; self.set_nxcache(account_id).await;
Ok(None) Ok(None)
} }
er => { Err(IdpError::BadRequest) => {
error!("client error -> {:?}", er);
// Some other transient error, continue with the token. // Some other transient error, continue with the token.
Ok(token) Ok(token)
} }
} }
} }
}
}
async fn refresh_grouptoken( async fn refresh_grouptoken(
&self, &self,
grp_id: &Id, grp_id: &Id,
token: Option<UnixGroupToken>, token: Option<GroupToken>,
) -> Result<Option<UnixGroupToken>, ()> { ) -> Result<Option<GroupToken>, ()> {
match self match self.client.unix_group_get(grp_id).await {
.client
.read()
.await
.idm_group_unix_token_get(grp_id.to_string().as_str())
.await
{
Ok(n_tok) => { Ok(n_tok) => {
if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { if self.check_nxset(&n_tok.name, n_tok.gidnumber).await {
// Refuse to release the token, it's in the denied set. // 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) Ok(None)
} else { } else {
// We have the token! // We have the token!
@ -507,66 +455,37 @@ impl Resolver {
Ok(Some(n_tok)) Ok(Some(n_tok))
} }
} }
Err(e) => { Err(IdpError::Transport) => {
match e { error!("transport error, moving to offline");
ClientError::Transport(er) => {
error!("transport error, moving to offline -> {:?}", er);
// Something went wrong, mark offline. // Something went wrong, mark offline.
let time = SystemTime::now().add(Duration::from_secs(15)); let time = SystemTime::now().add(Duration::from_secs(15));
self.set_cachestate(CacheState::OfflineNextCheck(time)) self.set_cachestate(CacheState::OfflineNextCheck(time))
.await; .await;
Ok(token) Ok(token)
} }
ClientError::Http( Err(IdpError::ProviderUnauthorised) => {
StatusCode::UNAUTHORIZED,
Some(OperationError::NotAuthenticated),
opid,
) => {
error!(
"transport unauthenticated, moving to offline - eventid {}",
opid
);
// Something went wrong, mark offline. // Something went wrong, mark offline.
let time = SystemTime::now().add(Duration::from_secs(15)); let time = SystemTime::now().add(Duration::from_secs(15));
self.set_cachestate(CacheState::OfflineNextCheck(time)) self.set_cachestate(CacheState::OfflineNextCheck(time))
.await; .await;
Ok(token) Ok(token)
} }
ClientError::Http( Err(IdpError::NotFound) => {
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);
if let Some(tok) = token { if let Some(tok) = token {
self.delete_cache_grouptoken(&tok.uuid).await?; self.delete_cache_grouptoken(tok.uuid).await?;
}; };
// Cache the NX here. // Cache the NX here.
self.set_nxcache(grp_id).await; self.set_nxcache(grp_id).await;
Ok(None) Ok(None)
} }
er => { Err(IdpError::BadRequest) => {
error!("client error -> {:?}", er);
// Some other transient error, continue with the token. // Some other transient error, continue with the token.
Ok(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"); debug!("get_usertoken");
// get the item from the cache // get the item from the cache
let (expired, item) = self.get_cached_usertoken(&account_id).await.map_err(|e| { 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"); debug!("get_grouptoken");
let (expired, item) = self.get_cached_grouptoken(&grp_id).await.map_err(|e| { let (expired, item) = self.get_cached_grouptoken(&grp_id).await.map_err(|e| {
debug!("get_grouptoken error -> {:?}", 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; let dbtxn = self.db.write().await;
dbtxn dbtxn
.get_group_members(uuid) .get_group_members(g_uuid)
.unwrap_or_else(|_| Vec::new()) .unwrap_or_else(|_| Vec::new())
.into_iter() .into_iter()
.map(|ut| self.token_uidattr(&ut)) .map(|ut| self.token_uidattr(&ut))
@ -692,37 +611,37 @@ impl Resolver {
} }
#[inline(always)] #[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 { self.home_alias.map(|t| match t {
// If we have an alias. use it. // 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::Spn => token.spn.as_str().to_string(),
HomeAttr::Name => token.name.as_str().to_string(), HomeAttr::Name => token.name.as_str().to_string(),
}) })
} }
#[inline(always)] #[inline(always)]
fn token_homedirectory_attr(&self, token: &UnixUserToken) -> String { fn token_homedirectory_attr(&self, token: &UserToken) -> String {
match self.home_attr { 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::Spn => token.spn.as_str().to_string(),
HomeAttr::Name => token.name.as_str().to_string(), HomeAttr::Name => token.name.as_str().to_string(),
} }
} }
#[inline(always)] #[inline(always)]
fn token_homedirectory(&self, token: &UnixUserToken) -> String { fn token_homedirectory(&self, token: &UserToken) -> String {
self.token_homedirectory_alias(token) self.token_homedirectory_alias(token)
.unwrap_or_else(|| self.token_homedirectory_attr(token)) .unwrap_or_else(|| self.token_homedirectory_attr(token))
} }
#[inline(always)] #[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)) format!("{}{}", self.home_prefix, self.token_homedirectory(token))
} }
#[inline(always)] #[inline(always)]
fn token_uidattr(&self, token: &UnixUserToken) -> String { fn token_uidattr(&self, token: &UserToken) -> String {
match self.uid_attr_map { match self.uid_attr_map {
UidAttr::Spn => token.spn.as_str(), UidAttr::Spn => token.spn.as_str(),
UidAttr::Name => token.name.as_str(), UidAttr::Name => token.name.as_str(),
@ -764,7 +683,7 @@ impl Resolver {
} }
#[inline(always)] #[inline(always)]
fn token_gidattr(&self, token: &UnixGroupToken) -> String { fn token_gidattr(&self, token: &GroupToken) -> String {
match self.gid_attr_map { match self.gid_attr_map {
UidAttr::Spn => token.spn.as_str(), UidAttr::Spn => token.spn.as_str(),
UidAttr::Name => token.name.as_str(), UidAttr::Name => token.name.as_str(),
@ -776,7 +695,7 @@ impl Resolver {
let l = self.get_cached_grouptokens().await?; let l = self.get_cached_grouptokens().await?;
let mut r: Vec<_> = Vec::with_capacity(l.len()); let mut r: Vec<_> = Vec::with_capacity(l.len());
for tok in l.into_iter() { 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 { r.push(NssGroup {
name: self.token_gidattr(&tok), name: self.token_gidattr(&tok),
gid: tok.gidnumber, gid: tok.gidnumber,
@ -791,7 +710,7 @@ impl Resolver {
// Get members set. // Get members set.
match token { match token {
Some(tok) => { Some(tok) => {
let members = self.get_groupmembers(&tok.uuid).await; let members = self.get_groupmembers(tok.uuid).await;
Ok(Some(NssGroup { Ok(Some(NssGroup {
name: self.token_gidattr(&tok), name: self.token_gidattr(&tok),
gid: tok.gidnumber, gid: tok.gidnumber,
@ -812,28 +731,22 @@ impl Resolver {
async fn online_account_authenticate( async fn online_account_authenticate(
&self, &self,
token: &Option<UnixUserToken>, token: &Option<UserToken>,
account_id: &str, account_id: &Id,
cred: &str, cred: &str,
) -> Result<Option<bool>, ()> { ) -> Result<Option<bool>, ()> {
debug!("Attempt online password check"); debug!("Attempt online password check");
// We are online, attempt the pw to the server. // We are online, attempt the pw to the server.
match self match self.client.unix_user_authenticate(account_id, cred).await {
.client
.read()
.await
.idm_account_unix_cred_verify(account_id, cred)
.await
{
Ok(Some(mut n_tok)) => { Ok(Some(mut n_tok)) => {
if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { if self.check_nxset(&n_tok.name, n_tok.gidnumber).await {
// Refuse to release the token, it's in the denied set. // 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) Ok(None)
} else { } else {
debug!("online password check success."); debug!("online password check success.");
self.set_cache_usertoken(&mut n_tok).await?; 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)) Ok(Some(true))
} }
} }
@ -842,80 +755,46 @@ impl Resolver {
// PW failed the check. // PW failed the check.
Ok(Some(false)) Ok(Some(false))
} }
Err(e) => { Err(IdpError::Transport) => {
match e { error!("transport error, moving to offline");
ClientError::Transport(er) => {
error!("transport error, moving to offline -> {:?}", er);
// Something went wrong, mark offline. // Something went wrong, mark offline.
let time = SystemTime::now().add(Duration::from_secs(15)); let time = SystemTime::now().add(Duration::from_secs(15));
self.set_cachestate(CacheState::OfflineNextCheck(time)) self.set_cachestate(CacheState::OfflineNextCheck(time))
.await; .await;
match token.as_ref() { 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), None => Ok(None),
} }
} }
ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid) => {
match reason { Err(IdpError::ProviderUnauthorised) => {
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
),
};
// Something went wrong, mark offline to force a re-auth ASAP. // Something went wrong, mark offline to force a re-auth ASAP.
let time = SystemTime::now().sub(Duration::from_secs(1)); let time = SystemTime::now().sub(Duration::from_secs(1));
self.set_cachestate(CacheState::OfflineNextCheck(time)) self.set_cachestate(CacheState::OfflineNextCheck(time))
.await; .await;
match token.as_ref() { 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), None => Ok(None),
} }
} }
ClientError::Http( Err(IdpError::NotFound) => Ok(None),
StatusCode::BAD_REQUEST, Err(IdpError::BadRequest) => {
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);
// Some other unknown processing error? // Some other unknown processing error?
Err(()) Err(())
} }
} }
} }
}
}
async fn offline_account_authenticate( async fn offline_account_authenticate(
&self, &self,
token: &Option<UnixUserToken>, token: &Option<UserToken>,
cred: &str, cred: &str,
) -> Result<Option<bool>, ()> { ) -> Result<Option<bool>, ()> {
debug!("Attempt offline password check"); debug!("Attempt offline password check");
match token.as_ref() { match token.as_ref() {
Some(t) => { Some(t) => {
if t.valid { if t.valid {
self.check_cache_userpassword(&t.uuid, cred).await.map(Some) self.check_cache_userpassword(t.uuid, cred).await.map(Some)
} else { } else {
Ok(Some(false)) Ok(Some(false))
} }
@ -942,7 +821,7 @@ impl Resolver {
let user_set: BTreeSet<_> = tok let user_set: BTreeSet<_> = tok
.groups .groups
.iter() .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(); .collect();
debug!( debug!(
@ -963,22 +842,18 @@ impl Resolver {
account_id: &str, account_id: &str,
cred: &str, cred: &str,
) -> Result<Option<bool>, ()> { ) -> Result<Option<bool>, ()> {
let id = Id::Name(account_id.to_string());
let state = self.get_cachestate().await; let state = self.get_cachestate().await;
let (_expired, token) = self let (_expired, token) = self.get_cached_usertoken(&id).await?;
.get_cached_usertoken(&Id::Name(account_id.to_string()))
.await?;
match state { match state {
CacheState::Online => { CacheState::Online => self.online_account_authenticate(&token, &id, cred).await,
self.online_account_authenticate(&token, account_id, cred)
.await
}
CacheState::OfflineNextCheck(_time) => { CacheState::OfflineNextCheck(_time) => {
// Always attempt to go online to attempt the authentication. // Always attempt to go online to attempt the authentication.
if self.test_connection().await { if self.test_connection().await {
// Brought ourselves online, lets check. // Brought ourselves online, lets check.
self.online_account_authenticate(&token, account_id, cred) self.online_account_authenticate(&token, &id, cred).await
.await
} else { } else {
// We are offline, check from the cache if possible. // We are offline, check from the cache if possible.
self.offline_account_authenticate(&token, cred).await self.offline_account_authenticate(&token, cred).await
@ -1014,8 +889,8 @@ impl Resolver {
false false
} }
CacheState::OfflineNextCheck(_time) => { CacheState::OfflineNextCheck(_time) => {
match self.client.write().await.auth_anonymous().await { match self.client.provider_authenticate().await {
Ok(_uat) => { Ok(()) => {
debug!("OfflineNextCheck -> authenticated"); debug!("OfflineNextCheck -> authenticated");
self.set_cachestate(CacheState::Online).await; self.set_cachestate(CacheState::Online).await;
true true

View file

@ -65,7 +65,7 @@ async fn main() -> ExitCode {
} }
let req = ClientRequest::SshKey(opt.account_id); 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 { Ok(r) => match r {
ClientResponse::SshKeys(sk) => sk.iter().for_each(|k| { ClientResponse::SshKeys(sk) => sk.iter().for_each(|k| {
println!("{}", k); println!("{}", k);

View file

@ -17,7 +17,6 @@ use std::process::ExitCode;
use clap::Parser; use clap::Parser;
use kanidm_unix_common::client::call_daemon; 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::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::unix_config::KanidmUnixdConfig; use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse}; use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse};
@ -70,7 +69,7 @@ async fn main() -> ExitCode {
let req = ClientRequest::PamAuthenticate(account_id.clone(), password); let req = ClientRequest::PamAuthenticate(account_id.clone(), password);
let sereq = ClientRequest::PamAccountAllowed(account_id); 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 { Ok(r) => match r {
ClientResponse::PamStatus(Some(true)) => { ClientResponse::PamStatus(Some(true)) => {
println!("auth success!"); 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 { Ok(r) => match r {
ClientResponse::PamStatus(Some(true)) => { ClientResponse::PamStatus(Some(true)) => {
println!("account success!"); println!("account success!");
@ -133,7 +132,7 @@ async fn main() -> ExitCode {
let req = ClientRequest::ClearCache; 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 { Ok(r) => match r {
ClientResponse::Ok => info!("success"), ClientResponse::Ok => info!("success"),
_ => { _ => {
@ -162,7 +161,7 @@ async fn main() -> ExitCode {
let req = ClientRequest::InvalidateCache; 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 { Ok(r) => match r {
ClientResponse::Ok => info!("success"), ClientResponse::Ok => info!("success"),
_ => { _ => {
@ -198,7 +197,7 @@ async fn main() -> ExitCode {
cfg.sock_path cfg.sock_path
) )
} else { } 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 { Ok(r) => match r {
ClientResponse::Ok => println!("working!"), ClientResponse::Ok => println!("working!"),
_ => { _ => {

View file

@ -11,7 +11,9 @@ use kanidm_unix_common::constants::{
DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP, DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP,
}; };
use kanidm_unix_common::db::Db; 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 kanidm_unix_common::unix_config::TpmPolicy;
use kanidmd_core::config::{Configuration, IntegrationTestConfig, ServerRole}; use kanidmd_core::config::{Configuration, IntegrationTestConfig, ServerRole};
use kanidmd_core::create_server_core; use kanidmd_core::create_server_core;
@ -42,7 +44,7 @@ where
Box::new(move |n| Box::pin(f(n))) 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(); sketching::test_init();
let mut counter = 0; let mut counter = 0;
@ -100,6 +102,8 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
.build() .build()
.expect("Failed to build client"); .expect("Failed to build client");
let idprovider = KanidmProvider::new(rsclient);
let db = Db::new( let db = Db::new(
"", // The sqlite db path, this is in memory. "", // The sqlite db path, this is in memory.
&TpmPolicy::default(), &TpmPolicy::default(),
@ -108,8 +112,8 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
let cachelayer = Resolver::new( let cachelayer = Resolver::new(
db, db,
idprovider,
300, 300,
rsclient,
vec!["allowed_group".to_string()], vec!["allowed_group".to_string()],
DEFAULT_SHELL.to_string(), DEFAULT_SHELL.to_string(),
DEFAULT_HOME_PREFIX.to_string(), DEFAULT_HOME_PREFIX.to_string(),