kanidm/server/lib/src/plugins/gidnumber.rs
Firstyear 77271c1720
20240213 3413 domain displayname ()
Remove older migrations and make domain displayname optional.
2025-02-14 10:52:49 +10:00

401 lines
16 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 std::sync::Arc;
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. :(
//
// Per <https://systemd.io/UIDS-GIDS/>, systemd claims a huge chunk of this
// space to itself. As a result we can't allocate between 65536 and u32 max
// because systemd takes most of the usable range for its own containers,
// and half the range is probably going to trigger linux kernel issues.
//
// Seriously, linux's uid/gid model is so fundamentally terrible... Windows
// NT got this right with SIDs.
//
// Because of this, we have to ensure that anything we allocate is in the
// range 1879048192 (0x70000000) to 2147483647 (0x7fffffff)
const GID_SYSTEM_NUMBER_PREFIX: u32 = 0x7000_0000;
const GID_SYSTEM_NUMBER_MASK: u32 = 0x0fff_ffff;
// Systemd claims so many ranges to itself, we have to check we are in certain bounds.
//
// This is the normal system range, we MUST NOT allow it to be allocated.
pub const GID_REGULAR_USER_MIN: u32 = 1000;
pub const GID_REGULAR_USER_MAX: u32 = 60000;
// Systemd homed claims 60001 through 60577
pub const GID_UNUSED_A_MIN: u32 = 60578;
pub const GID_UNUSED_A_MAX: u32 = 61183;
// Systemd dyn service users 61184 through 65519
pub const GID_UNUSED_B_MIN: u32 = 65520;
pub const GID_UNUSED_B_MAX: u32 = 65533;
// nobody is 65534
// 16bit uid -1 65535
pub const GID_UNUSED_C_MIN: u32 = 65536;
const GID_UNUSED_C_MAX: u32 = 524287;
// systemd claims 524288 through 1879048191 for nspawn
const GID_NSPAWN_MIN: u32 = 524288;
const GID_NSPAWN_MAX: u32 = 1879048191;
const GID_UNUSED_D_MIN: u32 = 0x7000_0000;
pub const GID_UNUSED_D_MAX: u32 = 0x7fff_ffff;
// Anything above 2147483648 can confuse the kernel (so basically half the address space
// can't be accessed.
// const GID_UNSAFE_MAX: u32 = 2147483648;
pub struct GidNumber {}
fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> Result<(), OperationError> {
if (e.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.into())
|| e.attribute_equality(Attribute::Class, &EntryClass::PosixAccount.into()))
&& !e.attribute_pres(Attribute::GidNumber)
{
let u_ref = e
.get_uuid()
.ok_or(OperationError::InvalidEntryState)
.inspect_err(|_e| {
admin_error!("Invalid Entry State - Missing UUID");
})?;
let gid = uuid_to_gid_u32(u_ref);
// Apply the mask to only take the last 24 bits, and then move them
// to the correct range.
let gid = gid & GID_SYSTEM_NUMBER_MASK;
let gid = gid | GID_SYSTEM_NUMBER_PREFIX;
let gid_v = Value::new_uint32(gid);
admin_info!("Generated {} for {:?}", gid, u_ref);
e.set_ava(&Attribute::GidNumber, once(gid_v));
Ok(())
} else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber) {
// If they provided us with a gid number, ensure it's in a safe range.
if (GID_REGULAR_USER_MIN..=GID_REGULAR_USER_MAX).contains(&gid)
|| (GID_UNUSED_A_MIN..=GID_UNUSED_A_MAX).contains(&gid)
|| (GID_UNUSED_B_MIN..= GID_UNUSED_B_MAX).contains(&gid)
|| (GID_UNUSED_C_MIN..=GID_UNUSED_C_MAX).contains(&gid)
// We won't ever generate an id in the nspawn range, but we do secretly allow
// it to be set for compatibility with services like freeipa or openldap. TBH
// most people don't even use systemd nspawn anyway ...
//
// I made this design choice to avoid a tunable that may confuse people to
// its purpose. This way things "just work" for imports and existing systems
// but we do the right thing in the future.
|| (GID_NSPAWN_MIN..=GID_NSPAWN_MAX).contains(&gid)
|| (GID_UNUSED_D_MIN..=GID_UNUSED_D_MAX).contains(&gid)
{
Ok(())
} else {
// Note that here we don't advertise that we allow the nspawn range to be set, even
// though we do allow it.
error!(
"Requested GID ({}) overlaps a system range. Allowed ranges are {} to {}, {} to {} and {} to {}",
gid,
GID_REGULAR_USER_MIN, GID_REGULAR_USER_MAX,
GID_UNUSED_C_MIN, GID_UNUSED_C_MAX,
GID_UNUSED_D_MIN, GID_UNUSED_D_MAX
);
Err(OperationError::PL0001GidOverlapsSystemRange)
}
} else {
Ok(())
}
}
impl Plugin for GidNumber {
fn id() -> &'static str {
"plugin_gidnumber"
}
#[instrument(level = "debug", name = "gidnumber_pre_create_transform", skip_all)]
fn pre_create_transform(
_qs: &mut QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
_ce: &CreateEvent,
) -> Result<(), OperationError> {
cand.iter_mut().try_for_each(apply_gidnumber)
}
#[instrument(level = "debug", name = "gidnumber_pre_modify", skip_all)]
fn pre_modify(
_qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_me: &ModifyEvent,
) -> Result<(), OperationError> {
cand.iter_mut().try_for_each(apply_gidnumber)
}
#[instrument(level = "debug", name = "gidnumber_pre_batch_modify", skip_all)]
fn pre_batch_modify(
_qs: &mut QueryServerWriteTransaction,
_pre_cand: &[Arc<EntrySealedCommitted>],
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_me: &BatchModifyEvent,
) -> Result<(), OperationError> {
cand.iter_mut().try_for_each(apply_gidnumber)
}
}
#[cfg(test)]
mod tests {
use super::{
GID_REGULAR_USER_MAX, GID_REGULAR_USER_MIN, GID_UNUSED_A_MAX, GID_UNUSED_A_MIN,
GID_UNUSED_B_MAX, GID_UNUSED_B_MIN, GID_UNUSED_C_MIN, GID_UNUSED_D_MAX,
};
use crate::prelude::*;
#[qs_test]
async fn test_gidnumber_generate(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.expect("txn");
// Test that the gid number is generated on create
{
let user_a_uuid = uuid!("83a0927f-3de1-45ec-bea0-2f7b997ef244");
let op_result = server_txn.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::PosixAccount.to_value()),
(Attribute::Name, Value::new_iname("testperson_1")),
(Attribute::Uuid, Value::Uuid(user_a_uuid)),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
)]);
assert!(op_result.is_ok());
let user_a = server_txn
.internal_search_uuid(user_a_uuid)
.expect("Unable to access user");
let user_a_uid = user_a
.get_ava_single_uint32(Attribute::GidNumber)
.expect("gidnumber not present on account");
assert_eq!(user_a_uid, 0x797ef244);
}
// test that gid is not altered if provided on create.
let user_b_uuid = uuid!("d90fb0cb-6785-4f36-94cb-e364d9c13255");
{
let op_result = server_txn.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::PosixAccount.to_value()),
(Attribute::Name, Value::new_iname("testperson_2")),
(Attribute::Uuid, Value::Uuid(user_b_uuid)),
(Attribute::GidNumber, Value::Uint32(10001)),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
)]);
assert!(op_result.is_ok());
let user_b = server_txn
.internal_search_uuid(user_b_uuid)
.expect("Unable to access user");
let user_b_uid = user_b
.get_ava_single_uint32(Attribute::GidNumber)
.expect("gidnumber not present on account");
assert_eq!(user_b_uid, 10001);
}
// Test that if the value is deleted, it is correctly regenerated.
{
let modlist = modlist!([m_purge(Attribute::GidNumber)]);
server_txn
.internal_modify_uuid(user_b_uuid, &modlist)
.expect("Unable to modify user");
let user_b = server_txn
.internal_search_uuid(user_b_uuid)
.expect("Unable to access user");
let user_b_uid = user_b
.get_ava_single_uint32(Attribute::GidNumber)
.expect("gidnumber not present on account");
assert_eq!(user_b_uid, 0x79c13255);
}
let user_c_uuid = uuid!("0d5086b0-74f9-4518-92b4-89df0c55971b");
// Test that an entry when modified to have posix attributes will have
// it's gidnumber generated.
{
let op_result = server_txn.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson_3")),
(Attribute::Uuid, Value::Uuid(user_c_uuid)),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
)]);
assert!(op_result.is_ok());
let user_c = server_txn
.internal_search_uuid(user_c_uuid)
.expect("Unable to access user");
assert_eq!(user_c.get_ava_single_uint32(Attribute::GidNumber), None);
let modlist = modlist!([m_pres(
Attribute::Class,
&EntryClass::PosixAccount.to_value()
)]);
server_txn
.internal_modify_uuid(user_c_uuid, &modlist)
.expect("Unable to modify user");
let user_c = server_txn
.internal_search_uuid(user_c_uuid)
.expect("Unable to access user");
let user_c_uid = user_c
.get_ava_single_uint32(Attribute::GidNumber)
.expect("gidnumber not present on account");
assert_eq!(user_c_uid, 0x7c55971b);
}
let user_d_uuid = uuid!("36dc9010-d80c-404b-b5ba-8f66657c2f1d");
// Test that an entry when modified to have posix attributes will have
// it's gidnumber generated.
{
let op_result = server_txn.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson_4")),
(Attribute::Uuid, Value::Uuid(user_d_uuid)),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
)]);
assert!(op_result.is_ok());
let user_d = server_txn
.internal_search_uuid(user_d_uuid)
.expect("Unable to access user");
assert_eq!(user_d.get_ava_single_uint32(Attribute::GidNumber), None);
let modlist = modlist!([m_pres(
Attribute::Class,
&EntryClass::PosixAccount.to_value()
)]);
server_txn
.internal_modify_uuid(user_d_uuid, &modlist)
.expect("Unable to modify user");
let user_d = server_txn
.internal_search_uuid(user_d_uuid)
.expect("Unable to access user");
let user_d_uid = user_d
.get_ava_single_uint32(Attribute::GidNumber)
.expect("gidnumber not present on account");
assert_eq!(user_d_uid, 0x757c2f1d);
}
let user_e_uuid = uuid!("a6dc0d68-9c7a-4dad-b1e2-f6274b691373");
// Test that an entry when modified to have posix attributes, if a gidnumber
// is provided then it is respected.
{
let op_result = server_txn.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson_5")),
(Attribute::Uuid, Value::Uuid(user_e_uuid)),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
)]);
assert!(op_result.is_ok());
let user_e = server_txn
.internal_search_uuid(user_e_uuid)
.expect("Unable to access user");
assert_eq!(user_e.get_ava_single_uint32(Attribute::GidNumber), None);
let modlist = modlist!([
m_pres(Attribute::Class, &EntryClass::PosixAccount.to_value()),
m_pres(Attribute::GidNumber, &Value::Uint32(10002))
]);
server_txn
.internal_modify_uuid(user_e_uuid, &modlist)
.expect("Unable to modify user");
let user_e = server_txn
.internal_search_uuid(user_e_uuid)
.expect("Unable to access user");
let user_e_uid = user_e
.get_ava_single_uint32(Attribute::GidNumber)
.expect("gidnumber not present on account");
assert_eq!(user_e_uid, 10002);
}
// Test rejection of important gid values.
let user_f_uuid = uuid!("33afc396-2434-47e5-b143-05176148b50e");
// Test that an entry when modified to have posix attributes, if a gidnumber
// is provided then it is respected.
{
let op_result = server_txn.internal_create(vec![entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson_6")),
(Attribute::Uuid, Value::Uuid(user_f_uuid)),
(Attribute::Description, Value::new_utf8s("testperson")),
(Attribute::DisplayName, Value::new_utf8s("testperson"))
)]);
assert!(op_result.is_ok());
for id in [
0,
500,
GID_REGULAR_USER_MIN - 1,
GID_REGULAR_USER_MAX + 1,
GID_UNUSED_A_MIN - 1,
GID_UNUSED_A_MAX + 1,
GID_UNUSED_B_MIN - 1,
GID_UNUSED_B_MAX + 1,
GID_UNUSED_C_MIN - 1,
GID_UNUSED_D_MAX + 1,
u32::MAX,
] {
let modlist = modlist!([
m_pres(Attribute::Class, &EntryClass::PosixAccount.to_value()),
m_pres(Attribute::GidNumber, &Value::Uint32(id))
]);
let op_result = server_txn.internal_modify_uuid(user_f_uuid, &modlist);
trace!(?id);
assert_eq!(op_result, Err(OperationError::PL0001GidOverlapsSystemRange));
}
}
assert!(server_txn.commit().is_ok());
}
}