Add file diagnosis ()

This commit is contained in:
Firstyear 2023-10-12 12:09:54 +10:00 committed by GitHub
parent fbc62ea51e
commit 88da55260a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 370 additions and 66 deletions
Cargo.lock
libs
client
file_permissions/src
server
unix_integration/src

2
Cargo.lock generated
View file

@ -2896,6 +2896,7 @@ name = "kanidm_client"
version = "1.1.0-rc.14-dev"
dependencies = [
"hyper",
"kanidm_lib_file_permissions",
"kanidm_proto",
"reqwest",
"serde",
@ -3055,6 +3056,7 @@ dependencies = [
"hyper",
"kanidm_build_profiles",
"kanidm_lib_crypto",
"kanidm_lib_file_permissions",
"kanidm_proto",
"kanidm_utils_users",
"kanidmd_lib",

View file

@ -17,6 +17,7 @@ reqwest = { workspace = true, default-features = false, features = [
"multipart",
] }
kanidm_proto = { workspace = true }
kanidm_lib_file_permissions = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
time = { workspace = true, features = ["serde", "std"] }

View file

@ -260,6 +260,8 @@ impl KanidmClientBuilder {
// error. This check enforces that we get the CORRECT error message instead.
if !config_path.as_ref().exists() {
debug!("{:?} does not exist", config_path);
let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
info!(%diag);
return Ok(self);
};
@ -290,6 +292,9 @@ impl KanidmClientBuilder {
);
}
};
let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
info!(%diag);
return Ok(self);
}
};

View file

@ -1,54 +1,9 @@
use std::fs::Metadata;
#[cfg(target_os = "linux")]
use std::os::linux::fs::MetadataExt;
#[cfg(target_os = "macos")]
use std::os::macos::fs::MetadataExt;
// #[cfg(target_os = "windows")]
// use std::os::windows::fs::MetadataExt;
#[cfg(target_family = "unix")]
use kanidm_utils_users::{get_current_gid, get_current_uid};
#[cfg(target_family = "windows")]
mod windows;
#[cfg(target_family = "windows")]
pub use windows::{diagnose_path, readonly};
#[cfg(target_family = "unix")]
/// Check a given file's metadata is read-only for the current user (true = read-only)
pub fn readonly(meta: &Metadata) -> bool {
// Who are we running as?
let cuid = get_current_uid();
let cgid = get_current_gid();
// Who owns the file?
// Who is the group owner of the file?
let f_gid = meta.st_gid();
let f_uid = meta.st_uid();
let f_mode = meta.st_mode();
!(
// If we are the owner, we have write perms as we can alter the DAC rights
cuid == f_uid ||
// If we are the group owner, check the mode bits do not have write.
(cgid == f_gid && (f_mode & 0o0020) != 0) ||
// Finally, check that everyone bits don't have write.
((f_mode & 0o0002) != 0)
)
}
mod unix;
#[cfg(target_family = "unix")]
#[test]
fn test_readonly() {
let meta = std::fs::metadata("Cargo.toml").expect("Can't find Cargo.toml");
println!("meta={:?} -> readonly={:?}", meta, readonly(&meta));
assert!(readonly(&meta) == false);
}
#[cfg(not(target_family = "unix"))]
/// Check a given file's metadata is read-only for the current user (true = read-only) Stub function if you're building for windows!
pub fn readonly(meta: &Metadata) -> bool {
eprintln!(
"Windows target asked to check metadata on {:?} returning false",
meta
);
false
}
pub use unix::{diagnose_path, readonly};

View file

