Compare commits

...

8 commits

Author SHA1 Message Date
Shaswat Raj 4d5445ca99
Merge 588012a8e8 into b113262357 2025-04-09 10:42:12 +05:30
Firstyear b113262357
Improve token handling ()
It was possible that a token could be updated in a way that caused
existing cached information to be lost if an event was delayed
in it's write to the user token.

To prevent this, the writes to user tokens now require the HsmLock
to be held, and refresh the token just ahead of writing to ensure
that these data can't be lost. The benefit to this approach is that
readers remain unblocked by a writer.
2025-04-09 14:49:06 +10:00
dependabot[bot] d025e8fff0
Bump tokio from 1.44.1 to 1.44.2 in the cargo group ()
Bumps the cargo group with 1 update: [tokio](https://github.com/tokio-rs/tokio).


Updates `tokio` from 1.44.1 to 1.44.2
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.1...tokio-1.44.2)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.44.2
  dependency-type: direct:production
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-09 09:39:19 +10:00
Firstyear aee9ed05f3
Update fs4 and improve klock handling () 2025-04-08 05:04:26 +00:00
James Hodgkinson 5458b13398
Less footguns () 2025-04-08 04:48:53 +00:00
Firstyear 94b6287e27
Unify unix config parser ()
* Unify unix config parser
* Document the various structs
* Compiler Update
2025-04-08 14:21:26 +10:00
dependabot[bot] b6813a11d3
Bump openssl from 0.10.71 to 0.10.72 in the cargo group ()
Bumps the cargo group with 1 update: [openssl](https://github.com/sfackler/rust-openssl).


Updates `openssl` from 0.10.71 to 0.10.72
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.72
  dependency-type: direct:production
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-08 01:31:20 +00:00
sh20raj 588012a8e8 Implemented a new overrides.css file that allows administrators to add custom styles that will be loaded after the main stylesheet. This provides a clean and maintainable way for administrators to customize the UI appearance without modifying core stylesheets.
Key changes:

- Created server/core/static/overrides.css for custom style overrides
- Added comments explaining the purpose and usage of the file
- Ensured the file loads after main stylesheet to properly override default styles
2025-02-26 11:07:43 +05:30
26 changed files with 780 additions and 673 deletions

22
Cargo.lock generated
View file

@ -1600,12 +1600,12 @@ dependencies = [
[[package]]
name = "fs4"
version = "0.12.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c29c30684418547d476f0b48e84f4821639119c483b1eccd566c8cd0cd05f521"
checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4"
dependencies = [
"rustix 0.38.44",
"windows-sys 0.52.0",
"rustix 1.0.3",
"windows-sys 0.59.0",
]
[[package]]
@ -3064,6 +3064,8 @@ dependencies = [
"futures",
"kanidm_build_profiles",
"kanidm_proto",
"kanidm_utils_users",
"selinux",
"serde",
"serde_json",
"serde_with",
@ -3955,9 +3957,9 @@ checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b"
[[package]]
name = "openssl"
version = "0.10.71"
version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
dependencies = [
"bitflags 2.9.0",
"cfg-if",
@ -3987,9 +3989,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.106"
version = "0.9.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
dependencies = [
"cc",
"libc",
@ -5656,9 +5658,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.44.1"
version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"backtrace",
"bytes",

View file

@ -173,7 +173,7 @@ dhat = "0.3.3"
dyn-clone = "^1.0.17"
fernet = "^0.2.1"
filetime = "^0.2.24"
fs4 = "^0.12.0"
fs4 = "^0.13.0"
futures = "^0.3.31"
futures-util = { version = "^0.3.30", features = ["sink"] }
gix = { version = "0.64.0", default-features = false }
@ -210,7 +210,7 @@ notify-debouncer-full = { version = "0.5" }
num_enum = "^0.5.11"
oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false }
openssl-sys = "^0.9"
openssl = "^0.10.70"
openssl = "^0.10.72"
opentelemetry = { version = "0.27.0" }
opentelemetry_api = { version = "0.27.0", features = ["logs", "metrics"] }
@ -268,7 +268,7 @@ tempfile = "3.15.0"
testkit-macros = { path = "./server/testkit-macros" }
time = { version = "^0.3.36", features = ["formatting", "local-offset"] }
tokio = "^1.43.0"
tokio = "^1.44.2"
tokio-openssl = "^0.6.5"
tokio-util = "^0.7.13"

View file

@ -8,7 +8,7 @@ const MD5_TRANSPOSE: &[u8] = b"\x0c\x06\x00\x0d\x07\x01\x0e\x08\x02\x0f\x09\x03\
const CRYPT_HASH64: &[u8] = b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
pub fn md5_sha2_hash64_encode(bs: &[u8]) -> String {
let ngroups = (bs.len() + 2) / 3;
let ngroups = bs.len().div_ceil(3);
let mut out = String::with_capacity(ngroups * 4);
for g in 0..ngroups {
let mut g_idx = g * 3;

View file

@ -21,6 +21,8 @@ ${SUDOCMD} apt-get update &&
cmake \
build-essential \
jq \
lld \
clang \
tpm-udev
if [ -z "${PACKAGING}" ]; then
@ -73,10 +75,6 @@ if [ -z "$(which cargo)" ]; then
ERROR=1
fi
if [ $ERROR -eq 0 ] && [ -z "$(which cross)" ]; then
echo "You don't have cross installed! Installing it now..."
cargo install -f cross
fi
if [ $ERROR -eq 0 ] && [ -z "$(which cargo-deb)" ]; then
echo "You don't have cargo-deb installed! Installing it now..."
cargo install -f cargo-deb

View file

@ -0,0 +1,3 @@
/* Custom stylesheet overrides */
/* Administrators can add custom styles here */
/* This file will be loaded after the main stylesheet */

View file

@ -465,13 +465,13 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
return ExitCode::FAILURE;
}
match &opt.commands {
let lock_was_setup = match &opt.commands {
// we aren't going to touch the DB so we can carry on
KanidmdOpt::ShowReplicationCertificate { .. }
| KanidmdOpt::RenewReplicationCertificate { .. }
| KanidmdOpt::RefreshReplicationConsumer { .. }
| KanidmdOpt::RecoverAccount { .. }
| KanidmdOpt::HealthCheck(_) => (),
| KanidmdOpt::HealthCheck(_) => None,
_ => {
// Okay - Lets now create our lock and go.
#[allow(clippy::expect_used)]
@ -482,24 +482,53 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
let flock = match File::create(&klock_path) {
Ok(flock) => flock,
Err(e) => {
error!("ERROR: Refusing to start - unable to create kanidmd exclusive lock at {} - {:?}", klock_path.display(), e);
Err(err) => {
error!(
"ERROR: Refusing to start - unable to create kanidmd exclusive lock at {}",
klock_path.display()
);
error!(?err);
return ExitCode::FAILURE;
}
};
match flock.try_lock_exclusive() {
Ok(()) => debug!("Acquired kanidm exclusive lock"),
Err(e) => {
error!("ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {} - {:?}", klock_path.display(), e);
Ok(true) => debug!("Acquired kanidm exclusive lock"),
Ok(false) => {
error!(
"ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {}",
klock_path.display()
);
error!("Is another kanidmd process running?");
return ExitCode::FAILURE;
}
Err(err) => {
error!(
"ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {}",
klock_path.display()
);
error!(?err);
return ExitCode::FAILURE;
}
};
Some(klock_path)
}
};
let result_code = kanidm_main(config, opt).await;
if let Some(klock_path) = lock_was_setup {
if let Err(reason) = std::fs::remove_file(&klock_path) {
warn!(
?reason,
"WARNING: Unable to clean up kanidmd exclusive lock at {}",
klock_path.display()
);
}
}
kanidm_main(config, opt).await
result_code
}
fn main() -> ExitCode {

View file

@ -165,10 +165,10 @@ impl ReplEntryV1 {
// but for now, if it's an empty set in any capacity, we map
// to None and just send the Cid since they have the same result
// on how the entry/attr state looks at each end.
if maybe.len() > 0 {
Some(maybe.to_db_valueset_v2())
} else {
if maybe.is_empty() {
None
} else {
Some(maybe.to_db_valueset_v2())
}
);
@ -298,10 +298,10 @@ impl ReplIncrementalEntryV1 {
let live_attr = live_attrs.get(attr_name);
let cid = cid.into();
let attr = live_attr.and_then(|maybe| {
if maybe.len() > 0 {
Some(maybe.to_db_valueset_v2())
} else {
if maybe.is_empty() {
None
} else {
Some(maybe.to_db_valueset_v2())
}
});

View file

@ -83,7 +83,7 @@ impl ActorReader {
// Is this a design flaw? We probably need to know what the state was that we
// requested to move to?
match (&self.state, action, result) {
(State::Unauthenticated { .. }, TransitionAction::Login, TransitionResult::Ok) => {
(State::Unauthenticated, TransitionAction::Login, TransitionResult::Ok) => {
self.state = State::Authenticated;
}
(State::Authenticated, TransitionAction::ReadSelfMemberOf, TransitionResult::Ok) => {

View file

@ -14,6 +14,8 @@ repository = { workspace = true }
[features]
default = ["unix"]
unix = []
selinux = ["dep:selinux"]
tpm = []
[lib]
name = "kanidm_unix_common"
@ -35,6 +37,11 @@ tokio-util = { workspace = true, features = ["codec"] }
toml = { workspace = true }
tracing = { workspace = true }
selinux = { workspace = true, optional = true }
[target.'cfg(not(target_family = "windows"))'.dependencies]
kanidm_utils_users = { workspace = true }
[build-dependencies]
kanidm_build_profiles = { workspace = true }

View file

@ -26,3 +26,6 @@ pub mod unix_config;
pub mod unix_passwd;
#[cfg(target_family = "unix")]
pub mod unix_proto;
#[cfg(all(target_family = "unix", feature = "selinux"))]
pub mod selinux_util;

View file

@ -1,13 +1,24 @@
//! This is configuration definitions and parser for the various unix integration
//! tools and services. This needs to support a number of use cases like pam/nss
//! modules parsing the config quickly and the unix daemon which has to connect to
//! various backend sources.
//!
//! To achieve this the configuration has two main sections - the configuration
//! specification which will be parsed by the tools, then the configuration as
//! relevant to that tool.
use std::env;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::{ErrorKind, Read};
use std::path::Path;
use std::path::{Path, PathBuf};
#[cfg(all(target_family = "unix", feature = "selinux"))]
use crate::selinux_util;
use crate::unix_passwd::UnixIntegrationError;
use serde::Deserialize;
use crate::constants::*;
use serde::Deserialize;
#[derive(Debug, Copy, Clone)]
pub enum HomeAttr {
@ -49,35 +60,539 @@ impl Display for UidAttr {
}
}
#[derive(Debug, Clone, Default)]
pub enum HsmType {
#[cfg_attr(not(feature = "tpm"), default)]
Soft,
#[cfg_attr(feature = "tpm", default)]
TpmIfPossible,
Tpm,
}
impl Display for HsmType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
HsmType::Soft => write!(f, "Soft"),
HsmType::TpmIfPossible => write!(f, "Tpm if possible"),
HsmType::Tpm => write!(f, "Tpm"),
}
}
}
// Allowed as the large enum is only short lived at startup to the true config
#[allow(clippy::large_enum_variant)]
// This bit of magic lets us deserialise the old config and the new versions.
#[derive(Debug, Deserialize)]
struct ConfigInt {
#[serde(untagged)]
enum ConfigUntagged {
Versioned(ConfigVersion),
Legacy(ConfigInt),
}
#[derive(Debug, Deserialize)]
#[serde(tag = "version")]
enum ConfigVersion {
#[serde(rename = "2")]
V2 {
#[serde(flatten)]
values: ConfigV2,
},
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
/// This is the version 2 of the JSON configuration specification for the unixd suite.
struct ConfigV2 {
cache_db_path: Option<String>,
sock_path: Option<String>,
task_sock_path: Option<String>,
cache_timeout: Option<u64>,
default_shell: Option<String>,
home_prefix: Option<String>,
home_mount_prefix: Option<String>,
home_attr: Option<String>,
home_alias: Option<String>,
use_etc_skel: Option<bool>,
uid_attr_map: Option<String>,
gid_attr_map: Option<String>,
selinux: Option<bool>,
hsm_pin_path: Option<String>,
hsm_type: Option<String>,
tpm_tcti_name: Option<String>,
kanidm: Option<KanidmConfigV2>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct GroupMap {
pub local: String,
pub with: String,
}
#[derive(Debug, Deserialize)]
struct KanidmConfigV2 {
conn_timeout: Option<u64>,
request_timeout: Option<u64>,
pam_allowed_login_groups: Option<Vec<String>>,
#[serde(default)]
map_group: Vec<GroupMap>,
}
#[derive(Debug, Deserialize)]
/// This is the version 1 of the JSON configuration specification for the unixd suite.
struct ConfigInt {
db_path: Option<String>,
sock_path: Option<String>,
task_sock_path: Option<String>,
conn_timeout: Option<u64>,
request_timeout: Option<u64>,
cache_timeout: Option<u64>,
pam_allowed_login_groups: Option<Vec<String>>,
default_shell: Option<String>,
home_prefix: Option<String>,
home_mount_prefix: Option<String>,
home_attr: Option<String>,
home_alias: Option<String>,
use_etc_skel: Option<bool>,
uid_attr_map: Option<String>,
gid_attr_map: Option<String>,
selinux: Option<bool>,
#[serde(default)]
allow_local_account_override: Vec<String>,
hsm_pin_path: Option<String>,
hsm_type: Option<String>,
tpm_tcti_name: Option<String>,
// Detect and warn on values in these places - this is to catch
// when someone is using a v2 value on a v1 config.
#[serde(default)]
cache_db_path: Option<toml::value::Value>,
#[serde(default)]
kanidm: Option<toml::value::Value>,
}
// ========================================================================
#[derive(Debug)]
/// This is the parsed Kanidm provider configuration that the Unixd resolver
/// will use to connect to Kanidm.
pub struct KanidmConfig {
pub conn_timeout: u64,
pub request_timeout: u64,
pub pam_allowed_login_groups: Vec<String>,
pub map_group: Vec<GroupMap>,
}
#[derive(Debug)]
pub struct KanidmUnixdConfig {
/// This is the parsed configuration for the Unixd resolver.
pub struct UnixdConfig {
pub cache_db_path: String,
pub sock_path: String,
pub task_sock_path: String,
pub cache_timeout: u64,
pub unix_sock_timeout: u64,
pub default_shell: String,
pub home_prefix: PathBuf,
pub home_mount_prefix: Option<PathBuf>,
pub home_attr: HomeAttr,
pub home_alias: Option<HomeAttr>,
pub use_etc_skel: bool,
pub uid_attr_map: UidAttr,
pub gid_attr_map: UidAttr,
pub selinux: bool,
pub hsm_type: HsmType,
pub hsm_pin_path: String,
pub tpm_tcti_name: String,
pub kanidm_config: Option<KanidmConfig>,
}
impl Default for UnixdConfig {
fn default() -> Self {
UnixdConfig::new()
}
}
impl Display for UnixdConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "cache_db_path: {}", &self.cache_db_path)?;
writeln!(f, "sock_path: {}", self.sock_path)?;
writeln!(f, "task_sock_path: {}", self.task_sock_path)?;
writeln!(f, "unix_sock_timeout: {}", self.unix_sock_timeout)?;
writeln!(f, "cache_timeout: {}", self.cache_timeout)?;
writeln!(f, "default_shell: {}", self.default_shell)?;
writeln!(f, "home_prefix: {:?}", self.home_prefix)?;
match self.home_mount_prefix.as_deref() {
Some(val) => writeln!(f, "home_mount_prefix: {:?}", val)?,
None => writeln!(f, "home_mount_prefix: unset")?,
}
writeln!(f, "home_attr: {}", self.home_attr)?;
match self.home_alias {
Some(val) => writeln!(f, "home_alias: {}", val)?,
None => writeln!(f, "home_alias: unset")?,
}
writeln!(f, "uid_attr_map: {}", self.uid_attr_map)?;
writeln!(f, "gid_attr_map: {}", self.gid_attr_map)?;
writeln!(f, "hsm_type: {}", self.hsm_type)?;
writeln!(f, "tpm_tcti_name: {}", self.tpm_tcti_name)?;
writeln!(f, "selinux: {}", self.selinux)?;
if let Some(kconfig) = &self.kanidm_config {
writeln!(f, "kanidm: enabled")?;
writeln!(
f,
"kanidm pam_allowed_login_groups: {:#?}",
kconfig.pam_allowed_login_groups
)?;
writeln!(f, "kanidm conn_timeout: {}", kconfig.conn_timeout)?;
writeln!(f, "kanidm request_timeout: {}", kconfig.request_timeout)?;
} else {
writeln!(f, "kanidm: disabled")?;
};
Ok(())
}
}
impl UnixdConfig {
pub fn new() -> Self {
let cache_db_path = match env::var("KANIDM_CACHE_DB_PATH") {
Ok(val) => val,
Err(_) => DEFAULT_CACHE_DB_PATH.into(),
};
let hsm_pin_path = match env::var("KANIDM_HSM_PIN_PATH") {
Ok(val) => val,
Err(_) => DEFAULT_HSM_PIN_PATH.into(),
};
UnixdConfig {
cache_db_path,
sock_path: DEFAULT_SOCK_PATH.to_string(),
task_sock_path: DEFAULT_TASK_SOCK_PATH.to_string(),
unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
cache_timeout: DEFAULT_CACHE_TIMEOUT,
default_shell: DEFAULT_SHELL.to_string(),
home_prefix: DEFAULT_HOME_PREFIX.into(),
home_mount_prefix: None,
home_attr: DEFAULT_HOME_ATTR,
home_alias: DEFAULT_HOME_ALIAS,
use_etc_skel: DEFAULT_USE_ETC_SKEL,
uid_attr_map: DEFAULT_UID_ATTR_MAP,
gid_attr_map: DEFAULT_GID_ATTR_MAP,
selinux: DEFAULT_SELINUX,
hsm_pin_path,
hsm_type: HsmType::default(),
tpm_tcti_name: DEFAULT_TPM_TCTI_NAME.to_string(),
kanidm_config: None,
}
}
pub fn read_options_from_optional_config<P: AsRef<Path> + std::fmt::Debug>(
self,
config_path: P,
) -> Result<Self, UnixIntegrationError> {
debug!("Attempting to load configuration from {:#?}", &config_path);
let mut f = match File::open(&config_path) {
Ok(f) => {
debug!("Successfully opened configuration file {:#?}", &config_path);
f
}
Err(e) => {
match e.kind() {
ErrorKind::NotFound => {
debug!(
"Configuration file {:#?} not found, skipping.",
&config_path
);
}
ErrorKind::PermissionDenied => {
warn!(
"Permission denied loading configuration file {:#?}, skipping.",
&config_path
);
}
_ => {
debug!(
"Unable to open config file {:#?} [{:?}], skipping ...",
&config_path, e
);
}
};
return Ok(self);
}
};
let mut contents = String::new();
f.read_to_string(&mut contents).map_err(|e| {
error!("{:?}", e);
UnixIntegrationError
})?;
let config: ConfigUntagged = toml::from_str(contents.as_str()).map_err(|e| {
error!("{:?}", e);
UnixIntegrationError
})?;
match config {
ConfigUntagged::Legacy(config) => self.apply_from_config_legacy(config),
ConfigUntagged::Versioned(ConfigVersion::V2 { values }) => {
self.apply_from_config_v2(values)
}
}
}
fn apply_from_config_legacy(self, config: ConfigInt) -> Result<Self, UnixIntegrationError> {
if config.kanidm.is_some() || config.cache_db_path.is_some() {
error!("You are using version=\"2\" options in a legacy config. THESE WILL NOT WORK.");
return Err(UnixIntegrationError);
}
let map_group = config
.allow_local_account_override
.iter()
.map(|name| GroupMap {
local: name.clone(),
with: name.clone(),
})
.collect();
let kanidm_config = Some(KanidmConfig {
conn_timeout: config.conn_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT),
request_timeout: config.request_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT * 2),
pam_allowed_login_groups: config.pam_allowed_login_groups.unwrap_or_default(),
map_group,
});
// Now map the values into our config.
Ok(UnixdConfig {
cache_db_path: config.db_path.unwrap_or(self.cache_db_path),
sock_path: config.sock_path.unwrap_or(self.sock_path),
task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
default_shell: config.default_shell.unwrap_or(self.default_shell),
home_prefix: config
.home_prefix
.map(|p| p.into())
.unwrap_or(self.home_prefix.clone()),
home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
home_attr: config
.home_attr
.and_then(|v| match v.as_str() {
"uuid" => Some(HomeAttr::Uuid),
"spn" => Some(HomeAttr::Spn),
"name" => Some(HomeAttr::Name),
_ => {
warn!("Invalid home_attr configured, using default ...");
None
}
})
.unwrap_or(self.home_attr),
home_alias: config
.home_alias
.and_then(|v| match v.as_str() {
"none" => Some(None),
"uuid" => Some(Some(HomeAttr::Uuid)),
"spn" => Some(Some(HomeAttr::Spn)),
"name" => Some(Some(HomeAttr::Name)),
_ => {
warn!("Invalid home_alias configured, using default ...");
None
}
})
.unwrap_or(self.home_alias),
use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
uid_attr_map: config
.uid_attr_map
.and_then(|v| match v.as_str() {
"spn" => Some(UidAttr::Spn),
"name" => Some(UidAttr::Name),
_ => {
warn!("Invalid uid_attr_map configured, using default ...");
None
}
})
.unwrap_or(self.uid_attr_map),
gid_attr_map: config
.gid_attr_map
.and_then(|v| match v.as_str() {
"spn" => Some(UidAttr::Spn),
"name" => Some(UidAttr::Name),
_ => {
warn!("Invalid gid_attr_map configured, using default ...");
None
}
})
.unwrap_or(self.gid_attr_map),
selinux: match config.selinux.unwrap_or(self.selinux) {
#[cfg(all(target_family = "unix", feature = "selinux"))]
true => selinux_util::supported(),
_ => false,
},
hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
hsm_type: config
.hsm_type
.and_then(|v| match v.as_str() {
"soft" => Some(HsmType::Soft),
"tpm_if_possible" => Some(HsmType::TpmIfPossible),
"tpm" => Some(HsmType::Tpm),
_ => {
warn!("Invalid hsm_type configured, using default ...");
None
}
})
.unwrap_or(self.hsm_type),
tpm_tcti_name: config
.tpm_tcti_name
.unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
kanidm_config,
})
}
fn apply_from_config_v2(self, config: ConfigV2) -> Result<Self, UnixIntegrationError> {
let kanidm_config = if let Some(kconfig) = config.kanidm {
match &kconfig.pam_allowed_login_groups {
None => {
error!("You have a 'kanidm' section in the config but an empty pam_allowed_login_groups set. USERS CANNOT AUTH.")
}
Some(groups) => {
if groups.is_empty() {
error!("You have a 'kanidm' section in the config but an empty pam_allowed_login_groups set. USERS CANNOT AUTH.");
}
}
}
Some(KanidmConfig {
conn_timeout: kconfig.conn_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT),
request_timeout: kconfig.request_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT * 2),
pam_allowed_login_groups: kconfig.pam_allowed_login_groups.unwrap_or_default(),
map_group: kconfig.map_group,
})
} else {
error!(
"You are using a version 2 config without a 'kanidm' section. USERS CANNOT AUTH."
);
None
};
// Now map the values into our config.
Ok(UnixdConfig {
cache_db_path: config.cache_db_path.unwrap_or(self.cache_db_path),
sock_path: config.sock_path.unwrap_or(self.sock_path),
task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
default_shell: config.default_shell.unwrap_or(self.default_shell),
home_prefix: config
.home_prefix
.map(|p| p.into())
.unwrap_or(self.home_prefix.clone()),
home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
home_attr: config
.home_attr
.and_then(|v| match v.as_str() {
"uuid" => Some(HomeAttr::Uuid),
"spn" => Some(HomeAttr::Spn),
"name" => Some(HomeAttr::Name),
_ => {
warn!("Invalid home_attr configured, using default ...");
None
}
})
.unwrap_or(self.home_attr),
home_alias: config
.home_alias
.and_then(|v| match v.as_str() {
"none" => Some(None),
"uuid" => Some(Some(HomeAttr::Uuid)),
"spn" => Some(Some(HomeAttr::Spn)),
"name" => Some(Some(HomeAttr::Name)),
_ => {
warn!("Invalid home_alias configured, using default ...");
None
}
})
.unwrap_or(self.home_alias),
use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
uid_attr_map: config
.uid_attr_map
.and_then(|v| match v.as_str() {
"spn" => Some(UidAttr::Spn),
"name" => Some(UidAttr::Name),
_ => {
warn!("Invalid uid_attr_map configured, using default ...");
None
}
})
.unwrap_or(self.uid_attr_map),
gid_attr_map: config
.gid_attr_map
.and_then(|v| match v.as_str() {
"spn" => Some(UidAttr::Spn),
"name" => Some(UidAttr::Name),
_ => {
warn!("Invalid gid_attr_map configured, using default ...");
None
}
})
.unwrap_or(self.gid_attr_map),
selinux: match config.selinux.unwrap_or(self.selinux) {
#[cfg(all(target_family = "unix", feature = "selinux"))]
true => selinux_util::supported(),
_ => false,
},
hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
hsm_type: config
.hsm_type
.and_then(|v| match v.as_str() {
"soft" => Some(HsmType::Soft),
"tpm_if_possible" => Some(HsmType::TpmIfPossible),
"tpm" => Some(HsmType::Tpm),
_ => {
warn!("Invalid hsm_type configured, using default ...");
None
}
})
.unwrap_or(self.hsm_type),
tpm_tcti_name: config
.tpm_tcti_name
.unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
kanidm_config,
})
}
}
#[derive(Debug)]
/// This is the parsed configuration that will be used by pam/nss tools that need fast access to
/// only the socket and timeout information related to the resolver.
pub struct PamNssConfig {
pub sock_path: String,
// pub conn_timeout: u64,
pub unix_sock_timeout: u64,
}
impl Default for KanidmUnixdConfig {
impl Default for PamNssConfig {
fn default() -> Self {
KanidmUnixdConfig::new()
PamNssConfig::new()
}
}
impl Display for KanidmUnixdConfig {
impl Display for PamNssConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "sock_path: {}", self.sock_path)?;
writeln!(f, "unix_sock_timeout: {}", self.unix_sock_timeout)
}
}
impl KanidmUnixdConfig {
impl PamNssConfig {
pub fn new() -> Self {
KanidmUnixdConfig {
PamNssConfig {
sock_path: DEFAULT_SOCK_PATH.to_string(),
unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
}
@ -124,22 +639,45 @@ impl KanidmUnixdConfig {
UnixIntegrationError
})?;
let config: ConfigInt = toml::from_str(contents.as_str()).map_err(|e| {
let config: ConfigUntagged = toml::from_str(contents.as_str()).map_err(|e| {
error!("{:?}", e);
UnixIntegrationError
})?;
match config {
ConfigUntagged::Legacy(config) => self.apply_from_config_legacy(config),
ConfigUntagged::Versioned(ConfigVersion::V2 { values }) => {
self.apply_from_config_v2(values)
}
}
}
fn apply_from_config_legacy(self, config: ConfigInt) -> Result<Self, UnixIntegrationError> {
let unix_sock_timeout = config
.conn_timeout
.map(|v| v * 2)
.unwrap_or(self.unix_sock_timeout);
// Now map the values into our config.
Ok(KanidmUnixdConfig {
Ok(PamNssConfig {
sock_path: config.sock_path.unwrap_or(self.sock_path),
unix_sock_timeout,
})
}
fn apply_from_config_v2(self, config: ConfigV2) -> Result<Self, UnixIntegrationError> {
let kanidm_conn_timeout = config
.kanidm
.as_ref()
.and_then(|k_config| k_config.conn_timeout)
.map(|timeout| timeout * 2);
// Now map the values into our config.
Ok(PamNssConfig {
sock_path: config.sock_path.unwrap_or(self.sock_path),
unix_sock_timeout: kanidm_conn_timeout.unwrap_or(self.unix_sock_timeout),
})
}
}
#[cfg(test)]
@ -165,9 +703,12 @@ mod tests {
if filename.starts_with("unixd") {
print!("Checking that {} parses as a valid config...", filename);
KanidmUnixdConfig::new()
UnixdConfig::new()
.read_options_from_optional_config(file.path())
.expect("Failed to parse");
.inspect_err(|e| {
println!("Failed to parse: {:?}", e);
})
.expect("Failed to parse!");
println!("OK");
}
}

View file

@ -1,5 +1,5 @@
use kanidm_unix_common::client_sync::DaemonClientBlocking;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_config::PamNssConfig;
use kanidm_unix_common::unix_passwd::{
read_etc_group_file, read_etc_passwd_file, EtcGroup, EtcUser,
};
@ -36,7 +36,7 @@ impl RequestOptions {
fn connect_to_daemon(self) -> Source {
match self {
RequestOptions::Main { config_path } => {
let maybe_client = KanidmUnixdConfig::new()
let maybe_client = PamNssConfig::new()
.read_options_from_optional_config(config_path)
.ok()
.and_then(|cfg| {

View file

@ -2,7 +2,7 @@ use crate::constants::PamResultCode;
use crate::module::PamResult;
use crate::pam::ModuleOptions;
use kanidm_unix_common::client_sync::DaemonClientBlocking;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_config::PamNssConfig;
use kanidm_unix_common::unix_passwd::{
read_etc_passwd_file, read_etc_shadow_file, EtcShadow, EtcUser,
};
@ -44,7 +44,7 @@ impl RequestOptions {
fn connect_to_daemon(self) -> Source {
match self {
RequestOptions::Main { config_path } => {
let maybe_client = KanidmUnixdConfig::new()
let maybe_client = PamNssConfig::new()
.read_options_from_optional_config(config_path)
.ok()
.and_then(|cfg| {

View file

@ -36,7 +36,7 @@ use std::convert::TryFrom;
use std::ffi::CStr;
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_config::PamNssConfig;
use crate::core::{self, RequestOptions};
use crate::pam::constants::*;
@ -50,8 +50,8 @@ use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt;
use tracing_subscriber::prelude::*;
pub fn get_cfg() -> Result<KanidmUnixdConfig, PamResultCode> {
KanidmUnixdConfig::new()
pub fn get_cfg() -> Result<PamNssConfig, PamResultCode> {
PamNssConfig::new()
.read_options_from_optional_config(DEFAULT_CONFIG_PATH)
.map_err(|_| PamResultCode::PAM_SERVICE_ERR)
}

View file

@ -14,8 +14,8 @@ repository = { workspace = true }
[features]
default = ["unix"]
unix = []
selinux = ["dep:selinux"]
tpm = ["kanidm-hsm-crypto/tpm"]
selinux = ["dep:selinux", "kanidm_unix_common/selinux"]
tpm = ["kanidm-hsm-crypto/tpm", "kanidm_unix_common/tpm"]
[[bin]]
name = "kanidm_unixd"

View file

@ -18,7 +18,7 @@ use std::process::ExitCode;
use clap::Parser;
use kanidm_unix_common::client::DaemonClient;
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_config::PamNssConfig;
use kanidm_unix_common::unix_proto::{
ClientRequest, ClientResponse, PamAuthRequest, PamAuthResponse, PamServiceInfo,
};
@ -28,8 +28,7 @@ include!("../opt/tool.rs");
macro_rules! setup_client {
() => {{
let Ok(cfg) =
KanidmUnixdConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH)
let Ok(cfg) = PamNssConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH)
else {
error!("Failed to parse {}", DEFAULT_CONFIG_PATH);
return ExitCode::FAILURE;

View file

@ -19,7 +19,7 @@ use std::process::ExitCode;
use clap::Parser;
use kanidm_unix_common::client::DaemonClient;
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::unix_config::KanidmUnixdConfig;
use kanidm_unix_common::unix_config::PamNssConfig;
use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse};
include!("../opt/ssh_authorizedkeys.rs");
@ -44,8 +44,7 @@ async fn main() -> ExitCode {
debug!("Starting authorized keys tool ...");
let cfg = match KanidmUnixdConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH)
{
let cfg = match PamNssConfig::new().read_options_from_optional_config(DEFAULT_CONFIG_PATH) {
Ok(c) => c,
Err(e) => {
error!("Failed to parse {}: {:?}", DEFAULT_CONFIG_PATH, e);

View file

@ -18,6 +18,7 @@ use kanidm_hsm_crypto::{soft::SoftTpm, AuthValue, BoxedDynTpm, Tpm};
use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
use kanidm_proto::internal::OperationError;
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::unix_config::{HsmType, UnixdConfig};
use kanidm_unix_common::unix_passwd::EtcDb;
use kanidm_unix_common::unix_proto::{
ClientRequest, ClientResponse, TaskRequest, TaskRequestFrame, TaskResponse,
@ -27,7 +28,6 @@ use kanidm_unix_resolver::idprovider::interface::IdProvider;
use kanidm_unix_resolver::idprovider::kanidm::KanidmProvider;
use kanidm_unix_resolver::idprovider::system::SystemProvider;
use kanidm_unix_resolver::resolver::Resolver;
use kanidm_unix_resolver::unix_config::{HsmType, UnixdConfig};
use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
use libc::umask;
use sketching::tracing::span;

View file

@ -13,11 +13,11 @@
use bytes::{BufMut, BytesMut};
use futures::{SinkExt, StreamExt};
use kanidm_unix_common::constants::DEFAULT_CONFIG_PATH;
use kanidm_unix_common::unix_config::UnixdConfig;
use kanidm_unix_common::unix_passwd::{parse_etc_group, parse_etc_passwd, parse_etc_shadow, EtcDb};
use kanidm_unix_common::unix_proto::{
HomeDirectoryInfo, TaskRequest, TaskRequestFrame, TaskResponse,
};
use kanidm_unix_resolver::unix_config::UnixdConfig;
use kanidm_utils_users::{get_effective_gid, get_effective_uid};
use libc::{lchown, umask};
use notify_debouncer_full::notify::RecommendedWatcher;
@ -43,7 +43,7 @@ use tokio_util::codec::{Decoder, Encoder, Framed};
use walkdir::WalkDir;
#[cfg(all(target_family = "unix", feature = "selinux"))]
use kanidm_unix_resolver::selinux_util;
use kanidm_unix_common::selinux_util;
struct TaskCodec;

View file

@ -194,7 +194,8 @@ impl Into<PamAuthResponse> for AuthRequest {
}
pub enum AuthResult {
Success { token: UserToken },
Success,
SuccessUpdate { new_token: UserToken },
Denied,
Next(AuthRequest),
}
@ -251,6 +252,7 @@ pub trait IdProvider {
async fn unix_user_online_auth_step(
&self,
_account_id: &str,
_current_token: Option<&UserToken>,
_cred_handler: &mut AuthCredHandler,
_pam_next_req: PamAuthRequest,
_tpm: &mut tpm::BoxedDynTpm,
@ -290,7 +292,8 @@ pub trait IdProvider {
// TPM key.
async fn unix_user_offline_auth_step(
&self,
_token: &UserToken,
_current_token: Option<&UserToken>,
_session_token: &UserToken,
_cred_handler: &mut AuthCredHandler,
_pam_next_req: PamAuthRequest,
_tpm: &mut tpm::BoxedDynTpm,

View file

@ -1,24 +1,22 @@
use crate::db::KeyStoreTxn;
use crate::unix_config::{GroupMap, KanidmConfig};
use async_trait::async_trait;
use hashbrown::HashMap;
use kanidm_client::{ClientError, KanidmClient, StatusCode};
use kanidm_proto::internal::OperationError;
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
use std::collections::BTreeSet;
use std::time::{Duration, SystemTime};
use tokio::sync::{broadcast, Mutex};
use kanidm_lib_crypto::CryptoPolicy;
use kanidm_lib_crypto::DbPasswordV1;
use kanidm_lib_crypto::Password;
use super::interface::{
tpm::{self, HmacKey, Tpm},
AuthCredHandler, AuthRequest, AuthResult, GroupToken, GroupTokenState, Id, IdProvider,
IdpError, ProviderOrigin, UserToken, UserTokenState,
};
use crate::db::KeyStoreTxn;
use async_trait::async_trait;
use hashbrown::HashMap;
use kanidm_client::{ClientError, KanidmClient, StatusCode};
use kanidm_lib_crypto::CryptoPolicy;
use kanidm_lib_crypto::DbPasswordV1;
use kanidm_lib_crypto::Password;
use kanidm_proto::internal::OperationError;
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
use kanidm_unix_common::unix_config::{GroupMap, KanidmConfig};
use kanidm_unix_common::unix_proto::PamAuthRequest;
use std::collections::BTreeSet;
use std::time::{Duration, SystemTime};
use tokio::sync::{broadcast, Mutex};
const KANIDM_HMAC_KEY: &str = "kanidm-hmac-key";
const KANIDM_PWV1_KEY: &str = "kanidm-pw-v1";
@ -57,8 +55,6 @@ impl KanidmProvider {
tpm: &mut tpm::BoxedDynTpm,
machine_key: &tpm::MachineKey,
) -> Result<Self, IdpError> {
// FUTURE: Randomised jitter on next check at startup.
// Initially retrieve our HMAC key.
let loadable_hmac_key: Option<tpm::LoadableHmacKey> = keystore
.get_tagged_hsm_key(KANIDM_HMAC_KEY)
@ -250,13 +246,25 @@ impl KanidmProviderInternal {
// Proceed
CacheState::Online => true,
CacheState::OfflineNextCheck(at_time) if now >= at_time => {
// Attempt online. If fails, return token.
self.attempt_online(tpm, now).await
}
CacheState::OfflineNextCheck(_) | CacheState::Offline => false,
}
}
#[instrument(level = "debug", skip_all)]
async fn check_online_right_meow(
&mut self,
tpm: &mut tpm::BoxedDynTpm,
now: SystemTime,
) -> bool {
match self.state {
CacheState::Online => true,
CacheState::OfflineNextCheck(_) => self.attempt_online(tpm, now).await,
CacheState::Offline => false,
}
}
#[instrument(level = "debug", skip_all)]
async fn attempt_online(&mut self, _tpm: &mut tpm::BoxedDynTpm, now: SystemTime) -> bool {
let mut max_attempts = 3;
@ -297,7 +305,7 @@ impl IdProvider for KanidmProvider {
async fn attempt_online(&self, tpm: &mut tpm::BoxedDynTpm, now: SystemTime) -> bool {
let mut inner = self.inner.lock().await;
inner.check_online(tpm, now).await
inner.check_online_right_meow(tpm, now).await
}
async fn mark_next_check(&self, now: SystemTime) {
@ -433,6 +441,7 @@ impl IdProvider for KanidmProvider {
async fn unix_user_online_auth_step(
&self,
account_id: &str,
current_token: Option<&UserToken>,
cred_handler: &mut AuthCredHandler,
pam_next_req: PamAuthRequest,
tpm: &mut tpm::BoxedDynTpm,
@ -451,15 +460,23 @@ impl IdProvider for KanidmProvider {
match auth_result {
Ok(Some(n_tok)) => {
let mut token = UserToken::from(n_tok);
token.kanidm_update_cached_password(
let mut new_token = UserToken::from(n_tok);
// Update any keys that may have been in the db in the current
// token.
if let Some(previous_token) = current_token {
new_token.extra_keys = previous_token.extra_keys.clone();
}
// Set any new keys that are relevant from this authentication
new_token.kanidm_update_cached_password(
&inner.crypto_policy,
cred.as_str(),
tpm,
&inner.hmac_key,
);
Ok(AuthResult::Success { token })
Ok(AuthResult::SuccessUpdate { new_token })
}
Ok(None) => {
// TODO: i'm not a huge fan of this rn, but currently the way we handle
@ -554,7 +571,8 @@ impl IdProvider for KanidmProvider {
async fn unix_user_offline_auth_step(
&self,
token: &UserToken,
current_token: Option<&UserToken>,
session_token: &UserToken,
cred_handler: &mut AuthCredHandler,
pam_next_req: PamAuthRequest,
tpm: &mut tpm::BoxedDynTpm,
@ -563,11 +581,13 @@ impl IdProvider for KanidmProvider {
(AuthCredHandler::Password, PamAuthRequest::Password { cred }) => {
let inner = self.inner.lock().await;
if token.kanidm_check_cached_password(cred.as_str(), tpm, &inner.hmac_key) {
if session_token.kanidm_check_cached_password(cred.as_str(), tpm, &inner.hmac_key) {
// Ensure we have either the latest token, or if none, at least the session token.
let new_token = current_token.unwrap_or(session_token).clone();
// TODO: We can update the token here and then do lockouts.
Ok(AuthResult::Success {
token: token.clone(),
})
Ok(AuthResult::SuccessUpdate { new_token })
} else {
Ok(AuthResult::Denied)
}

View file

@ -23,7 +23,3 @@ pub mod db;
pub mod idprovider;
#[cfg(target_family = "unix")]
pub mod resolver;
#[cfg(all(target_family = "unix", feature = "selinux"))]
pub mod selinux_util;
#[cfg(target_family = "unix")]
pub mod unix_config;

View file

@ -1,18 +1,4 @@
// use async_trait::async_trait;
use hashbrown::HashMap;
use std::fmt::Display;
use std::num::NonZeroUsize;
use std::ops::DerefMut;
use std::path::{Path, PathBuf};
use std::string::ToString;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use lru::LruCache;
use time::OffsetDateTime;
use tokio::sync::Mutex;
use uuid::Uuid;
use crate::db::{Cache, Db};
use crate::idprovider::interface::{
AuthCredHandler,
@ -30,13 +16,25 @@ use crate::idprovider::interface::{
use crate::idprovider::system::{
Shadow, SystemAuthResult, SystemProvider, SystemProviderAuthInit, SystemProviderSession,
};
use crate::unix_config::{HomeAttr, UidAttr};
use hashbrown::HashMap;
use kanidm_unix_common::constants::DEFAULT_SHELL_SEARCH_PATHS;
use kanidm_unix_common::unix_config::{HomeAttr, UidAttr};
use kanidm_unix_common::unix_passwd::{EtcGroup, EtcShadow, EtcUser};
use kanidm_unix_common::unix_proto::{
HomeDirectoryInfo, NssGroup, NssUser, PamAuthRequest, PamAuthResponse, PamServiceInfo,
ProviderStatus,
};
use lru::LruCache;
use std::fmt::Display;
use std::num::NonZeroUsize;
use std::ops::DerefMut;
use std::path::{Path, PathBuf};
use std::string::ToString;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use time::OffsetDateTime;
use tokio::sync::Mutex;
use uuid::Uuid;
use kanidm_hsm_crypto::BoxedDynTpm;
@ -49,7 +47,6 @@ pub enum AuthSession {
client: Arc<dyn IdProvider + Sync + Send>,
account_id: String,
id: Id,
token: Option<Box<UserToken>>,
cred_handler: AuthCredHandler,
/// Some authentication operations may need to spawn background tasks. These tasks need
/// to know when to stop as the caller has disconnected. This receiver allows that, so
@ -61,7 +58,7 @@ pub enum AuthSession {
account_id: String,
id: Id,
client: Arc<dyn IdProvider + Sync + Send>,
token: Box<UserToken>,
session_token: Box<UserToken>,
cred_handler: AuthCredHandler,
},
System {
@ -227,7 +224,7 @@ impl Resolver {
// Attempt to search these in the db.
let mut dbtxn = self.db.write().await;
let r = dbtxn.get_account(account_id).map_err(|err| {
debug!("get_cached_usertoken {:?}", err);
debug!(?err, "get_cached_usertoken");
})?;
drop(dbtxn);
@ -320,7 +317,12 @@ impl Resolver {
}
}
async fn set_cache_usertoken(&self, token: &mut UserToken) -> Result<(), ()> {
async fn set_cache_usertoken(
&self,
token: &mut UserToken,
// This is just for proof that only one write can occur at a time.
_tpm: &mut BoxedDynTpm,
) -> Result<(), ()> {
// Set an expiry
let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
let offset = ex_time
@ -453,6 +455,22 @@ impl Resolver {
let mut hsm_lock = self.hsm.lock().await;
// We need to re-acquire the token now behind the hsmlock - this is so that
// we know that as we write the updated token, we know that no one else has
// written to this token, since we are now the only task that is allowed
// to be in a write phase.
let token = if token.is_some() {
self.get_cached_usertoken(account_id)
.await
.map(|(_expired, option_token)| option_token)
.map_err(|err| {
debug!(?err, "get_usertoken error");
})?
} else {
// Was already none, leave it that way.
None
};
let user_get_result = if let Some(tok) = token.as_ref() {
// Re-use the provider that the token is from.
match self.client_ids.get(&tok.provider) {
@ -488,12 +506,11 @@ impl Resolver {
}
};
drop(hsm_lock);
match user_get_result {
Ok(UserTokenState::Update(mut n_tok)) => {
// We have the token!
self.set_cache_usertoken(&mut n_tok).await?;
self.set_cache_usertoken(&mut n_tok, hsm_lock.deref_mut())
.await?;
Ok(Some(n_tok))
}
Ok(UserTokenState::NotFound) => {
@ -960,7 +977,6 @@ impl Resolver {
client,
account_id: account_id.to_string(),
id,
token: Some(Box::new(token)),
cred_handler,
shutdown_rx,
};
@ -981,7 +997,7 @@ impl Resolver {
account_id: account_id.to_string(),
id,
client,
token: Box::new(token),
session_token: Box::new(token),
cred_handler,
};
Ok((auth_session, next_req.into()))
@ -1024,7 +1040,6 @@ impl Resolver {
client: client.clone(),
account_id: account_id.to_string(),
id,
token: None,
cred_handler,
shutdown_rx,
};
@ -1052,19 +1067,32 @@ impl Resolver {
auth_session: &mut AuthSession,
pam_next_req: PamAuthRequest,
) -> Result<PamAuthResponse, ()> {
let mut hsm_lock = self.hsm.lock().await;
let maybe_err = match &mut *auth_session {
&mut AuthSession::Online {
ref client,
ref account_id,
id: _,
token: _,
ref id,
ref mut cred_handler,
ref shutdown_rx,
} => {
let mut hsm_lock = self.hsm.lock().await;
// This is not used in the authentication, but is so that any new
// extra keys or data on the token are updated correctly if the authentication
// requests an update. Since we hold the hsm_lock, no other task can
// update this token between now and completion of the fn.
let current_token = self
.get_cached_usertoken(id)
.await
.map(|(_expired, option_token)| option_token)
.map_err(|err| {
debug!(?err, "get_usertoken error");
})?;
let result = client
.unix_user_online_auth_step(
account_id,
current_token.as_ref(),
cred_handler,
pam_next_req,
hsm_lock.deref_mut(),
@ -1073,7 +1101,7 @@ impl Resolver {
.await;
match result {
Ok(AuthResult::Success { .. }) => {
Ok(AuthResult::SuccessUpdate { .. } | AuthResult::Success) => {
info!(?account_id, "Authentication Success");
}
Ok(AuthResult::Denied) => {
@ -1089,17 +1117,29 @@ impl Resolver {
}
&mut AuthSession::Offline {
ref account_id,
id: _,
ref id,
ref client,
ref token,
ref session_token,
ref mut cred_handler,
} => {
// This is not used in the authentication, but is so that any new
// extra keys or data on the token are updated correctly if the authentication
// requests an update. Since we hold the hsm_lock, no other task can
// update this token between now and completion of the fn.
let current_token = self
.get_cached_usertoken(id)
.await
.map(|(_expired, option_token)| option_token)
.map_err(|err| {
debug!(?err, "get_usertoken error");
})?;
// We are offline, continue. Remember, authsession should have
// *everything you need* to proceed here!
let mut hsm_lock = self.hsm.lock().await;
let result = client
.unix_user_offline_auth_step(
token,
current_token.as_ref(),
session_token,
cred_handler,
pam_next_req,
hsm_lock.deref_mut(),
@ -1107,7 +1147,7 @@ impl Resolver {
.await;
match result {
Ok(AuthResult::Success { .. }) => {
Ok(AuthResult::SuccessUpdate { .. } | AuthResult::Success) => {
info!(?account_id, "Authentication Success");
}
Ok(AuthResult::Denied) => {
@ -1158,8 +1198,13 @@ impl Resolver {
match maybe_err {
// What did the provider direct us to do next?
Ok(AuthResult::Success { mut token }) => {
self.set_cache_usertoken(&mut token).await?;
Ok(AuthResult::Success) => {
*auth_session = AuthSession::Success;
Ok(PamAuthResponse::Success)
}
Ok(AuthResult::SuccessUpdate { mut new_token }) => {
self.set_cache_usertoken(&mut new_token, hsm_lock.deref_mut())
.await?;
*auth_session = AuthSession::Success;
Ok(PamAuthResponse::Success)

View file

@ -1,538 +0,0 @@
use std::env;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::{ErrorKind, Read};
use std::path::{Path, PathBuf};
#[cfg(all(target_family = "unix", feature = "selinux"))]
use crate::selinux_util;
use kanidm_unix_common::unix_passwd::UnixIntegrationError;
pub(crate) use kanidm_unix_common::unix_config::{HomeAttr, UidAttr};
use serde::Deserialize;
use kanidm_unix_common::constants::*;
// Allowed as the large enum is only short lived at startup to the true config
#[allow(clippy::large_enum_variant)]
// This bit of magic lets us deserialise the old config and the new versions.
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum ConfigUntagged {
Versioned(ConfigVersion),
Legacy(ConfigInt),
}
#[derive(Debug, Deserialize)]
#[serde(tag = "version")]
enum ConfigVersion {
#[serde(rename = "2")]
V2 {
#[serde(flatten)]
values: ConfigV2,
},
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct ConfigV2 {
cache_db_path: Option<String>,
sock_path: Option<String>,
task_sock_path: Option<String>,
cache_timeout: Option<u64>,
default_shell: Option<String>,
home_prefix: Option<String>,
home_mount_prefix: Option<String>,
home_attr: Option<String>,
home_alias: Option<String>,
use_etc_skel: Option<bool>,
uid_attr_map: Option<String>,
gid_attr_map: Option<String>,
selinux: Option<bool>,
hsm_pin_path: Option<String>,
hsm_type: Option<String>,
tpm_tcti_name: Option<String>,
kanidm: Option<KanidmConfigV2>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct GroupMap {
pub local: String,
pub with: String,
}
#[derive(Debug, Deserialize)]
struct KanidmConfigV2 {
conn_timeout: Option<u64>,
request_timeout: Option<u64>,
pam_allowed_login_groups: Option<Vec<String>>,
#[serde(default)]
map_group: Vec<GroupMap>,
}
#[derive(Debug, Deserialize)]
struct ConfigInt {
db_path: Option<String>,
sock_path: Option<String>,
task_sock_path: Option<String>,
conn_timeout: Option<u64>,
request_timeout: Option<u64>,
cache_timeout: Option<u64>,
pam_allowed_login_groups: Option<Vec<String>>,
default_shell: Option<String>,
home_prefix: Option<String>,
home_mount_prefix: Option<String>,
home_attr: Option<String>,
home_alias: Option<String>,
use_etc_skel: Option<bool>,
uid_attr_map: Option<String>,
gid_attr_map: Option<String>,
selinux: Option<bool>,
#[serde(default)]
allow_local_account_override: Vec<String>,
hsm_pin_path: Option<String>,
hsm_type: Option<String>,
tpm_tcti_name: Option<String>,
// Detect and warn on values in these places.
#[serde(default)]
cache_db_path: Option<toml::value::Value>,
#[serde(default)]
kanidm: Option<toml::value::Value>,
}
#[derive(Debug, Clone, Default)]
pub enum HsmType {
#[cfg_attr(not(feature = "tpm"), default)]
Soft,
#[cfg_attr(feature = "tpm", default)]
TpmIfPossible,
Tpm,
}
impl Display for HsmType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
HsmType::Soft => write!(f, "Soft"),
HsmType::TpmIfPossible => write!(f, "Tpm if possible"),
HsmType::Tpm => write!(f, "Tpm"),
}
}
}
#[derive(Debug)]
pub struct UnixdConfig {
pub cache_db_path: String,
pub sock_path: String,
pub task_sock_path: String,
pub cache_timeout: u64,
pub unix_sock_timeout: u64,
pub default_shell: String,
pub home_prefix: PathBuf,
pub home_mount_prefix: Option<PathBuf>,
pub home_attr: HomeAttr,
pub home_alias: Option<HomeAttr>,
pub use_etc_skel: bool,
pub uid_attr_map: UidAttr,
pub gid_attr_map: UidAttr,
pub selinux: bool,
pub hsm_type: HsmType,
pub hsm_pin_path: String,
pub tpm_tcti_name: String,
pub kanidm_config: Option<KanidmConfig>,
}
#[derive(Debug)]
pub struct KanidmConfig {
pub conn_timeout: u64,
pub request_timeout: u64,
pub pam_allowed_login_groups: Vec<String>,
pub map_group: Vec<GroupMap>,
}
impl Default for UnixdConfig {
fn default() -> Self {
UnixdConfig::new()
}
}
impl Display for UnixdConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "cache_db_path: {}", &self.cache_db_path)?;
writeln!(f, "sock_path: {}", self.sock_path)?;
writeln!(f, "task_sock_path: {}", self.task_sock_path)?;
writeln!(f, "unix_sock_timeout: {}", self.unix_sock_timeout)?;
writeln!(f, "cache_timeout: {}", self.cache_timeout)?;
writeln!(f, "default_shell: {}", self.default_shell)?;
writeln!(f, "home_prefix: {:?}", self.home_prefix)?;
match self.home_mount_prefix.as_deref() {
Some(val) => writeln!(f, "home_mount_prefix: {:?}", val)?,
None => writeln!(f, "home_mount_prefix: unset")?,
}
writeln!(f, "home_attr: {}", self.home_attr)?;
match self.home_alias {
Some(val) => writeln!(f, "home_alias: {}", val)?,
None => writeln!(f, "home_alias: unset")?,
}
writeln!(f, "uid_attr_map: {}", self.uid_attr_map)?;
writeln!(f, "gid_attr_map: {}", self.gid_attr_map)?;
writeln!(f, "hsm_type: {}", self.hsm_type)?;
writeln!(f, "tpm_tcti_name: {}", self.tpm_tcti_name)?;
writeln!(f, "selinux: {}", self.selinux)?;
if let Some(kconfig) = &self.kanidm_config {
writeln!(f, "kanidm: enabled")?;
writeln!(
f,
"kanidm pam_allowed_login_groups: {:#?}",
kconfig.pam_allowed_login_groups
)?;
writeln!(f, "kanidm conn_timeout: {}", kconfig.conn_timeout)?;
writeln!(f, "kanidm request_timeout: {}", kconfig.request_timeout)?;
} else {
writeln!(f, "kanidm: disabled")?;
};
Ok(())
}
}
impl UnixdConfig {
pub fn new() -> Self {
let cache_db_path = match env::var("KANIDM_CACHE_DB_PATH") {
Ok(val) => val,
Err(_) => DEFAULT_CACHE_DB_PATH.into(),
};
let hsm_pin_path = match env::var("KANIDM_HSM_PIN_PATH") {
Ok(val) => val,
Err(_) => DEFAULT_HSM_PIN_PATH.into(),
};
UnixdConfig {
cache_db_path,
sock_path: DEFAULT_SOCK_PATH.to_string(),
task_sock_path: DEFAULT_TASK_SOCK_PATH.to_string(),
unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
cache_timeout: DEFAULT_CACHE_TIMEOUT,
default_shell: DEFAULT_SHELL.to_string(),
home_prefix: DEFAULT_HOME_PREFIX.into(),
home_mount_prefix: None,
home_attr: DEFAULT_HOME_ATTR,
home_alias: DEFAULT_HOME_ALIAS,
use_etc_skel: DEFAULT_USE_ETC_SKEL,
uid_attr_map: DEFAULT_UID_ATTR_MAP,
gid_attr_map: DEFAULT_GID_ATTR_MAP,
selinux: DEFAULT_SELINUX,
hsm_pin_path,
hsm_type: HsmType::default(),
tpm_tcti_name: DEFAULT_TPM_TCTI_NAME.to_string(),
kanidm_config: None,
}
}
pub fn read_options_from_optional_config<P: AsRef<Path> + std::fmt::Debug>(
self,
config_path: P,
) -> Result<Self, UnixIntegrationError> {
debug!("Attempting to load configuration from {:#?}", &config_path);
let mut f = match File::open(&config_path) {
Ok(f) => {
debug!("Successfully opened configuration file {:#?}", &config_path);
f
}
Err(e) => {
match e.kind() {
ErrorKind::NotFound => {
debug!(
"Configuration file {:#?} not found, skipping.",
&config_path
);
}
ErrorKind::PermissionDenied => {
warn!(
"Permission denied loading configuration file {:#?}, skipping.",
&config_path
);
}
_ => {
debug!(
"Unable to open config file {:#?} [{:?}], skipping ...",
&config_path, e
);
}
};
return Ok(self);
}
};
let mut contents = String::new();
f.read_to_string(&mut contents).map_err(|e| {
error!("{:?}", e);
UnixIntegrationError
})?;
let config: ConfigUntagged = toml::from_str(contents.as_str()).map_err(|e| {
error!("{:?}", e);
UnixIntegrationError
})?;
match config {
ConfigUntagged::Legacy(config) => self.apply_from_config_legacy(config),
ConfigUntagged::Versioned(ConfigVersion::V2 { values }) => {
self.apply_from_config_v2(values)
}
}
}
fn apply_from_config_legacy(self, config: ConfigInt) -> Result<Self, UnixIntegrationError> {
if config.kanidm.is_some() || config.cache_db_path.is_some() {
error!("You are using version=\"2\" options in a legacy config. THESE WILL NOT WORK.");
return Err(UnixIntegrationError);
}
let map_group = config
.allow_local_account_override
.iter()
.map(|name| GroupMap {
local: name.clone(),
with: name.clone(),
})
.collect();
let kanidm_config = Some(KanidmConfig {
conn_timeout: config.conn_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT),
request_timeout: config.request_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT * 2),
pam_allowed_login_groups: config.pam_allowed_login_groups.unwrap_or_default(),
map_group,
});
// Now map the values into our config.
Ok(UnixdConfig {
cache_db_path: config.db_path.unwrap_or(self.cache_db_path),
sock_path: config.sock_path.unwrap_or(self.sock_path),
task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
default_shell: config.default_shell.unwrap_or(self.default_shell),
home_prefix: config
.home_prefix
.map(|p| p.into())
.unwrap_or(self.home_prefix.clone()),
home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
home_attr: config
.home_attr
.and_then(|v| match v.as_str() {
"uuid" => Some(HomeAttr::Uuid),
"spn" => Some(HomeAttr::Spn),
"name" => Some(HomeAttr::Name),
_ => {
warn!("Invalid home_attr configured, using default ...");
None
}
})
.unwrap_or(self.home_attr),
home_alias: config
.home_alias
.and_then(|v| match v.as_str() {
"none" => Some(None),
"uuid" => Some(Some(HomeAttr::Uuid)),
"spn" => Some(Some(HomeAttr::Spn)),
"name" => Some(Some(HomeAttr::Name)),
_ => {
warn!("Invalid home_alias configured, using default ...");
None
}
})
.unwrap_or(self.home_alias),
use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
uid_attr_map: config
.uid_attr_map
.and_then(|v| match v.as_str() {
"spn" => Some(UidAttr::Spn),
"name" => Some(UidAttr::Name),
_ => {
warn!("Invalid uid_attr_map configured, using default ...");
None
}
})
.unwrap_or(self.uid_attr_map),
gid_attr_map: config
.gid_attr_map
.and_then(|v| match v.as_str() {
"spn" => Some(UidAttr::Spn),
"name" => Some(UidAttr::Name),
_ => {
warn!("Invalid gid_attr_map configured, using default ...");
None
}
})
.unwrap_or(self.gid_attr_map),
selinux: match config.selinux.unwrap_or(self.selinux) {
#[cfg(all(target_family = "unix", feature = "selinux"))]
true => selinux_util::supported(),
_ => false,
},
hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
hsm_type: config
.hsm_type
.and_then(|v| match v.as_str() {
"soft" => Some(HsmType::Soft),
"tpm_if_possible" => Some(HsmType::TpmIfPossible),
"tpm" => Some(HsmType::Tpm),
_ => {
warn!("Invalid hsm_type configured, using default ...");
None
}
})
.unwrap_or(self.hsm_type),
tpm_tcti_name: config
.tpm_tcti_name
.unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
kanidm_config,
})
}
fn apply_from_config_v2(self, config: ConfigV2) -> Result<Self, UnixIntegrationError> {
let kanidm_config = if let Some(kconfig) = config.kanidm {
Some(KanidmConfig {
conn_timeout: kconfig.conn_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT),
request_timeout: kconfig.request_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT * 2),
pam_allowed_login_groups: kconfig.pam_allowed_login_groups.unwrap_or_default(),
map_group: kconfig.map_group,
})
} else {
None
};
// Now map the values into our config.
Ok(UnixdConfig {
cache_db_path: config.cache_db_path.unwrap_or(self.cache_db_path),
sock_path: config.sock_path.unwrap_or(self.sock_path),
task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
default_shell: config.default_shell.unwrap_or(self.default_shell),
home_prefix: config
.home_prefix
.map(|p| p.into())
.unwrap_or(self.home_prefix.clone()),
home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
home_attr: config
.home_attr
.and_then(|v| match v.as_str() {
"uuid" => Some(HomeAttr::Uuid),
"spn" => Some(HomeAttr::Spn),
"name" => Some(HomeAttr::Name),
_ => {
warn!("Invalid home_attr configured, using default ...");
None
}
})
.unwrap_or(self.home_attr),
home_alias: config
.home_alias
.and_then(|v| match v.as_str() {
"none" => Some(None),
"uuid" => Some(Some(HomeAttr::Uuid)),
"spn" => Some(Some(HomeAttr::Spn)),
"name" => Some(Some(HomeAttr::Name)),
_ => {
warn!("Invalid home_alias configured, using default ...");
None
}
})
.unwrap_or(self.home_alias),
use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
uid_attr_map: config
.uid_attr_map
.and_then(|v| match v.as_str() {
"spn" => Some(UidAttr::Spn),
"name" => Some(UidAttr::Name),
_ => {
warn!("Invalid uid_attr_map configured, using default ...");
None
}
})
.unwrap_or(self.uid_attr_map),
gid_attr_map: config
.gid_attr_map
.and_then(|v| match v.as_str() {
"spn" => Some(UidAttr::Spn),
"name" => Some(UidAttr::Name),
_ => {
warn!("Invalid gid_attr_map configured, using default ...");
None
}
})
.unwrap_or(self.gid_attr_map),
selinux: match config.selinux.unwrap_or(self.selinux) {
#[cfg(all(target_family = "unix", feature = "selinux"))]
true => selinux_util::supported(),
_ => false,
},
hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
hsm_type: config
.hsm_type
.and_then(|v| match v.as_str() {
"soft" => Some(HsmType::Soft),
"tpm_if_possible" => Some(HsmType::TpmIfPossible),
"tpm" => Some(HsmType::Tpm),
_ => {
warn!("Invalid hsm_type configured, using default ...");
None
}
})
.unwrap_or(self.hsm_type),
tpm_tcti_name: config
.tpm_tcti_name
.unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
kanidm_config,
})
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
#[test]
fn test_load_example_configs() {
// Test the various included configs
let examples_dir = env!("CARGO_MANIFEST_DIR").to_string() + "/../../examples/";
for file in PathBuf::from(&examples_dir)
.canonicalize()
.expect(&format!("Can't find examples dir at {}", examples_dir))
.read_dir()
.expect("Can't read examples dir!")
{
let file = file.unwrap();
let filename = file.file_name().into_string().unwrap();
if filename.starts_with("unixd") {
print!("Checking that {} parses as a valid config...", filename);
UnixdConfig::new()
.read_options_from_optional_config(file.path())
.inspect_err(|e| {
println!("Failed to parse: {:?}", e);
})
.expect("Failed to parse!");
println!("OK");
}
}
}
}

View file

@ -12,13 +12,13 @@ use kanidm_unix_common::constants::{
DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX,
DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP,
};
use kanidm_unix_common::unix_config::{GroupMap, KanidmConfig};
use kanidm_unix_common::unix_passwd::{CryptPw, EtcGroup, EtcShadow, EtcUser};
use kanidm_unix_resolver::db::{Cache, Db};
use kanidm_unix_resolver::idprovider::interface::Id;
use kanidm_unix_resolver::idprovider::kanidm::KanidmProvider;
use kanidm_unix_resolver::idprovider::system::SystemProvider;
use kanidm_unix_resolver::resolver::Resolver;
use kanidm_unix_resolver::unix_config::{GroupMap, KanidmConfig};
use kanidmd_core::config::{Configuration, IntegrationTestConfig, ServerRole};
use kanidmd_core::create_server_core;
use kanidmd_testkit::{is_free_port, PORT_ALLOC};