20230720 unix int modular (#1881)

* Progress
* Db traits mostly sorted, need to get dyn working next
* updoot
This commit is contained in:
Firstyear 2023-07-24 17:10:37 +10:00 committed by GitHub
parent 8981478d76
commit 046a6fb298
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 372 additions and 331 deletions

118
Cargo.lock generated
View file

@ -104,9 +104,9 @@ dependencies = [
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.15" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]] [[package]]
name = "android-tzdata" name = "android-tzdata"
@ -180,9 +180,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.71" version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
[[package]] [[package]]
name = "anymap2" name = "anymap2"
@ -276,7 +276,7 @@ dependencies = [
"async-lock", "async-lock",
"async-task", "async-task",
"concurrent-queue", "concurrent-queue",
"fastrand", "fastrand 1.9.0",
"futures-lite", "futures-lite",
"slab", "slab",
] ]
@ -621,7 +621,7 @@ dependencies = [
"lazycell", "lazycell",
"log", "log",
"peeking_take_while", "peeking_take_while",
"prettyplease 0.2.10", "prettyplease 0.2.12",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
@ -722,7 +722,7 @@ dependencies = [
"async-lock", "async-lock",
"async-task", "async-task",
"atomic-waker", "atomic-waker",
"fastrand", "fastrand 1.9.0",
"futures-lite", "futures-lite",
"log", "log",
] ]
@ -1478,9 +1478,9 @@ checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272"
[[package]] [[package]]
name = "either" name = "either"
version = "1.8.1" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
@ -1609,6 +1609,12 @@ dependencies = [
"instant", "instant",
] ]
[[package]]
name = "fastrand"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
[[package]] [[package]]
name = "fernet" name = "fernet"
version = "0.2.1" version = "0.2.1"
@ -1767,7 +1773,7 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [ dependencies = [
"fastrand", "fastrand 1.9.0",
"futures-core", "futures-core",
"futures-io", "futures-io",
"memchr", "memchr",
@ -2222,9 +2228,9 @@ dependencies = [
[[package]] [[package]]
name = "http-range-header" name = "http-range-header"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
[[package]] [[package]]
name = "http-types" name = "http-types"
@ -2485,9 +2491,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.8" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
@ -2649,6 +2655,7 @@ dependencies = [
name = "kanidm_unix_int" name = "kanidm_unix_int"
version = "1.1.0-beta.13-dev" version = "1.1.0-beta.13-dev"
dependencies = [ dependencies = [
"async-trait",
"base64urlsafedata", "base64urlsafedata",
"bytes", "bytes",
"clap 4.3.19", "clap 4.3.19",
@ -3025,9 +3032,9 @@ dependencies = [
[[package]] [[package]]
name = "libz-sys" name = "libz-sys"
version = "1.1.9" version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" checksum = "24e6ab01971eb092ffe6a7d42f49f9ff42662f17604681e2843ad65077ba47dc"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -3337,9 +3344,9 @@ dependencies = [
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.15" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -3654,9 +3661,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.7.0" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5"
dependencies = [ dependencies = [
"thiserror", "thiserror",
"ucd-trie", "ucd-trie",
@ -3819,9 +3826,9 @@ dependencies = [
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.10" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 2.0.27", "syn 2.0.27",
@ -4079,7 +4086,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-automata 0.3.2", "regex-automata 0.3.3",
"regex-syntax 0.7.4", "regex-syntax 0.7.4",
] ]
@ -4094,9 +4101,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -4281,15 +4288,15 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.13" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.14" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -4334,15 +4341,15 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.9.1" version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"core-foundation", "core-foundation",
@ -4353,9 +4360,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework-sys" name = "security-framework-sys"
version = "2.9.0" version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -4453,9 +4460,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_bytes" name = "serde_bytes"
version = "0.11.11" version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a" checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -4504,9 +4511,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_path_to_error" name = "serde_path_to_error"
version = "0.1.13" version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc4422959dd87a76cb117c191dcbffc20467f06c9100b76721dab370f24d3a" checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
dependencies = [ dependencies = [
"itoa", "itoa",
"serde", "serde",
@ -4837,21 +4844,20 @@ dependencies = [
[[package]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.12.9" version = "0.12.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8e77cb757a61f51b947ec4a7e3646efd825b73561db1c232a8ccb639e611a0" checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.6.0" version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998"
dependencies = [ dependencies = [
"autocfg",
"cfg-if", "cfg-if",
"fastrand", "fastrand 2.0.0",
"redox_syscall 0.3.5", "redox_syscall 0.3.5",
"rustix 0.37.23", "rustix 0.38.4",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -4872,18 +4878,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.43" version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.43" version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -5117,9 +5123,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.12" version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [ dependencies = [
"indexmap 2.0.0", "indexmap 2.0.0",
"toml_datetime", "toml_datetime",
@ -5348,9 +5354,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.10" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@ -5948,9 +5954,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.4.9" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -42,6 +42,7 @@ name = "kanidm_unix_common"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
async-trait.workspace = true
base64urlsafedata = { workspace = true } base64urlsafedata = { workspace = true }
bytes = { workspace = true } bytes = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] } clap = { workspace = true, features = ["derive", "env"] }

View file

@ -25,8 +25,9 @@ use clap::{Arg, ArgAction, Command};
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use kanidm_client::KanidmClientBuilder; use kanidm_client::KanidmClientBuilder;
use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH; use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
use kanidm_unix_common::cache::CacheLayer;
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::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};
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse, TaskRequest, TaskResponse}; use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse, TaskRequest, TaskResponse};
@ -181,7 +182,7 @@ async fn handle_task_client(
async fn handle_client( async fn handle_client(
sock: UnixStream, sock: UnixStream,
cachelayer: Arc<CacheLayer>, cachelayer: Arc<Resolver>,
task_channel_tx: &Sender<AsyncTaskRequest>, task_channel_tx: &Sender<AsyncTaskRequest>,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
debug!("Accepted connection"); debug!("Accepted connection");
@ -371,7 +372,7 @@ async fn handle_client(
Ok(()) Ok(())
} }
async fn process_etc_passwd_group(cachelayer: &CacheLayer) -> Result<(), Box<dyn Error>> { async fn process_etc_passwd_group(cachelayer: &Resolver) -> 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?;
@ -660,8 +661,16 @@ async fn main() -> ExitCode {
} }
}; };
let cl_inner = match CacheLayer::new( let db = match Db::new(cfg.db_path.as_str(), &cfg.tpm_policy) {
cfg.db_path.as_str(), // The sqlite db path Ok(db) => db,
Err(_e) => {
error!("Failed to create database");
return ExitCode::FAILURE
}
};
let cl_inner = match Resolver::new(
db,
cfg.cache_timeout, cfg.cache_timeout,
rsclient, rsclient,
cfg.pam_allowed_login_groups.clone(), cfg.pam_allowed_login_groups.clone(),
@ -672,7 +681,6 @@ async fn main() -> ExitCode {
cfg.uid_attr_map, cfg.uid_attr_map,
cfg.gid_attr_map, cfg.gid_attr_map,
cfg.allow_local_account_override.clone(), cfg.allow_local_account_override.clone(),
&cfg.tpm_policy,
) )
.await .await
{ {

View file

@ -3,6 +3,7 @@ use std::fmt;
use std::time::Duration; use std::time::Duration;
use crate::unix_config::TpmPolicy; use crate::unix_config::TpmPolicy;
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;
@ -11,7 +12,58 @@ use libc::umask;
use rusqlite::Connection; use rusqlite::Connection;
use tokio::sync::{Mutex, MutexGuard}; use tokio::sync::{Mutex, MutexGuard};
use crate::cache::Id; use crate::resolver::Id;
#[async_trait]
pub trait Cache {
type Txn<'db>
where
Self: 'db;
async fn write<'db>(&'db self) -> Self::Txn<'db>;
}
#[derive(Debug)]
pub enum CacheError {
Cryptography,
SerdeJson,
Parse,
Sqlite,
TooManyResults,
TransactionInvalidState,
}
pub trait CacheTxn {
fn migrate(&self) -> Result<(), CacheError>;
fn commit(self) -> Result<(), CacheError>;
fn invalidate(&self) -> Result<(), CacheError>;
fn clear(&self) -> Result<(), CacheError>;
fn get_account(&self, account_id: &Id) -> Result<Option<(UnixUserToken, u64)>, CacheError>;
fn get_accounts(&self) -> Result<Vec<UnixUserToken>, CacheError>;
fn update_account(&self, account: &UnixUserToken, expire: u64) -> Result<(), CacheError>;
fn delete_account(&self, a_uuid: &str) -> Result<(), CacheError>;
fn update_account_password(&self, a_uuid: &str, cred: &str) -> Result<(), CacheError>;
fn check_account_password(&self, a_uuid: &str, cred: &str) -> Result<bool, CacheError>;
fn get_group(&self, grp_id: &Id) -> Result<Option<(UnixGroupToken, u64)>, CacheError>;
fn get_group_members(&self, g_uuid: &str) -> Result<Vec<UnixUserToken>, CacheError>;
fn get_groups(&self) -> Result<Vec<UnixGroupToken>, CacheError>;
fn update_group(&self, grp: &UnixGroupToken, expire: u64) -> Result<(), CacheError>;
fn delete_group(&self, g_uuid: &str) -> Result<(), CacheError>;
}
pub struct Db { pub struct Db {
conn: Mutex<Connection>, conn: Mutex<Connection>,
@ -53,9 +105,14 @@ impl Db {
require_tpm, require_tpm,
}) })
} }
}
#[async_trait]
impl Cache for Db {
type Txn<'db> = DbTxn<'db>;
#[allow(clippy::expect_used)] #[allow(clippy::expect_used)]
pub async fn write(&self) -> DbTxn<'_> { async fn write<'db>(&'db self) -> Self::Txn<'db> {
let conn = self.conn.lock().await; let conn = self.conn.lock().await;
DbTxn::new(conn, &self.crypto_policy, self.require_tpm.as_ref()) DbTxn::new(conn, &self.crypto_policy, self.require_tpm.as_ref())
} }
@ -68,7 +125,7 @@ impl fmt::Debug for Db {
} }
impl<'a> DbTxn<'a> { impl<'a> DbTxn<'a> {
pub fn new( fn new(
conn: MutexGuard<'a, Connection>, conn: MutexGuard<'a, Connection>,
crypto_policy: &'a CryptoPolicy, crypto_policy: &'a CryptoPolicy,
require_tpm: Option<&'a tpm::TpmConfig>, require_tpm: Option<&'a tpm::TpmConfig>,
@ -87,33 +144,109 @@ impl<'a> DbTxn<'a> {
} }
/// This handles an error coming back from an sqlite event and dumps more information from it /// This handles an error coming back from an sqlite event and dumps more information from it
fn sqlite_error(&self, msg: &str, error: &rusqlite::Error) { fn sqlite_error(&self, msg: &str, error: &rusqlite::Error) -> CacheError {
error!( error!(
"sqlite {} error: {:?} db_path={:?}", "sqlite {} error: {:?} db_path={:?}",
msg, msg,
error, error,
&self.conn.path() &self.conn.path()
); );
CacheError::Sqlite
} }
/// This handles an error coming back from an sqlite transaction and dumps a load of information from it /// This handles an error coming back from an sqlite transaction and dumps a load of information from it
fn sqlite_transaction_error(&self, error: &rusqlite::Error, _stmt: &rusqlite::Statement) { fn sqlite_transaction_error(
&self,
error: &rusqlite::Error,
_stmt: &rusqlite::Statement,
) -> CacheError {
error!( error!(
"sqlite transaction error={:?} db_path={:?}", "sqlite transaction error={:?} db_path={:?}",
error, error,
&self.conn.path(), &self.conn.path(),
); );
// TODO: one day figure out if there's an easy way to dump the transaction without the token... // TODO: one day figure out if there's an easy way to dump the transaction without the token...
CacheError::Sqlite
} }
pub fn migrate(&self) -> Result<(), ()> { fn get_account_data_name(&self, account_id: &str) -> Result<Vec<(Vec<u8>, i64)>, CacheError> {
let mut stmt = self.conn
.prepare(
"SELECT token, expiry FROM account_t WHERE uuid = :account_id OR name = :account_id OR spn = :account_id"
)
.map_err(|e| {
self.sqlite_error("select prepare", &e)
})?;
// Makes tuple (token, expiry)
let data_iter = stmt
.query_map([account_id], |row| Ok((row.get(0)?, row.get(1)?)))
.map_err(|e| self.sqlite_error("query_map failure", &e))?;
let data: Result<Vec<(Vec<u8>, i64)>, _> = data_iter
.map(|v| v.map_err(|e| self.sqlite_error("map failure", &e)))
.collect();
data
}
fn get_account_data_gid(&self, gid: u32) -> Result<Vec<(Vec<u8>, i64)>, CacheError> {
let mut stmt = self
.conn
.prepare("SELECT token, expiry FROM account_t WHERE gidnumber = :gid")
.map_err(|e| self.sqlite_error("select prepare", &e))?;
// Makes tuple (token, expiry)
let data_iter = stmt
.query_map(params![gid], |row| Ok((row.get(0)?, row.get(1)?)))
.map_err(|e| self.sqlite_error("query_map", &e))?;
let data: Result<Vec<(Vec<u8>, i64)>, _> = data_iter
.map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
.collect();
data
}
fn get_group_data_name(&self, grp_id: &str) -> Result<Vec<(Vec<u8>, i64)>, CacheError> {
let mut stmt = self.conn
.prepare(
"SELECT token, expiry FROM group_t WHERE uuid = :grp_id OR name = :grp_id OR spn = :grp_id"
)
.map_err(|e| {
self.sqlite_error("select prepare", &e)
})?;
// Makes tuple (token, expiry)
let data_iter = stmt
.query_map([grp_id], |row| Ok((row.get(0)?, row.get(1)?)))
.map_err(|e| self.sqlite_error("query_map", &e))?;
let data: Result<Vec<(Vec<u8>, i64)>, _> = data_iter
.map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
.collect();
data
}
fn get_group_data_gid(&self, gid: u32) -> Result<Vec<(Vec<u8>, i64)>, CacheError> {
let mut stmt = self
.conn
.prepare("SELECT token, expiry FROM group_t WHERE gidnumber = :gid")
.map_err(|e| self.sqlite_error("select prepare", &e))?;
// Makes tuple (token, expiry)
let data_iter = stmt
.query_map(params![gid], |row| Ok((row.get(0)?, row.get(1)?)))
.map_err(|e| self.sqlite_error("query_map", &e))?;
let data: Result<Vec<(Vec<u8>, i64)>, _> = data_iter
.map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
.collect();
data
}
}
impl<'a> CacheTxn for DbTxn<'a> {
fn migrate(&self) -> Result<(), CacheError> {
self.conn.set_prepared_statement_cache_capacity(16); self.conn.set_prepared_statement_cache_capacity(16);
self.conn self.conn
.prepare("PRAGMA journal_mode=WAL;") .prepare("PRAGMA journal_mode=WAL;")
.and_then(|mut wal_stmt| wal_stmt.query([]).map(|_| ())) .and_then(|mut wal_stmt| wal_stmt.query([]).map(|_| ()))
.map_err(|e| { .map_err(|e| self.sqlite_error("account_t create", &e))?;
self.sqlite_error("account_t create", &e);
})?;
// Setup two tables - one for accounts, one for groups. // Setup two tables - one for accounts, one for groups.
// correctly index the columns. // correctly index the columns.
@ -132,9 +265,7 @@ impl<'a> DbTxn<'a> {
", ",
[], [],
) )
.map_err(|e| { .map_err(|e| self.sqlite_error("account_t create", &e))?;
self.sqlite_error("account_t create", &e);
})?;
self.conn self.conn
.execute( .execute(
@ -149,9 +280,7 @@ impl<'a> DbTxn<'a> {
", ",
[], [],
) )
.map_err(|e| { .map_err(|e| self.sqlite_error("group_t create", &e))?;
self.sqlite_error("group_t create", &e);
})?;
// We defer group foreign keys here because we now manually cascade delete these when // We defer group foreign keys here because we now manually cascade delete these when
// required. This is because insert or replace into will always delete then add // required. This is because insert or replace into will always delete then add
@ -170,115 +299,54 @@ impl<'a> DbTxn<'a> {
", ",
[], [],
) )
.map_err(|e| { .map_err(|e| self.sqlite_error("memberof_t create error", &e))?;
self.sqlite_error("memberof_t create error", &e);
})?;
Ok(()) Ok(())
} }
pub fn commit(mut self) -> Result<(), ()> { fn commit(mut self) -> Result<(), CacheError> {
// debug!("Committing BE txn"); // debug!("Committing BE txn");
if self.committed { if self.committed {
error!("Invalid state, SQL transaction was already committed!"); error!("Invalid state, SQL transaction was already committed!");
return Err(()); return Err(CacheError::TransactionInvalidState);
} }
self.committed = true; self.committed = true;
self.conn self.conn
.execute("COMMIT TRANSACTION", []) .execute("COMMIT TRANSACTION", [])
.map(|_| ()) .map(|_| ())
.map_err(|e| { .map_err(|e| self.sqlite_error("commit", &e))
self.sqlite_error("commit", &e);
})
} }
pub fn invalidate(&self) -> Result<(), ()> { fn invalidate(&self) -> Result<(), CacheError> {
self.conn self.conn
.execute("UPDATE group_t SET expiry = 0", []) .execute("UPDATE group_t SET expiry = 0", [])
.map_err(|e| { .map_err(|e| self.sqlite_error("update group_t", &e))?;
self.sqlite_error("update group_t", &e);
})?;
self.conn self.conn
.execute("UPDATE account_t SET expiry = 0", []) .execute("UPDATE account_t SET expiry = 0", [])
.map_err(|e| { .map_err(|e| self.sqlite_error("update account_t", &e))?;
self.sqlite_error("update account_t", &e);
})?;
Ok(()) Ok(())
} }
pub fn clear_cache(&self) -> Result<(), ()> { fn clear(&self) -> Result<(), CacheError> {
self.conn self.conn
.execute("DELETE FROM memberof_t", []) .execute("DELETE FROM memberof_t", [])
.map_err(|e| { .map_err(|e| self.sqlite_error("delete memberof_t", &e))?;
self.sqlite_error("delete memberof_t", &e);
})?;
self.conn.execute("DELETE FROM group_t", []).map_err(|e| { self.conn
self.sqlite_error("delete group_t", &e); .execute("DELETE FROM group_t", [])
})?; .map_err(|e| self.sqlite_error("delete group_t", &e))?;
self.conn self.conn
.execute("DELETE FROM account_t", []) .execute("DELETE FROM account_t", [])
.map_err(|e| { .map_err(|e| self.sqlite_error("delete group_t", &e))?;
self.sqlite_error("delete group_t", &e);
})?;
Ok(()) Ok(())
} }
fn get_account_data_name(&self, account_id: &str) -> Result<Vec<(Vec<u8>, i64)>, ()> { fn get_account(&self, account_id: &Id) -> Result<Option<(UnixUserToken, u64)>, CacheError> {
let mut stmt = self.conn
.prepare(
"SELECT token, expiry FROM account_t WHERE uuid = :account_id OR name = :account_id OR spn = :account_id"
)
.map_err(|e| {
self.sqlite_error("select prepare", &e);
})?;
// Makes tuple (token, expiry)
let data_iter = stmt
.query_map([account_id], |row| Ok((row.get(0)?, row.get(1)?)))
.map_err(|e| {
self.sqlite_error("query_map failure", &e);
})?;
let data: Result<Vec<(Vec<u8>, i64)>, _> = data_iter
.map(|v| {
v.map_err(|e| {
self.sqlite_error("map failure", &e);
})
})
.collect();
data
}
fn get_account_data_gid(&self, gid: u32) -> Result<Vec<(Vec<u8>, i64)>, ()> {
let mut stmt = self
.conn
.prepare("SELECT token, expiry FROM account_t WHERE gidnumber = :gid")
.map_err(|e| {
self.sqlite_error("select prepare", &e);
})?;
// Makes tuple (token, expiry)
let data_iter = stmt
.query_map(params![gid], |row| Ok((row.get(0)?, row.get(1)?)))
.map_err(|e| {
self.sqlite_error("query_map", &e);
})?;
let data: Result<Vec<(Vec<u8>, i64)>, _> = data_iter
.map(|v| {
v.map_err(|e| {
self.sqlite_error("map", &e);
})
})
.collect();
data
}
pub fn get_account(&self, account_id: &Id) -> Result<Option<(UnixUserToken, u64)>, ()> {
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),
@ -287,7 +355,7 @@ impl<'a> DbTxn<'a> {
// Assert only one result? // Assert only one result?
if data.len() >= 2 { if data.len() >= 2 {
error!("invalid db state, multiple entries matched query?"); error!("invalid db state, multiple entries matched query?");
return Err(()); return Err(CacheError::TooManyResults);
} }
if let Some((token, expiry)) = data.first() { if let Some((token, expiry)) = data.first() {
@ -298,6 +366,7 @@ impl<'a> DbTxn<'a> {
Ok(t) => { Ok(t) => {
let e = u64::try_from(*expiry).map_err(|e| { let e = u64::try_from(*expiry).map_err(|e| {
error!("u64 convert error -> {:?}", e); error!("u64 convert error -> {:?}", e);
CacheError::Parse
})?; })?;
Ok(Some((t, e))) Ok(Some((t, e)))
} }
@ -311,23 +380,17 @@ impl<'a> DbTxn<'a> {
} }
} }
pub fn get_accounts(&self) -> Result<Vec<UnixUserToken>, ()> { fn get_accounts(&self) -> Result<Vec<UnixUserToken>, CacheError> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT token FROM account_t") .prepare("SELECT token FROM account_t")
.map_err(|e| { .map_err(|e| self.sqlite_error("select prepare", &e))?;
self.sqlite_error("select prepare", &e);
})?;
let data_iter = stmt.query_map([], |row| row.get(0)).map_err(|e| { let data_iter = stmt
self.sqlite_error("query_map", &e); .query_map([], |row| row.get(0))
})?; .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| { .map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
v.map_err(|e| {
self.sqlite_error("map", &e);
})
})
.collect(); .collect();
let data = data?; let data = data?;
@ -346,12 +409,14 @@ impl<'a> DbTxn<'a> {
.collect()) .collect())
} }
pub fn update_account(&self, account: &UnixUserToken, expire: u64) -> Result<(), ()> { fn update_account(&self, account: &UnixUserToken, 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
})?; })?;
let expire = i64::try_from(expire).map_err(|e| { let expire = i64::try_from(expire).map_err(|e| {
error!("update_account i64 conversion error -> {:?}", e); error!("update_account i64 conversion error -> {:?}", e);
CacheError::Parse
})?; })?;
// 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
@ -368,7 +433,7 @@ impl<'a> DbTxn<'a> {
} }
) )
.map_err(|e| { .map_err(|e| {
self.sqlite_error("delete account_t duplicate", &e); self.sqlite_error("delete account_t duplicate", &e)
}) })
.map(|_| ())?; .map(|_| ())?;
@ -384,14 +449,14 @@ impl<'a> DbTxn<'a> {
} }
) )
.map_err(|e| { .map_err(|e| {
self.sqlite_error("delete account_t duplicate", &e); self.sqlite_error("delete account_t duplicate", &e)
})?; })?;
if updated == 0 { if updated == 0 {
let mut stmt = self.conn let mut stmt = self.conn
.prepare("INSERT INTO account_t (uuid, name, spn, gidnumber, token, expiry) VALUES (:uuid, :name, :spn, :gidnumber, :token, :expiry) ON CONFLICT(uuid) DO UPDATE SET name=excluded.name, spn=excluded.name, gidnumber=excluded.gidnumber, token=excluded.token, expiry=excluded.expiry") .prepare("INSERT INTO account_t (uuid, name, spn, gidnumber, token, expiry) VALUES (:uuid, :name, :spn, :gidnumber, :token, :expiry) ON CONFLICT(uuid) DO UPDATE SET name=excluded.name, spn=excluded.name, gidnumber=excluded.gidnumber, token=excluded.token, expiry=excluded.expiry")
.map_err(|e| { .map_err(|e| {
self.sqlite_error("prepare", &e); self.sqlite_error("prepare", &e)
})?; })?;
stmt.execute(named_params! { stmt.execute(named_params! {
@ -405,9 +470,7 @@ impl<'a> DbTxn<'a> {
.map(|r| { .map(|r| {
debug!("insert -> {:?}", r); debug!("insert -> {:?}", r);
}) })
.map_err(|error| { .map_err(|error| self.sqlite_transaction_error(&error, &stmt))?;
self.sqlite_transaction_error(&error, &stmt);
})?;
} }
// Now, we have to update the group memberships. // Now, we have to update the group memberships.
@ -416,24 +479,18 @@ impl<'a> DbTxn<'a> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("DELETE FROM memberof_t WHERE a_uuid = :a_uuid") .prepare("DELETE FROM memberof_t WHERE a_uuid = :a_uuid")
.map_err(|e| { .map_err(|e| self.sqlite_error("prepare", &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);
}) })
.map_err(|error| { .map_err(|error| self.sqlite_transaction_error(&error, &stmt))?;
self.sqlite_transaction_error(&error, &stmt);
})?;
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("INSERT INTO memberof_t (a_uuid, g_uuid) VALUES (:a_uuid, :g_uuid)") .prepare("INSERT INTO memberof_t (a_uuid, g_uuid) VALUES (:a_uuid, :g_uuid)")
.map_err(|e| { .map_err(|e| self.sqlite_error("prepare", &e))?;
self.sqlite_error("prepare", &e);
})?;
// 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! {
@ -443,22 +500,18 @@ impl<'a> DbTxn<'a> {
.map(|r| { .map(|r| {
debug!("insert membership -> {:?}", r); debug!("insert membership -> {:?}", r);
}) })
.map_err(|error| { .map_err(|error| self.sqlite_transaction_error(&error, &stmt))
self.sqlite_transaction_error(&error, &stmt);
})
}) })
} }
pub fn delete_account(&self, a_uuid: &str) -> Result<(), ()> { fn delete_account(&self, a_uuid: &str) -> Result<(), CacheError> {
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![a_uuid],
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| { .map_err(|e| self.sqlite_error("account_t memberof_t cascade delete", &e))?;
self.sqlite_error("account_t memberof_t cascade delete", &e);
})?;
self.conn self.conn
.execute( .execute(
@ -466,12 +519,10 @@ impl<'a> DbTxn<'a> {
params![a_uuid], params![a_uuid],
) )
.map(|_| ()) .map(|_| ())
.map_err(|e| { .map_err(|e| self.sqlite_error("account_t delete", &e))
self.sqlite_error("account_t delete", &e);
})
} }
pub fn update_account_password(&self, a_uuid: &str, cred: &str) -> Result<(), ()> { fn update_account_password(&self, a_uuid: &str, 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.
@ -485,12 +536,14 @@ impl<'a> DbTxn<'a> {
} else { } else {
Password::new(self.crypto_policy, cred).map_err(|e| { Password::new(self.crypto_policy, cred).map_err(|e| {
error!("password error -> {:?}", e); error!("password error -> {:?}", e);
CacheError::Cryptography
})? })?
}; };
let dbpw = pw.to_dbpasswordv1(); let dbpw = pw.to_dbpasswordv1();
let data = serde_json::to_vec(&dbpw).map_err(|e| { let data = serde_json::to_vec(&dbpw).map_err(|e| {
error!("json error -> {:?}", e); error!("json error -> {:?}", e);
CacheError::SerdeJson
})?; })?;
self.conn self.conn
@ -501,13 +554,11 @@ impl<'a> DbTxn<'a> {
":data": &data, ":data": &data,
}, },
) )
.map_err(|e| { .map_err(|e| self.sqlite_error("update account_t password", &e))
self.sqlite_error("update account_t password", &e);
})
.map(|_| ()) .map(|_| ())
} }
pub fn check_account_password(&self, a_uuid: &str, cred: &str) -> Result<bool, ()> { fn check_account_password(&self, a_uuid: &str, 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);
@ -516,20 +567,14 @@ impl<'a> DbTxn<'a> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT password FROM account_t WHERE uuid = :a_uuid AND password IS NOT NULL") .prepare("SELECT password FROM account_t WHERE uuid = :a_uuid AND password IS NOT NULL")
.map_err(|e| { .map_err(|e| self.sqlite_error("select prepare", &e))?;
self.sqlite_error("select prepare", &e);
})?;
// Makes tuple (token, expiry) // Makes tuple (token, expiry)
let data_iter = stmt.query_map([a_uuid], |row| row.get(0)).map_err(|e| { let data_iter = stmt
self.sqlite_error("query_map", &e); .query_map([a_uuid], |row| row.get(0))
})?; .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| { .map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
v.map_err(|e| {
self.sqlite_error("map", &e);
})
})
.collect(); .collect();
let data = data?; let data = data?;
@ -541,7 +586,7 @@ impl<'a> DbTxn<'a> {
if data.len() >= 2 { if data.len() >= 2 {
error!("invalid db state, multiple entries matched query?"); error!("invalid db state, multiple entries matched query?");
return Err(()); return Err(CacheError::TooManyResults);
} }
let pw = data.first().map(|raw| { let pw = data.first().map(|raw| {
@ -570,60 +615,12 @@ impl<'a> DbTxn<'a> {
} else { } else {
pw.verify(cred).map_err(|e| { pw.verify(cred).map_err(|e| {
error!("password error -> {:?}", e); error!("password error -> {:?}", e);
CacheError::Cryptography
}) })
} }
} }
fn get_group_data_name(&self, grp_id: &str) -> Result<Vec<(Vec<u8>, i64)>, ()> { fn get_group(&self, grp_id: &Id) -> Result<Option<(UnixGroupToken, u64)>, CacheError> {
let mut stmt = self.conn
.prepare(
"SELECT token, expiry FROM group_t WHERE uuid = :grp_id OR name = :grp_id OR spn = :grp_id"
)
.map_err(|e| {
self.sqlite_error("select prepare", &e);
})?;
// Makes tuple (token, expiry)
let data_iter = stmt
.query_map([grp_id], |row| Ok((row.get(0)?, row.get(1)?)))
.map_err(|e| {
self.sqlite_error("query_map", &e);
})?;
let data: Result<Vec<(Vec<u8>, i64)>, _> = data_iter
.map(|v| {
v.map_err(|e| {
self.sqlite_error("map", &e);
})
})
.collect();
data
}
fn get_group_data_gid(&self, gid: u32) -> Result<Vec<(Vec<u8>, i64)>, ()> {
let mut stmt = self
.conn
.prepare("SELECT token, expiry FROM group_t WHERE gidnumber = :gid")
.map_err(|e| {
self.sqlite_error("select prepare", &e);
})?;
// Makes tuple (token, expiry)
let data_iter = stmt
.query_map(params![gid], |row| Ok((row.get(0)?, row.get(1)?)))
.map_err(|e| {
self.sqlite_error("query_map", &e);
})?;
let data: Result<Vec<(Vec<u8>, i64)>, _> = data_iter
.map(|v| {
v.map_err(|e| {
self.sqlite_error("map", &e);
})
})
.collect();
data
}
pub fn get_group(&self, grp_id: &Id) -> Result<Option<(UnixGroupToken, u64)>, ()> {
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),
@ -632,7 +629,7 @@ impl<'a> DbTxn<'a> {
// Assert only one result? // Assert only one result?
if data.len() >= 2 { if data.len() >= 2 {
error!("invalid db state, multiple entries matched query?"); error!("invalid db state, multiple entries matched query?");
return Err(()); return Err(CacheError::TooManyResults);
} }
if let Some((token, expiry)) = data.first() { if let Some((token, expiry)) = data.first() {
@ -643,6 +640,7 @@ impl<'a> DbTxn<'a> {
Ok(t) => { Ok(t) => {
let e = u64::try_from(*expiry).map_err(|e| { let e = u64::try_from(*expiry).map_err(|e| {
error!("u64 convert error -> {:?}", e); error!("u64 convert error -> {:?}", e);
CacheError::Parse
})?; })?;
Ok(Some((t, e))) Ok(Some((t, e)))
} }
@ -656,23 +654,19 @@ impl<'a> DbTxn<'a> {
} }
} }
pub fn get_group_members(&self, g_uuid: &str) -> Result<Vec<UnixUserToken>, ()> { fn get_group_members(&self, g_uuid: &str) -> Result<Vec<UnixUserToken>, 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")
.map_err(|e| { .map_err(|e| {
self.sqlite_error("select prepare", &e); self.sqlite_error("select prepare", &e)
})?; })?;
let data_iter = stmt.query_map([g_uuid], |row| row.get(0)).map_err(|e| { let data_iter = stmt
self.sqlite_error("query_map", &e); .query_map([g_uuid], |row| row.get(0))
})?; .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| { .map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
v.map_err(|e| {
self.sqlite_error("map", &e);
})
})
.collect(); .collect();
let data = data?; let data = data?;
@ -683,28 +677,23 @@ impl<'a> DbTxn<'a> {
// debug!("{:?}", token); // debug!("{:?}", token);
serde_json::from_slice(token.as_slice()).map_err(|e| { serde_json::from_slice(token.as_slice()).map_err(|e| {
error!("json error -> {:?}", e); error!("json error -> {:?}", e);
CacheError::SerdeJson
}) })
}) })
.collect() .collect()
} }
pub fn get_groups(&self) -> Result<Vec<UnixGroupToken>, ()> { fn get_groups(&self) -> Result<Vec<UnixGroupToken>, CacheError> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT token FROM group_t") .prepare("SELECT token FROM group_t")
.map_err(|e| { .map_err(|e| self.sqlite_error("select prepare", &e))?;
self.sqlite_error("select prepare", &e);
})?;
let data_iter = stmt.query_map([], |row| row.get(0)).map_err(|e| { let data_iter = stmt
self.sqlite_error("query_map", &e); .query_map([], |row| row.get(0))
})?; .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| { .map(|v| v.map_err(|e| self.sqlite_error("map", &e)))
v.map_err(|e| {
self.sqlite_error("map", &e);
})
})
.collect(); .collect();
let data = data?; let data = data?;
@ -723,18 +712,20 @@ impl<'a> DbTxn<'a> {
.collect()) .collect())
} }
pub fn update_group(&self, grp: &UnixGroupToken, expire: u64) -> Result<(), ()> { fn update_group(&self, grp: &UnixGroupToken, 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
})?; })?;
let expire = i64::try_from(expire).map_err(|e| { let expire = i64::try_from(expire).map_err(|e| {
error!("i64 convert error -> {:?}", e); error!("i64 convert error -> {:?}", e);
CacheError::Parse
})?; })?;
let mut stmt = self.conn let mut stmt = self.conn
.prepare("INSERT OR REPLACE INTO group_t (uuid, name, spn, gidnumber, token, expiry) VALUES (:uuid, :name, :spn, :gidnumber, :token, :expiry)") .prepare("INSERT OR REPLACE INTO group_t (uuid, name, spn, gidnumber, token, expiry) VALUES (:uuid, :name, :spn, :gidnumber, :token, :expiry)")
.map_err(|e| { .map_err(|e| {
self.sqlite_error("prepare", &e); self.sqlite_error("prepare", &e)
})?; })?;
stmt.execute(named_params! { stmt.execute(named_params! {
@ -748,24 +739,18 @@ impl<'a> DbTxn<'a> {
.map(|r| { .map(|r| {
debug!("insert -> {:?}", r); debug!("insert -> {:?}", r);
}) })
.map_err(|e| { .map_err(|e| self.sqlite_error("execute", &e))
self.sqlite_error("execute", &e);
})
} }
pub fn delete_group(&self, g_uuid: &str) -> Result<(), ()> { fn delete_group(&self, g_uuid: &str) -> Result<(), CacheError> {
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", [g_uuid])
.map(|_| ()) .map(|_| ())
.map_err(|e| { .map_err(|e| self.sqlite_error("group_t memberof_t cascade delete", &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", [g_uuid])
.map(|_| ()) .map(|_| ())
.map_err(|e| { .map_err(|e| self.sqlite_error("group_t delete", &e))
self.sqlite_error("group_t delete", &e);
})
} }
} }
@ -1143,9 +1128,10 @@ pub(crate) mod tpm {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken}; use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
// use std::assert_matches::assert_matches;
use super::Db; use super::{Cache, CacheTxn, Db};
use crate::cache::Id; use crate::resolver::Id;
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";
@ -1220,7 +1206,7 @@ mod tests {
assert!(r4.is_some()); assert!(r4.is_some());
// Clear cache // Clear cache
assert!(dbtxn.clear_cache().is_ok()); assert!(dbtxn.clear().is_ok());
// should be nothing // should be nothing
let r1 = dbtxn.get_account(&id_name2).unwrap(); let r1 = dbtxn.get_account(&id_name2).unwrap();
@ -1295,7 +1281,7 @@ mod tests {
assert!(r4.is_some()); assert!(r4.is_some());
// clear cache // clear cache
assert!(dbtxn.clear_cache().is_ok()); assert!(dbtxn.clear().is_ok());
// should be nothing. // should be nothing.
let r1 = dbtxn.get_group(&id_name2).unwrap(); let r1 = dbtxn.get_group(&id_name2).unwrap();
@ -1407,30 +1393,51 @@ mod tests {
}; };
// Test that with no account, is false // Test that with no account, is false
assert!(dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_A) == Ok(false)); assert!(matches!(
dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_A),
Ok(false)
));
// test adding an account // test adding an account
dbtxn.update_account(&ut1, 0).unwrap(); dbtxn.update_account(&ut1, 0).unwrap();
// check with no password is false. // check with no password is false.
assert!(dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_A) == Ok(false)); assert!(matches!(
dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_A),
Ok(false)
));
// update the pw // update the pw
assert!(dbtxn assert!(dbtxn
.update_account_password(uuid1, TESTACCOUNT1_PASSWORD_A) .update_account_password(uuid1, TESTACCOUNT1_PASSWORD_A)
.is_ok()); .is_ok());
// Check it now works. // Check it now works.
assert!(dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_A) == Ok(true)); assert!(matches!(
assert!(dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_B) == Ok(false)); dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_A),
Ok(true)
));
assert!(matches!(
dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_B),
Ok(false)
));
// Update the pw // Update the pw
assert!(dbtxn assert!(dbtxn
.update_account_password(uuid1, TESTACCOUNT1_PASSWORD_B) .update_account_password(uuid1, TESTACCOUNT1_PASSWORD_B)
.is_ok()); .is_ok());
// Check it matches. // Check it matches.
assert!(dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_A) == Ok(false)); assert!(matches!(
assert!(dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_B) == Ok(true)); dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_A),
Ok(false)
));
assert!(matches!(
dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_B),
Ok(true)
));
// Check that updating the account does not break the password. // Check that updating the account does not break the password.
ut1.displayname = "Test User Update".to_string(); ut1.displayname = "Test User Update".to_string();
dbtxn.update_account(&ut1, 0).unwrap(); dbtxn.update_account(&ut1, 0).unwrap();
assert!(dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_B) == Ok(true)); assert!(matches!(
dbtxn.check_account_password(uuid1, TESTACCOUNT1_PASSWORD_B),
Ok(true)
));
assert!(dbtxn.commit().is_ok()); assert!(dbtxn.commit().is_ok());
} }

