|From: Roland Bauerschmidt <rb@debian.org> |Newsgroups: gmane.linux.debian.devel.general |Subject: Re: [RFC] adding system users: which is the best way?? |Date: Mon, 1 Dec 2003 09:36:51 +0100 | |Steve Greenland wrote: |> If you're not going to re-use the ids (a good idea), then you might as |> well leave a username associated with it, so that the admin identify |> *why* it was used. |> |> My personal opinion and policy is that user ids should never be re-used. |> I realize that on very large networks with many users (possibly |> distributed timewise, like a univesity undergraduate system), this may |> not be possible, but it ought to be true for at least the "system" |> users. | |That's a good idea. I'll try to add that as an option to adduser. | |-- Roland This is not yet implemented, but should be easy enough to build- Greetings Marc
After thinking more about it, the naive approach of simply looking for the last used UID in the range to be used and taking the first one after that one is bad since that uid could also have been used and deleted again. We'd need to use a persistent state file, which makes the change less trivial. Greetings Marc
tags #248500 confirmed thanks
Hello, on a local system I wanted to fix the problem talked about in this bug, and for us, the "naive" approach of selecting a UID one higher than the highest currently in the range was good enough. I locally modified adduser on my system to do that, and I created a patch against 3.80 in case it would be at all useful. The patch uses a "NO_REUSE_IDS" option, which activates the new behavior if set to "yes". It implements the change in the "first_avail_id()" routine. I am curious about the state-file approach, though; if that is introduced with a new version of adduser, what do you do about "holes" in the passwd file that existed prior to installation of that new version? Maybe do something similar to the naive approach once, to pre-fill the state file? Cheers, Aaron
Hi Aaron Thank you for the patch. "NO_REUSE_IDS" sounds a bit strange, I'd rather call it "REUSE_IDS" and set it to "yes" by default. That would be the only feasible solution. Jörg
Hast Du den applied? Ich find "reuse" nicht in der Konfigdatei und auch nicht in adduser. Grüße Marc
Errm. That one was not meant to go to the BTS. I apologize. Greetings Marc
Hi We need to have a persistent statefile if we want to avoid to reuse a uid which was already used by some other user. Think of the following scenario: adduser foo -> foo gets uid X deluser foo -> uid X is no longer used adduser bar -> bar gets uid X So this uid is reused, even with the "always increment the hightest already used uid by one and assign it to the new user"-algorithm. But the whole thing of making this value persistent seems rather ugly to me. Need to think some time about that, maybe I have a better idea to solve that issue (a file for a single value). Joerg
Well, we already have this "state file" in the system, namely
"/etc/passwd". We could create a disabled system user with a system UID
(for example UID 999, and for example named "adduser"), which would be
managed by the adduser scripts, and which would hold in the GECOS field
the highest ever used user ID in the system.
So, every time a new user is added to the system, the adduser scripts
would update the GECOS field of this "adduser" system user.
This "adduser" user could be disabled ("passwd -l adduser") as it is
never meant to login into the system.
This solution keeps compatibility with the old scheme of things, and
needs no special state file, while solves the problem.
What do you think?
*UGH* I'd prefer a /var/lib/adduser/state over abusing /etc/passwd any time. Greetings Marc
*UGH* I'd prefer a /var/lib/adduser/state over abusing /etc/passwd any time. Greetings Marc
Hi, I’m interested in working on bug #248500 and have been looking into how UID allocation is currently handled by adduser. The main issue I’m trying to address is the distinction between avoiding currently allocated UIDs and avoiding reuse of UIDs that have been allocated in the past. The latter requires some form of persistent state, since once an account is removed there is no longer an entry in /etc/passwd from which adduser can determine that the UID was previously used. I also looked at the UID/GID pool functionality introduced in #1067845, in particular UID_POOL and RESERVE_UID_POOL. This provides a mechanism for reserving known UID assignments, but it does not appear to constitute a general history of previously allocated UIDs. I’m therefore interested in investigating whether the existing UID pool mechanism could be extended or reused to implement the semantics requested by #248500, while considering a few questions: 1. How should UIDs assigned by adduser be persisted so that they remain unavailable after the corresponding account is removed? 2. What should happen when an account is recreated, should it receive its previous UID or should that UID remain permanently unavailable? 3. How should existing systems be handled, where previously used UIDs are no longer present in /etc/passwd and therefore cannot be reconstructed reliably? 4. How should manually specified UIDs (--uid) interact with the mechanism? 5. Should system UIDs and regular user UIDs follow the same policy? 6. How should concurrent adduser invocations be handled to avoid allocating the same UID? Before starting an implementation, I’d like to check whether this approach matches the intended direction for #248500, and whether there are any previous proposals or design constraints I should take into account. If the bug is still considered open for development, I’d be happy to investigate this further and prepare a patch with tests. If, however, the issue has already been addressed by recent changes or is no longer considered relevant, I think the bug can be closed. Thanks, Juan Munoz Salsa: Juaesm
Hi Juan, thanks for helping with adduser! adduser has grown a state file in the past, it's a simple, persistent key-value store that could store the last UID being assigned by adduser and then just increment from there. This leaves the use case when an account was explicitly created by the admin with a forced uid and then removed later, adduser will then happily reuse that uid when the counter reaches that value. I think we must ignore that since those operations could be done with useradd/userdel or by directly editing /etc/passwd without adduser knowing. I don't think that the pool mechanism is the right means to address this. pool is more a method to reserve uids, and adduser should actually avoid assigning uids/gids See above, you can use a persistent counter. That uid is then lower than the counter and will not be reused. I think it would be sane to start the counter at FIRST_*_UID and then iterate until the first free uid is found. then initialize the counter and use that uid. The wish of the local admin wins and turns off the automatism. Yes, with dedicated counters. That's a good point, is adduser not yet locking itself against concurrent execution? Please let me know if any of those ideas are bad ideas. I would be happy if you'd work on that. This bug is the next I would have tackled myself, but I'll happily leave that for you. Just remember rebasing your debian/latest branch on the main repo's before you branch. Greetings Marc
Hi Marc, Thanks for the detailed and fast feedback and for the opportunity to work on this. The persistent counter approach makes sense to me, and I agree that the UID pool mechanism is not the right abstraction for this. I’ll look into the existing state file implementation and how separate counters for regular and system UIDs could fit into it. I’ll also investigate the current locking behavior of adduser and make sure concurrent invocations cannot result in the same UID being allocated. For existing installations, I’ll look at initializing the counters by scanning from the configured FIRST_*_UID range until the first available UID is found, as you suggested. Thanks again. I’ll get started on it. Greetings, Juan Muñoz Debian: Juaesm
Thank you very much for working on that, I always love to close bugs that are 20 years old. If I can help or comment, let me know. https://www.incluesion.de/impressum has my messenger data if you need more direct contact. Greetings Marc
Hi Marc, I have a working implementation of the counter, with tests, and running it raised four questions I would rather settle with you than guess. 1. A single counter does not survive sub-ranges The naive reading of "store the last uid and increment from there" breaks when a range is requested explicitly: # REUSE_IDS=no adduser --firstuid 5000 --lastuid 5010 rhigh # gets 5000, counter=5000 addgroup --firstgid 2090 --lastgid 2093 rlow warn: No GID is available in the range 5001-2093 (FIRST_GID - LAST_GID) fatal: The group `rlow' was not created. The 2090-2093 range is completely empty, but the counter of the default range pushed the floor above its ceiling. My fix is to make the range part of the state file key, so each range carries its own counter: @global:last_id_1000_59999=1006:last_sys_id_100_999=104 The guarantee becomes "an id is not handed out twice within the range it came from". Does that match what you had in mind, or would you rather have a single counter with a different rule for sub-ranges? 2. Should an explicit range turn off the automatism? You wrote that for --uid "the wish of the local admin wins and turns off the automatism". Asking for --firstuid/--lastuid looks like the same kind of explicit wish to me, and treating it that way would also leave firstlastuidgid.t untouched. But it does mean that an admin who always carves ranges never gets the guarantee. Your call. 3. The default is more expensive than it looks With the counter always on, 122 assertions fail in firstlastuidgid.t, firstlastuidgid_orig.t, uidgidpool.t and suidsgidpool.t. They are not stale: they encode the current contract, that adduser picks the first free id of the range. After any deletion that stops being true. So I currently default REUSE_IDS to yes, which leaves every existing installation and the whole test suite untouched, and lets an admin opt in. Flipping it to no is a one-word change plus a NEWS.Debian entry, but it needs those tests adapted. Which do you prefer? I did not want to rewrite assertions in firstlastuidgid*.t anyway, since you are reworking that file in wip/new-firstlastuidgid. 4. Where should the counter live inside the state file? The store is keyed by user name and a counter belongs to no user. Rather than storing it under a fake user name, I taught _read_state() and _write_state() about a reserved '@global' record, so delete_state_user() can not take the counters with it. That touches the format of your module, so tell me if you prefer something else. 5. Is a second knob wanted at all? You never mentioned configuration, so this may be over-engineering on my side: I added ID_COUNTER_INIT=first_free|highest, because starting at the first free id (as you suggested) cannot cover accounts that were deleted before the state file existed, which was Aaron Hall's unanswered question from 2006. If you would rather not have that option, I will drop it. Unrelated to this bug, I also came across a couple of small things while reading the code (a duplicated exit code value that contradicts the manpage, among others). I will file those separately rather than mixing them in here.
Hi Juan, I would say "if a range is explicitly requested, that overrides everything and the accounts gets the first free uid in that range without state being updated". That would also help to keep the state file small. Yes, it should turm off the automatism. I would offer to adapt the tests to save you from that tedious work. I think that's worth it. That rework is done, I am fine with the -old file going away now instead of spending an hour fixing it. The new test was vibecoded and I kept the old one around to be sure. I think I am now confident enough that the new test is fine. @global, _adduser, _adduser-internal, _internal or any other string that doesn't constitute a valid user name is fine. I like a gold plate on code, so if it's already there, go ahead. It's a bit ugly that we now have a config option that is only used once on adduser's first run. Yes, please. Greetings Marc
Hi, I have opened a merge request implementing this, following the decisions Marc took in this log: https://salsa.debian.org/debian/adduser/-/merge_requests/144 adduser now keeps the highest uid and gid it has handed out in a reserved @global record in /var/lib/adduser/state, and does not hand an id to a new account if it has already been used once. The counter is written inside the existing lock and only when the run succeeds.