kanidmd_lib/plugins/
gidnumber.rs

1// A plugin that generates gid numbers on types that require them for posix
2// support.
3
4use std::iter::once;
5use std::sync::Arc;
6
7use crate::event::{CreateEvent, ModifyEvent};
8use crate::plugins::Plugin;
9use crate::prelude::*;
10use crate::utils::uuid_to_gid_u32;
11
12// Systemd dynamic units allocate between 61184–65519, most distros allocate
13// system uids from 0 - 1000, and many others give user ids between 1000 to
14// 2000. This whole numberspace is cursed, lets assume it's not ours. :(
15//
16// Per <https://systemd.io/UIDS-GIDS/>, systemd claims a huge chunk of this
17// space to itself. As a result we can't allocate between 65536 and u32 max
18// because systemd takes most of the usable range for its own containers,
19// and half the range is probably going to trigger linux kernel issues.
20//
21// Seriously, linux's uid/gid model is so fundamentally terrible... Windows
22// NT got this right with SIDs.
23//
24// Because of this, we have to ensure that anything we allocate is in the
25// range 1879048192 (0x70000000) to 2147483647 (0x7fffffff)
26const GID_SYSTEM_NUMBER_PREFIX: u32 = 0x7000_0000;
27const GID_SYSTEM_NUMBER_MASK: u32 = 0x0fff_ffff;
28
29// Systemd claims so many ranges to itself, we have to check we are in certain bounds.
30//
31// This is the normal system range, we MUST NOT allow it to be allocated.
32pub const GID_REGULAR_USER_MIN: u32 = 1000;
33pub const GID_REGULAR_USER_MAX: u32 = 60000;
34
35// Systemd homed claims 60001 through 60577
36
37pub const GID_UNUSED_A_MIN: u32 = 60578;
38pub const GID_UNUSED_A_MAX: u32 = 61183;
39
40// Systemd dyn service users 61184 through 65519
41
42pub const GID_UNUSED_B_MIN: u32 = 65520;
43pub const GID_UNUSED_B_MAX: u32 = 65533;
44
45// nobody is 65534
46// 16bit uid -1 65535
47
48pub const GID_UNUSED_C_MIN: u32 = 65536;
49const GID_UNUSED_C_MAX: u32 = 524287;
50
51// systemd claims 524288 through 1879048191 for nspawn
52
53const GID_NSPAWN_MIN: u32 = 524288;
54const GID_NSPAWN_MAX: u32 = 1879048191;
55
56const GID_UNUSED_D_MIN: u32 = 0x7000_0000;
57pub const GID_UNUSED_D_MAX: u32 = 0x7fff_ffff;
58
59// Anything above 2147483648 can confuse the kernel (so basically half the address space
60// can't be accessed.
61// const GID_UNSAFE_MAX: u32 = 2147483648;
62
63pub struct GidNumber {}
64
65fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> Result<(), OperationError> {
66    if (e.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.into())
67        || e.attribute_equality(Attribute::Class, &EntryClass::PosixAccount.into()))
68        && !e.attribute_pres(Attribute::GidNumber)
69    {
70        let u_ref = e
71            .get_uuid()
72            .ok_or(OperationError::InvalidEntryState)
73            .inspect_err(|_e| {
74                admin_error!("Invalid Entry State - Missing UUID");
75            })?;
76
77        let gid = uuid_to_gid_u32(u_ref);
78
79        // Apply the mask to only take the last 24 bits, and then move them
80        // to the correct range.
81        let gid = gid & GID_SYSTEM_NUMBER_MASK;
82        let gid = gid | GID_SYSTEM_NUMBER_PREFIX;
83
84        let gid_v = Value::new_uint32(gid);
85        admin_info!("Generated {} for {:?}", gid, u_ref);
86        e.set_ava(&Attribute::GidNumber, once(gid_v));
87        Ok(())
88    } else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber) {
89        // If they provided us with a gid number, ensure it's in a safe range.
90        if (GID_REGULAR_USER_MIN..=GID_REGULAR_USER_MAX).contains(&gid)
91            || (GID_UNUSED_A_MIN..=GID_UNUSED_A_MAX).contains(&gid)
92            || (GID_UNUSED_B_MIN..= GID_UNUSED_B_MAX).contains(&gid)
93            || (GID_UNUSED_C_MIN..=GID_UNUSED_C_MAX).contains(&gid)
94            // We won't ever generate an id in the nspawn range, but we do secretly allow
95            // it to be set for compatibility with services like freeipa or openldap. TBH
96            // most people don't even use systemd nspawn anyway ...
97            //
98            // I made this design choice to avoid a tunable that may confuse people to
99            // its purpose. This way things "just work" for imports and existing systems
100            // but we do the right thing in the future.
101            || (GID_NSPAWN_MIN..=GID_NSPAWN_MAX).contains(&gid)
102            || (GID_UNUSED_D_MIN..=GID_UNUSED_D_MAX).contains(&gid)
103        {
104            Ok(())
105        } else {
106            // Note that here we don't advertise that we allow the nspawn range to be set, even
107            // though we do allow it.
108            error!(
109                "Requested GID ({}) overlaps a system range. Allowed ranges are {} to {}, {} to {} and {} to {}",
110                gid,
111                GID_REGULAR_USER_MIN, GID_REGULAR_USER_MAX,
112                GID_UNUSED_C_MIN, GID_UNUSED_C_MAX,
113                GID_UNUSED_D_MIN, GID_UNUSED_D_MAX
114            );
115            Err(OperationError::PL0001GidOverlapsSystemRange)
116        }
117    } else {
118        Ok(())
119    }
120}
121
122impl Plugin for GidNumber {
123    fn id() -> &'static str {
124        "plugin_gidnumber"
125    }
126
127    #[instrument(level = "debug", name = "gidnumber_pre_create_transform", skip_all)]
128    fn pre_create_transform(
129        _qs: &mut QueryServerWriteTransaction,
130        cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
131        _ce: &CreateEvent,
132    ) -> Result<(), OperationError> {
133        cand.iter_mut().try_for_each(apply_gidnumber)
134    }
135
136    #[instrument(level = "debug", name = "gidnumber_pre_modify", skip_all)]
137    fn pre_modify(
138        _qs: &mut QueryServerWriteTransaction,
139        _pre_cand: &[Arc<EntrySealedCommitted>],
140        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
141        _me: &ModifyEvent,
142    ) -> Result<(), OperationError> {
143        cand.iter_mut().try_for_each(apply_gidnumber)
144    }
145
146    #[instrument(level = "debug", name = "gidnumber_pre_batch_modify", skip_all)]
147    fn pre_batch_modify(
148        _qs: &mut QueryServerWriteTransaction,
149        _pre_cand: &[Arc<EntrySealedCommitted>],
150        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
151        _me: &BatchModifyEvent,
152    ) -> Result<(), OperationError> {
153        cand.iter_mut().try_for_each(apply_gidnumber)
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::{
160        GID_REGULAR_USER_MAX, GID_REGULAR_USER_MIN, GID_UNUSED_A_MAX, GID_UNUSED_A_MIN,
161        GID_UNUSED_B_MAX, GID_UNUSED_B_MIN, GID_UNUSED_C_MIN, GID_UNUSED_D_MAX,
162    };
163    use crate::prelude::*;
164
165    #[qs_test]
166    async fn test_gidnumber_generate(server: &QueryServer) {
167        let mut server_txn = server.write(duration_from_epoch_now()).await.expect("txn");
168
169        // Test that the gid number is generated on create
170        {
171            let user_a_uuid = uuid!("83a0927f-3de1-45ec-bea0-2f7b997ef244");
172            let op_result = server_txn.internal_create(vec![entry_init!(
173                (Attribute::Class, EntryClass::Account.to_value()),
174                (Attribute::Class, EntryClass::PosixAccount.to_value()),
175                (Attribute::Name, Value::new_iname("testperson_1")),
176                (Attribute::Uuid, Value::Uuid(user_a_uuid)),
177                (Attribute::Description, Value::new_utf8s("testperson")),
178                (Attribute::DisplayName, Value::new_utf8s("testperson"))
179            )]);
180
181            assert!(op_result.is_ok());
182
183            let user_a = server_txn
184                .internal_search_uuid(user_a_uuid)
185                .expect("Unable to access user");
186
187            let user_a_uid = user_a
188                .get_ava_single_uint32(Attribute::GidNumber)
189                .expect("gidnumber not present on account");
190
191            assert_eq!(user_a_uid, 0x797ef244);
192        }
193
194        // test that gid is not altered if provided on create.
195        let user_b_uuid = uuid!("d90fb0cb-6785-4f36-94cb-e364d9c13255");
196        {
197            let op_result = server_txn.internal_create(vec![entry_init!(
198                (Attribute::Class, EntryClass::Account.to_value()),
199                (Attribute::Class, EntryClass::PosixAccount.to_value()),
200                (Attribute::Name, Value::new_iname("testperson_2")),
201                (Attribute::Uuid, Value::Uuid(user_b_uuid)),
202                (Attribute::GidNumber, Value::Uint32(10001)),
203                (Attribute::Description, Value::new_utf8s("testperson")),
204                (Attribute::DisplayName, Value::new_utf8s("testperson"))
205            )]);
206
207            assert!(op_result.is_ok());
208
209            let user_b = server_txn
210                .internal_search_uuid(user_b_uuid)
211                .expect("Unable to access user");
212
213            let user_b_uid = user_b
214                .get_ava_single_uint32(Attribute::GidNumber)
215                .expect("gidnumber not present on account");
216
217            assert_eq!(user_b_uid, 10001);
218        }
219
220        // Test that if the value is deleted, it is correctly regenerated.
221        {
222            let modlist = modlist!([m_purge(Attribute::GidNumber)]);
223            server_txn
224                .internal_modify_uuid(user_b_uuid, &modlist)
225                .expect("Unable to modify user");
226
227            let user_b = server_txn
228                .internal_search_uuid(user_b_uuid)
229                .expect("Unable to access user");
230
231            let user_b_uid = user_b
232                .get_ava_single_uint32(Attribute::GidNumber)
233                .expect("gidnumber not present on account");
234
235            assert_eq!(user_b_uid, 0x79c13255);
236        }
237
238        let user_c_uuid = uuid!("0d5086b0-74f9-4518-92b4-89df0c55971b");
239        // Test that an entry when modified to have posix attributes will have
240        // it's gidnumber generated.
241        {
242            let op_result = server_txn.internal_create(vec![entry_init!(
243                (Attribute::Class, EntryClass::Account.to_value()),
244                (Attribute::Class, EntryClass::Person.to_value()),
245                (Attribute::Name, Value::new_iname("testperson_3")),
246                (Attribute::Uuid, Value::Uuid(user_c_uuid)),
247                (Attribute::Description, Value::new_utf8s("testperson")),
248                (Attribute::DisplayName, Value::new_utf8s("testperson"))
249            )]);
250
251            assert!(op_result.is_ok());
252
253            let user_c = server_txn
254                .internal_search_uuid(user_c_uuid)
255                .expect("Unable to access user");
256
257            assert_eq!(user_c.get_ava_single_uint32(Attribute::GidNumber), None);
258
259            let modlist = modlist!([m_pres(
260                Attribute::Class,
261                &EntryClass::PosixAccount.to_value()
262            )]);
263            server_txn
264                .internal_modify_uuid(user_c_uuid, &modlist)
265                .expect("Unable to modify user");
266
267            let user_c = server_txn
268                .internal_search_uuid(user_c_uuid)
269                .expect("Unable to access user");
270
271            let user_c_uid = user_c
272                .get_ava_single_uint32(Attribute::GidNumber)
273                .expect("gidnumber not present on account");
274
275            assert_eq!(user_c_uid, 0x7c55971b);
276        }
277
278        let user_d_uuid = uuid!("36dc9010-d80c-404b-b5ba-8f66657c2f1d");
279        // Test that an entry when modified to have posix attributes will have
280        // it's gidnumber generated.
281        {
282            let op_result = server_txn.internal_create(vec![entry_init!(
283                (Attribute::Class, EntryClass::Account.to_value()),
284                (Attribute::Class, EntryClass::Person.to_value()),
285                (Attribute::Name, Value::new_iname("testperson_4")),
286                (Attribute::Uuid, Value::Uuid(user_d_uuid)),
287                (Attribute::Description, Value::new_utf8s("testperson")),
288                (Attribute::DisplayName, Value::new_utf8s("testperson"))
289            )]);
290
291            assert!(op_result.is_ok());
292
293            let user_d = server_txn
294                .internal_search_uuid(user_d_uuid)
295                .expect("Unable to access user");
296
297            assert_eq!(user_d.get_ava_single_uint32(Attribute::GidNumber), None);
298
299            let modlist = modlist!([m_pres(
300                Attribute::Class,
301                &EntryClass::PosixAccount.to_value()
302            )]);
303            server_txn
304                .internal_modify_uuid(user_d_uuid, &modlist)
305                .expect("Unable to modify user");
306
307            let user_d = server_txn
308                .internal_search_uuid(user_d_uuid)
309                .expect("Unable to access user");
310
311            let user_d_uid = user_d
312                .get_ava_single_uint32(Attribute::GidNumber)
313                .expect("gidnumber not present on account");
314
315            assert_eq!(user_d_uid, 0x757c2f1d);
316        }
317
318        let user_e_uuid = uuid!("a6dc0d68-9c7a-4dad-b1e2-f6274b691373");
319        // Test that an entry when modified to have posix attributes, if a gidnumber
320        // is provided then it is respected.
321        {
322            let op_result = server_txn.internal_create(vec![entry_init!(
323                (Attribute::Class, EntryClass::Account.to_value()),
324                (Attribute::Class, EntryClass::Person.to_value()),
325                (Attribute::Name, Value::new_iname("testperson_5")),
326                (Attribute::Uuid, Value::Uuid(user_e_uuid)),
327                (Attribute::Description, Value::new_utf8s("testperson")),
328                (Attribute::DisplayName, Value::new_utf8s("testperson"))
329            )]);
330
331            assert!(op_result.is_ok());
332
333            let user_e = server_txn
334                .internal_search_uuid(user_e_uuid)
335                .expect("Unable to access user");
336
337            assert_eq!(user_e.get_ava_single_uint32(Attribute::GidNumber), None);
338
339            let modlist = modlist!([
340                m_pres(Attribute::Class, &EntryClass::PosixAccount.to_value()),
341                m_pres(Attribute::GidNumber, &Value::Uint32(10002))
342            ]);
343            server_txn
344                .internal_modify_uuid(user_e_uuid, &modlist)
345                .expect("Unable to modify user");
346
347            let user_e = server_txn
348                .internal_search_uuid(user_e_uuid)
349                .expect("Unable to access user");
350
351            let user_e_uid = user_e
352                .get_ava_single_uint32(Attribute::GidNumber)
353                .expect("gidnumber not present on account");
354
355            assert_eq!(user_e_uid, 10002);
356        }
357
358        // Test rejection of important gid values.
359        let user_f_uuid = uuid!("33afc396-2434-47e5-b143-05176148b50e");
360        // Test that an entry when modified to have posix attributes, if a gidnumber
361        // is provided then it is respected.
362        {
363            let op_result = server_txn.internal_create(vec![entry_init!(
364                (Attribute::Class, EntryClass::Account.to_value()),
365                (Attribute::Class, EntryClass::Person.to_value()),
366                (Attribute::Name, Value::new_iname("testperson_6")),
367                (Attribute::Uuid, Value::Uuid(user_f_uuid)),
368                (Attribute::Description, Value::new_utf8s("testperson")),
369                (Attribute::DisplayName, Value::new_utf8s("testperson"))
370            )]);
371
372            assert!(op_result.is_ok());
373
374            for id in [
375                0,
376                500,
377                GID_REGULAR_USER_MIN - 1,
378                GID_REGULAR_USER_MAX + 1,
379                GID_UNUSED_A_MIN - 1,
380                GID_UNUSED_A_MAX + 1,
381                GID_UNUSED_B_MIN - 1,
382                GID_UNUSED_B_MAX + 1,
383                GID_UNUSED_C_MIN - 1,
384                GID_UNUSED_D_MAX + 1,
385                u32::MAX,
386            ] {
387                let modlist = modlist!([
388                    m_pres(Attribute::Class, &EntryClass::PosixAccount.to_value()),
389                    m_pres(Attribute::GidNumber, &Value::Uint32(id))
390                ]);
391                let op_result = server_txn.internal_modify_uuid(user_f_uuid, &modlist);
392
393                trace!(?id);
394                assert_eq!(op_result, Err(OperationError::PL0001GidOverlapsSystemRange));
395            }
396        }
397
398        assert!(server_txn.commit().is_ok());
399    }
400}