@ -0,0 +1,299 @@
use std::fs::Metadata;
#[cfg(target_os = "linux")]
use std::os::linux::fs::MetadataExt;
#[cfg(target_os = "macos")]
use std::os::macos::fs::MetadataExt;
use kanidm_utils_users::{get_current_gid, get_current_uid};
use std::fmt;
use std::path::{Path, PathBuf};
/// Check a given file's metadata is read-only for the current user (true = read-only)
pub fn readonly(meta: &Metadata) -> bool {
// Who are we running as?
let cuid = get_current_uid();
let cgid = get_current_gid();
// Who owns the file?
// Who is the group owner of the file?
let f_gid = meta.st_gid();
let f_uid = meta.st_uid();
let f_mode = meta.st_mode();
!(
// If we are the owner, we have write perms as we can alter the DAC rights
cuid == f_uid ||
// If we are the group owner, check the mode bits do not have write.
(cgid == f_gid && (f_mode & 0o0020) != 0) ||
// Finally, check that everyone bits don't have write.
((f_mode & 0o0002) != 0)
)
}
#[derive(Debug)]
pub enum PathStatus {
Dir {
f_gid: u32,
f_uid: u32,
f_mode: u32,
access: bool,
},
Link {
f_gid: u32,
f_uid: u32,
f_mode: u32,
access: bool,
},
File {
f_gid: u32,
f_uid: u32,
f_mode: u32,
access: bool,
},
Error(std::io::Error),
}
#[derive(Debug)]
pub struct Diagnosis {
cuid: u32,
cgid: u32,
path: PathBuf,
abs_path: Result<PathBuf, std::io::Error>,
ancestors: Vec<(PathBuf, PathStatus)>,
}
impl fmt::Display for Diagnosis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "-- diagnosis for path: {}", self.path.to_string_lossy())?;
let indent = match &self.abs_path {
Ok(abs) => {
let abs_str = abs.to_string_lossy();
writeln!(f, "canonicalised to: {}", abs_str)?;
abs_str.len() + 1
}
Err(_) => {
writeln!(f, "unable to canonicalise path")?;
self.path.to_string_lossy().len() + 1
}
};
writeln!(f, "running as: {}:{}", self.cuid, self.cgid)?;
writeln!(f, "path permissions\n")?;
for (anc, status) in &self.ancestors {
match &status {
PathStatus::Dir {
f_gid,
f_uid,
f_mode,
access,
} => {
writeln!(
f,
" {:indent$}: DIR access: {} owner: {} group: {} mode: {}",
anc.to_string_lossy(),
access,
f_uid,
f_gid,
mode_to_string(*f_mode)
)?;
}
PathStatus::Link {
f_gid,
f_uid,
f_mode,
access,
} => {
writeln!(
f,
" {:indent$}: LINK access: {} owner: {} group: {} mode: {}",
anc.to_string_lossy(),
access,
f_uid,
f_gid,
mode_to_string(*f_mode)
)?;
}
PathStatus::File {
f_gid,
f_uid,
f_mode,
access,
} => {
writeln!(
f,
" {:indent$}: FILE access: {} owner: {} group: {} mode: {}",
anc.to_string_lossy(),
access,
f_uid,
f_gid,
mode_to_string(*f_mode)
)?;
}
PathStatus::Error(err) => {
writeln!(f, " {:indent$}: ERROR: {:?}", anc.to_string_lossy(), err)?;
}
}
}
writeln!(
f,
"\n note that accesibility does not account for ACL's or MAC"
)?;
writeln!(f, "-- end diagnosis")
}
}
pub fn diagnose_path(path: &Path) -> Diagnosis {
// Who are we?
let cuid = get_current_uid();
let cgid = get_current_gid();
// clone the path
let path: PathBuf = path.into();
// Display the abs/resolved path.
let abs_path = path.canonicalize();
// For each segment, from the root inc root
// show the path -> owner/group mode
// or show that we have permission denied.
let mut all_ancestors: Vec<_> = match &abs_path {
Ok(ap) => ap.ancestors().collect(),
Err(_) => path.ancestors().collect(),
};
let mut ancestors = Vec::with_capacity(all_ancestors.len());
// Now pop from the right to start from the root.
while let Some(anc) = all_ancestors.pop() {
let status = match anc.metadata() {
Ok(meta) => {
let f_gid = meta.st_gid();
let f_uid = meta.st_uid();
let f_mode = meta.st_mode();
if meta.is_dir() {
let access = x_accessible(cuid, cgid, f_uid, f_gid, f_mode);
PathStatus::Dir {
f_gid,
f_uid,
f_mode,
access,
}
} else if meta.is_symlink() {
let access = x_accessible(cuid, cgid, f_uid, f_gid, f_mode);
PathStatus::Link {
f_gid,
f_uid,
f_mode,
access,
}
} else {
let access = accessible(cuid, cgid, f_uid, f_gid, f_mode);
PathStatus::File {
f_gid,
f_uid,
f_mode,
access,
}
}
}
Err(e) => PathStatus::Error(e),
};
ancestors.push((anc.into(), status))
}
Diagnosis {
cuid,
cgid,
path,
abs_path,
ancestors,
}
}
fn x_accessible(cuid: u32, cgid: u32, f_uid: u32, f_gid: u32, f_mode: u32) -> bool {
(cuid == f_uid && f_mode & 0o500 == 0o500)
|| (cgid == f_gid && f_mode & 0o050 == 0o050)
|| f_mode & 0o005 == 0o005
}
fn accessible(cuid: u32, cgid: u32, f_uid: u32, f_gid: u32, f_mode: u32) -> bool {
(cuid == f_uid && f_mode & 0o400 == 0o400)
|| (cgid == f_gid && f_mode & 0o040 == 0o040)
|| f_mode & 0o004 == 0o004
}
fn mode_to_string(mode: u32) -> String {
let mut mode_str = String::with_capacity(9);
if mode & 0o400 != 0 {
mode_str.push('r')
} else {
mode_str.push('-')
}
if mode & 0o200 != 0 {
mode_str.push('w')
} else {
mode_str.push('-')
}
if mode & 0o100 != 0 {
mode_str.push('x')
} else {
mode_str.push('-')
}
if mode & 0o040 != 0 {
mode_str.push('r')
} else {
mode_str.push('-')
}
if mode & 0o020 != 0 {
mode_str.push('w')
} else {
mode_str.push('-')
}
if mode & 0o010 != 0 {
mode_str.push('x')
} else {
mode_str.push('-')
}
if mode & 0o004 != 0 {
mode_str.push('r')
} else {
mode_str.push('-')
}
if mode & 0o002 != 0 {
mode_str.push('w')
} else {
mode_str.push('-')
}
if mode & 0o001 != 0 {
mode_str.push('x')
} else {
mode_str.push('-')
}
mode_str
}
#[test]
fn test_readonly() {
let meta = std::fs::metadata("Cargo.toml").expect("Can't find Cargo.toml");
println!("meta={:?} -> readonly={:?}", meta, readonly(&meta));
assert!(readonly(&meta) == false);
}