View file

@ -17,8 +17,6 @@ extern crate tracing;
#[macro_use] #[macro_use]
extern crate rusqlite; extern crate rusqlite;
#[cfg(target_family = "unix")]
pub mod cache;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub mod client; pub mod client;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
@ -26,7 +24,9 @@ pub mod client_sync;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub mod constants; pub mod constants;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub(crate) mod db; pub mod db;
#[cfg(target_family = "unix")]
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;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]

View file

@ -12,8 +12,9 @@ use lru::LruCache;
use reqwest::StatusCode; use reqwest::StatusCode;
use tokio::sync::{Mutex, RwLock}; use tokio::sync::{Mutex, RwLock};
use crate::db::Db; use crate::db::{Cache, CacheTxn, Db};
use crate::unix_config::{HomeAttr, TpmPolicy, 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};
@ -34,7 +35,7 @@ enum CacheState {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct CacheLayer { pub struct Resolver {
db: Db, db: Db,
client: RwLock<KanidmClient>, client: RwLock<KanidmClient>,
state: Mutex<CacheState>, state: Mutex<CacheState>,
@ -60,12 +61,14 @@ impl ToString for Id {
} }
} }
impl CacheLayer { impl Resolver {
// TODO: Could consider refactoring this to be better ...
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn new( pub async fn new(
db: Db,
// need db path // need db path
path: &str, // path: &str,
// tpm_policy: &TpmPolicy,
// cache timeout // cache timeout
timeout_seconds: u64, timeout_seconds: u64,
// //
@ -78,15 +81,12 @@ impl CacheLayer {
uid_attr_map: UidAttr, uid_attr_map: UidAttr,
gid_attr_map: UidAttr, gid_attr_map: UidAttr,
allow_id_overrides: Vec<String>, allow_id_overrides: Vec<String>,
tpm_policy: &TpmPolicy,
) -> Result<Self, ()> { ) -> Result<Self, ()> {
let db = Db::new(path, tpm_policy)?;
// setup and do a migrate. // setup and do a migrate.
{ {
let dbtxn = db.write().await; let dbtxn = db.write().await;
dbtxn.migrate()?; dbtxn.migrate().map_err(|_| ())?;
dbtxn.commit()?; dbtxn.commit().map_err(|_| ())?;
} }
if pam_allow_groups.is_empty() { if pam_allow_groups.is_empty() {
@ -95,7 +95,7 @@ impl CacheLayer {
// We assume we are offline at start up, and we mark the next "online check" as // We assume we are offline at start up, and we mark the next "online check" as
// being valid from "now". // being valid from "now".
Ok(CacheLayer { Ok(Resolver {
db, db,
client: RwLock::new(client), client: RwLock::new(client),
state: Mutex::new(CacheState::OfflineNextCheck(SystemTime::now())), state: Mutex::new(CacheState::OfflineNextCheck(SystemTime::now())),
@ -137,24 +137,27 @@ impl CacheLayer {
let mut nxcache_txn = self.nxcache.lock().await; let mut nxcache_txn = self.nxcache.lock().await;
nxcache_txn.clear(); nxcache_txn.clear();
let dbtxn = self.db.write().await; let dbtxn = self.db.write().await;
dbtxn.clear_cache().and_then(|_| dbtxn.commit()) dbtxn.clear().and_then(|_| dbtxn.commit()).map_err(|_| ())
} }
pub async fn invalidate(&self) -> Result<(), ()> { pub async fn invalidate(&self) -> Result<(), ()> {
let mut nxcache_txn = self.nxcache.lock().await; let mut nxcache_txn = self.nxcache.lock().await;
nxcache_txn.clear(); nxcache_txn.clear();
let dbtxn = self.db.write().await; let dbtxn = self.db.write().await;
dbtxn.invalidate().and_then(|_| dbtxn.commit()) dbtxn
.invalidate()
.and_then(|_| dbtxn.commit())
.map_err(|_| ())
} }
async fn get_cached_usertokens(&self) -> Result<Vec<UnixUserToken>, ()> { async fn get_cached_usertokens(&self) -> Result<Vec<UnixUserToken>, ()> {
let dbtxn = self.db.write().await; let dbtxn = self.db.write().await;
dbtxn.get_accounts() dbtxn.get_accounts().map_err(|_| ())
} }
async fn get_cached_grouptokens(&self) -> Result<Vec<UnixGroupToken>, ()> { async fn get_cached_grouptokens(&self) -> Result<Vec<UnixGroupToken>, ()> {
let dbtxn = self.db.write().await; let dbtxn = self.db.write().await;
dbtxn.get_groups() dbtxn.get_groups().map_err(|_| ())
} }
async fn set_nxcache(&self, id: &Id) { async fn set_nxcache(&self, id: &Id) {
@ -201,7 +204,7 @@ impl CacheLayer {
// * uuid // * uuid
// Attempt to search these in the db. // Attempt to search these in the db.
let dbtxn = self.db.write().await; let dbtxn = self.db.write().await;
let r = dbtxn.get_account(account_id)?; let r = dbtxn.get_account(account_id).map_err(|_| ())?;
match r { match r {
Some((ut, ex)) => { Some((ut, ex)) => {
@ -252,7 +255,7 @@ impl CacheLayer {
// * uuid // * uuid
// Attempt to search these in the db. // Attempt to search these in the db.
let dbtxn = self.db.write().await; let dbtxn = self.db.write().await;
let r = dbtxn.get_group(grp_id)?; let r = dbtxn.get_group(grp_id).map_err(|_| ())?;
match r { match r {
Some((ut, ex)) => { Some((ut, ex)) => {
@ -344,6 +347,7 @@ impl CacheLayer {
dbtxn dbtxn
.update_account(token, offset.as_secs())) .update_account(token, offset.as_secs()))
.and_then(|_| dbtxn.commit()) .and_then(|_| dbtxn.commit())
.map_err(|_| ())
} }
async fn set_cache_grouptoken(&self, token: &UnixGroupToken) -> Result<(), ()> { async fn set_cache_grouptoken(&self, token: &UnixGroupToken) -> Result<(), ()> {
@ -359,16 +363,23 @@ impl CacheLayer {
dbtxn dbtxn
.update_group(token, offset.as_secs()) .update_group(token, offset.as_secs())
.and_then(|_| dbtxn.commit()) .and_then(|_| dbtxn.commit())
.map_err(|_| ())
} }
async fn delete_cache_usertoken(&self, a_uuid: &str) -> Result<(), ()> { async fn delete_cache_usertoken(&self, a_uuid: &str) -> Result<(), ()> {
let dbtxn = self.db.write().await; let dbtxn = self.db.write().await;
dbtxn.delete_account(a_uuid).and_then(|_| dbtxn.commit()) dbtxn
.delete_account(a_uuid)
.and_then(|_| dbtxn.commit())
.map_err(|_| ())
} }
async fn delete_cache_grouptoken(&self, g_uuid: &str) -> Result<(), ()> { async fn delete_cache_grouptoken(&self, g_uuid: &str) -> Result<(), ()> {
let dbtxn = self.db.write().await; let dbtxn = self.db.write().await;
dbtxn.delete_group(g_uuid).and_then(|_| dbtxn.commit()) dbtxn
.delete_group(g_uuid)
.and_then(|_| dbtxn.commit())
.map_err(|_| ())
} }
async fn set_cache_userpassword(&self, a_uuid: &str, cred: &str) -> Result<(), ()> { async fn set_cache_userpassword(&self, a_uuid: &str, cred: &str) -> Result<(), ()> {
@ -376,6 +387,7 @@ impl CacheLayer {
dbtxn dbtxn
.update_account_password(a_uuid, cred) .update_account_password(a_uuid, cred)
.and_then(|x| dbtxn.commit().map(|_| x)) .and_then(|x| dbtxn.commit().map(|_| x))
.map_err(|_| ())
} }
async fn check_cache_userpassword(&self, a_uuid: &str, cred: &str) -> Result<bool, ()> { async fn check_cache_userpassword(&self, a_uuid: &str, cred: &str) -> Result<bool, ()> {
@ -383,6 +395,7 @@ impl CacheLayer {
dbtxn dbtxn
.check_account_password(a_uuid, cred) .check_account_password(a_uuid, cred)
.and_then(|x| dbtxn.commit().map(|_| x)) .and_then(|x| dbtxn.commit().map(|_| x))
.map_err(|_| ())
} }
async fn refresh_usertoken( async fn refresh_usertoken(

View file

@ -6,11 +6,12 @@ use std::sync::atomic::{AtomicU16, Ordering};
use std::time::Duration; use std::time::Duration;
use kanidm_client::{KanidmClient, KanidmClientBuilder}; use kanidm_client::{KanidmClient, KanidmClientBuilder};
use kanidm_unix_common::cache::{CacheLayer, Id};
use kanidm_unix_common::constants::{ use kanidm_unix_common::constants::{
DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX,
DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP, DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP,
}; };
use kanidm_unix_common::db::Db;
use kanidm_unix_common::resolver::{Id, 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;
@ -41,7 +42,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) -> (CacheLayer, KanidmClient) { async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) {
sketching::test_init(); sketching::test_init();
let mut counter = 0; let mut counter = 0;
@ -99,8 +100,14 @@ async fn setup_test(fix_fn: Fixture) -> (CacheLayer, KanidmClient) {
.build() .build()
.expect("Failed to build client"); .expect("Failed to build client");
let cachelayer = CacheLayer::new( let db = Db::new(
"", // The sqlite db path, this is in memory. "", // The sqlite db path, this is in memory.
&TpmPolicy::default(),
)
.expect("Failed to setup DB");
let cachelayer = Resolver::new(
db,
300, 300,
rsclient, rsclient,
vec!["allowed_group".to_string()], vec!["allowed_group".to_string()],
@ -111,7 +118,6 @@ async fn setup_test(fix_fn: Fixture) -> (CacheLayer, KanidmClient) {
DEFAULT_UID_ATTR_MAP, DEFAULT_UID_ATTR_MAP,
DEFAULT_GID_ATTR_MAP, DEFAULT_GID_ATTR_MAP,
vec!["masked_group".to_string()], vec!["masked_group".to_string()],
&TpmPolicy::default(),
) )
.await .await
.expect("Failed to build cache layer."); .expect("Failed to build cache layer.");