kanidm/kanidmd/idm/src/plugins/gidnumber.rs
2022-10-01 16:08:51 +10:00

361 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// A plugin that generates gid numbers on types that require them for posix
// support.
use std::iter::once;
use crate::event::{CreateEvent, ModifyEvent};
use crate::plugins::Plugin;
use crate::prelude::*;
use crate::utils::uuid_to_gid_u32;
/// Systemd dynamic units allocate between 6118465519, most distros allocate
/// system uids from 0 - 1000, and many others give user ids between 1000 to
/// 2000. This whole numberspace is cursed, lets assume it's not ours. :(
const GID_SYSTEM_NUMBER_MIN: u32 = 65536;
/// This is the normal system range, we MUST NOT allow it to be allocated.
const GID_SAFETY_NUMBER_MIN: u32 = 1000;
lazy_static! {
static ref CLASS_POSIXGROUP: PartialValue = PartialValue::new_class("posixgroup");
static ref CLASS_POSIXACCOUNT: PartialValue = PartialValue::new_class("posixaccount");
}
pub struct GidNumber {}
fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> Result<(), OperationError> {
if (e.attribute_equality("class", &CLASS_POSIXGROUP)
|| e.attribute_equality("class", &CLASS_POSIXACCOUNT))
&& !e.attribute_pres("gidnumber")
{
let u_ref = e
.get_uuid()
.ok_or(OperationError::InvalidEntryState)
.map_err(|e| {
admin_error!("Invalid Entry State - Missing UUID");
e
})?;
let gid = uuid_to_gid_u32(u_ref);
// assert the value is greater than the system range.
if gid < GID_SYSTEM_NUMBER_MIN {
return Err(OperationError::InvalidAttribute(format!(
"gidnumber {} may overlap with system range {}",
gid, GID_SYSTEM_NUMBER_MIN
)));
}
let gid_v = Value::new_uint32(gid);
admin_info!("Generated {} for {:?}", gid, u_ref);
e.set_ava("gidnumber", once(gid_v));
Ok(())
} else if let Some(gid) = e.get_ava_single_uint32("gidnumber") {
// If they provided us with a gid number, ensure it's in a safe range.
if gid <= GID_SAFETY_NUMBER_MIN {
Err(OperationError::InvalidAttribute(format!(
"gidnumber {} overlaps into system secure range {}",
gid, GID_SAFETY_NUMBER_MIN
)))
} else {
Ok(())
}
} else {
Ok(())
}
}
impl Plugin for GidNumber {
fn id() -> &'static str {
"plugin_gidnumber"
}
#[instrument(
level = "debug",
name = "gidnumber_pre_create_transform",
skip(_qs, cand, _ce)
)]
fn pre_create_transform(
_qs: &QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
_ce: &CreateEvent,
) -> Result<(), OperationError> {
for e in cand.iter_mut() {
apply_gidnumber(e)?;
}
Ok(())
}
#[instrument(level = "debug", name = "gidnumber_pre_modify", skip(_qs, cand, _me))]
fn pre_modify(
_qs: &QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_me: &ModifyEvent,
) -> Result<(), OperationError> {
for e in cand.iter_mut() {
apply_gidnumber(e)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
fn check_gid(qs_write: &QueryServerWriteTransaction, uuid: &str, gid: u32) {
let u = Uuid::parse_str(uuid).unwrap();
let e = qs_write.internal_search_uuid(&u).unwrap();
let gidnumber = e.get_ava_single("gidnumber").unwrap();
let ex_gid = Value::new_uint32(gid);
assert!(ex_gid == gidnumber);
}
#[test]
fn test_gidnumber_create_generate() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Ok(()),
preload,
create,
None,
|qs_write: &QueryServerWriteTransaction| check_gid(
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
0x997ef244
)
);
}
// test that gid is not altered if provided on create.
#[test]
fn test_gidnumber_create_noaction() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"gidnumber": ["10001"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Ok(()),
preload,
create,
None,
|qs_write: &QueryServerWriteTransaction| check_gid(
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
10001
)
);
}
// Test generated if not on mod (ie adding posixaccount to something)
#[test]
fn test_gidnumber_modify_generate() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "service_account"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let preload = vec![e];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iname("testperson"))),
modlist!([m_pres("class", &Value::new_class("posixgroup"))]),
None,
|_| {},
|qs_write: &QueryServerWriteTransaction| check_gid(
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
0x997ef244
)
);
}
// test generated if DELETED on mod
#[test]
fn test_gidnumber_modify_regenerate() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"gidnumber": ["2000"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let preload = vec![e];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iname("testperson"))),
modlist!([m_purge("gidnumber")]),
None,
|_| {},
|qs_write: &QueryServerWriteTransaction| check_gid(
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
0x997ef244
)
);
}
// Test NOT regenerated if given on mod
#[test]
fn test_gidnumber_modify_noregen() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"gidnumber": ["3999"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let preload = vec![e];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iname("testperson"))),
modlist!([
m_purge("gidnumber"),
m_pres("gidnumber", &Value::new_uint32(2000))
]),
None,
|_| {},
|qs_write: &QueryServerWriteTransaction| check_gid(
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
2000
)
);
}
#[test]
fn test_gidnumber_create_system_reject() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b00000244"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Err(OperationError::InvalidAttribute(
"gidnumber 580 may overlap with system range 65536".to_string()
)),
preload,
create,
None,
|_| {}
);
}
#[test]
fn test_gidnumber_create_secure_reject() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"gidnumber": ["500"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Err(OperationError::InvalidAttribute(
"gidnumber 500 overlaps into system secure range 1000".to_string()
)),
preload,
create,
None,
|_| {}
);
}
#[test]
fn test_gidnumber_create_secure_root_reject() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"gidnumber": ["0"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Err(OperationError::InvalidAttribute(
"gidnumber 0 overlaps into system secure range 1000".to_string()
)),
preload,
create,
None,
|_| {}
);
}
}