View file

@ -0,0 +1,24 @@
// #[cfg(target_os = "windows")]
// use std::os::windows::fs::MetadataExt;
/// Check a given file's metadata is read-only for the current user (true = read-only) Stub function if you're building for windows!
pub fn readonly(meta: &Metadata) -> bool {
eprintln!(
"Windows target asked to check metadata on {:?} returning false",
meta
);
false
}
#[derive(Debug)]
pub struct Diagnosis;
impl fmt::Display for Diagnosis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Unable to diagnose path issues on windows 😢")
}
}
pub fn diagnose_path(path: &Path) -> Diagnosis {
Diagnosis {}
}

View file

@ -32,6 +32,7 @@ kanidm_proto = { workspace = true }
kanidm_utils_users = { workspace = true }
kanidmd_lib = { workspace = true }
kanidm_lib_crypto = { workspace = true }
kanidm_lib_file_permissions = { workspace = true }
ldap3_proto = { workspace = true }
libc = { workspace = true }
openssl = { workspace = true }

View file

@ -113,14 +113,18 @@ pub struct ServerConfig {
impl ServerConfig {
pub fn new<P: AsRef<Path>>(config_path: P) -> Result<Self, std::io::Error> {
let mut f = File::open(config_path).map_err(|e| {
let mut f = File::open(config_path.as_ref()).map_err(|e| {
eprintln!("Unable to open config file [{:?}] 🥺", e);
let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
eprintln!("{}", diag);
e
})?;
let mut contents = String::new();
f.read_to_string(&mut contents).map_err(|e| {
eprintln!("unable to read contents {:?}", e);
let diag = kanidm_lib_file_permissions::diagnose_path(config_path.as_ref());
eprintln!("{}", diag);
e
})?;

View file

@ -121,6 +121,8 @@ async fn submit_admin_req(path: &str, req: AdminTaskRequest, output_mode: Consol
Ok(s) => s,
Err(e) => {
error!(err = ?e, %path, "Unable to connect to socket path");
let diag = kanidm_lib_file_permissions::diagnose_path(path.as_ref());
info!(%diag);
return;
}
};
@ -228,21 +230,12 @@ async fn kanidm_main() -> ExitCode {
return ExitCode::FAILURE;
};
let sconfig = match cfg_path.exists() {
false => {
config_error.push(format!(
"Refusing to run - config file {} does not exist",
cfg_path.to_str().unwrap_or("<invalid filename>")
));
None
let sconfig = match ServerConfig::new(&cfg_path) {
Ok(c) => Some(c),
Err(e) => {
config_error.push(format!("Config Parse failure {:?}", e));
return ExitCode::FAILURE;
}
true => match ServerConfig::new(&cfg_path) {
Ok(c) => Some(c),
Err(e) => {
config_error.push(format!("Config Parse failure {:?}", e));
return ExitCode::FAILURE;
}
},
};
// We only allow config file for log level now.
@ -359,6 +352,8 @@ async fn kanidm_main() -> ExitCode {
"DB folder {} may not exist, server startup may FAIL!",
db_parent_path.to_str().unwrap_or("invalid file path")
);
let diag = kanidm_lib_file_permissions::diagnose_path(&db_path);
info!(%diag);
}
let db_par_path_buf = db_parent_path.to_path_buf();
@ -455,6 +450,8 @@ async fn kanidm_main() -> ExitCode {
&i_path.to_str().unwrap_or("invalid file path"),
e
);
let diag = kanidm_lib_file_permissions::diagnose_path(&i_path);
info!(%diag);
return ExitCode::FAILURE
}
};
@ -474,6 +471,8 @@ async fn kanidm_main() -> ExitCode {
&i_path.to_str().unwrap_or("invalid file path"),
e
);
let diag = kanidm_lib_file_permissions::diagnose_path(&i_path);
info!(%diag);
return ExitCode::FAILURE
}
};

View file

@ -524,12 +524,16 @@ async fn main() -> ExitCode {
"Client config missing from {} - cannot start up. Quitting.",
cfg_path_str
);
let diag = kanidm_lib_file_permissions::diagnose_path(cfg_path.as_ref());
info!(%diag);
return ExitCode::FAILURE
} else {
let cfg_meta = match metadata(&cfg_path) {
Ok(v) => v,
Err(e) => {
error!("Unable to read metadata for {} - {:?}", cfg_path_str, e);
let diag = kanidm_lib_file_permissions::diagnose_path(cfg_path.as_ref());
info!(%diag);
return ExitCode::FAILURE
}
};
@ -558,12 +562,16 @@ async fn main() -> ExitCode {
"unixd config missing from {} - cannot start up. Quitting.",
unixd_path_str
);
let diag = kanidm_lib_file_permissions::diagnose_path(unixd_path.as_ref());
info!(%diag);
return ExitCode::FAILURE
} else {
let unixd_meta = match metadata(&unixd_path) {
Ok(v) => v,
Err(e) => {
error!("Unable to read metadata for {} - {:?}", unixd_path_str, e);
let diag = kanidm_lib_file_permissions::diagnose_path(unixd_path.as_ref());
info!(%diag);
return ExitCode::FAILURE
}
};
@ -624,6 +632,8 @@ async fn main() -> ExitCode {
.to_str()
.unwrap_or("<db_parent_path invalid>")
);
let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref());
info!(%diag);
return ExitCode::FAILURE
}
@ -672,6 +682,8 @@ async fn main() -> ExitCode {
"Refusing to run - DB path {} already exists and is not a file.",
db_path.to_str().unwrap_or("<db_path invalid>")
);
let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref());
info!(%diag);
return ExitCode::FAILURE
};
@ -683,6 +695,8 @@ async fn main() -> ExitCode {
db_path.to_str().unwrap_or("<db_path invalid>"),
e
);
let diag = kanidm_lib_file_permissions::diagnose_path(db_path.as_ref());
info!(%diag);
return ExitCode::FAILURE
}
};