From Fedora Project Wiki

User and group handling in packages

Author: Tom 'spot' Callaway
Revision: 0.01
Initial Draft: Wednesday June 13, 2007
Last Revised: Wednesday, June 13, 2007


Rationale

The need for caution

It is important to be extremely careful when adding users and groups at installtime. We need to be sure of the following:

  • We do not create easily exploitable security vulnerabilities
  • We do not break RPM transactions trying to manage users and groups

When adding a user or group at package install-time, there are four system states:

  • The user/group does not exist on the system
  • The user/group exists from a previous package creating it
  • The user/group is a normal user, overlapping the namespace (e.g. amanda)
  • The user/group is pre-created by the administrator with a specific UID/GID

We need to cover all four of these cases.

Case 1: The user/group does not exist on the system

This is the simplest case, we would simply create the new user/group.

Case 2: The user/group exists from a previous package creating it

This is difficult to detect. We cannot assume that a created user/group came from the previous package simply because we're in an upgrade transaction.

By creating and leveraging a "Fedora User Manifest" and "Fedora Group Manifest" we can track when packages add users or groups, and refer to that manifest. The package can check to see if the user/group exists on the system, and then check to see if the user/group is logged in the local system manifest. If so, we do not need to do anything.

Case 3: The user/group is a normal user, overlapping the namespace (e.g. amanda)

The user and group manifests provide a mechanism to determine whether the user is an overlapping "normal" user/group, as opposed to a package added user/group. When the package sees that the user/group exists, and is not in the manifest, it knows it is in this case. This is also a case where security is a concern, someone who gains access to the "amanda" user, then has an admin install the Fedora amanda package could forseeably then gain access to the amanda package. The package needs to take actions to prevent this case from occurring.

Case 4: The user/group is pre-created by the administrator with a specific UID/GID

The user and group manifests provide a controlled, simple mechanism for administrators to "pre-create" users and groups to specific UID/GID settings that would otherwise be defined by packages. This task can be accomplished before adding packages to an installed system, or during the kickstart (don't install packages which add users during the install, then, in %post, create the users/groups, add entries to the manifests and yum install the additional "user creating" packages). Packages which create users and groups will be required to register on a wiki page at fedoraproject.org, so SAs can track which packages add users.

Avoidance

If an existing system user/group would function effectively for an application, it should use that user/group instead of creating its own. Packagers should avoid user/group creation whenever possible, as it adds significant complication to the packaging.

fedora-uidgid-tools

In order to try to simplify user/group creation in Fedora packages, I've created a simple toolkit called fedora-uidgid-tools. This package provides:

  • /etc/sysconfig/fedora-user-manifest : A text manifest where packages record adding users.
  • /etc/sysconfig/fedora-group-manifest : A text manifest where packages record adding groups.
  • /sbin/fedora-uidgidcheck.sh : A script that checks a username or groupname for presence on the system and the fedora user/group manifest files, and returns exit codes for the various cases.

The sources can be found here: http://people.redhat.com/tcallawa/fedora-uidgid-tools/

A sample implementation

Here is a sample spec file, showing how a user and a group can be added, using the fedora-uidgid-tools:

%global username tester
%global groupname testing

Name: test
Version: 1.0
Release: 1
License: GPL
Summary: Test
Group: Applications/System
URL: http://test.com
Source0: testfiles.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch
Requires(pre): shadow-utils, fedora-uidgid-tools
Requires(post): coreutils, fedora-uidgid-tools

%description
test

%prep

%setup

%build

%install
rm -rf %{buildroot}

mkdir -p %{buildroot}%{_datadir}/%{name}/
install *fish %{buildroot}%{_datadir}/%{name}/
find %{buildroot} -type f | sed 's|^%{buildroot}||g' &> %{username}-user-owned-files.list
echo %{_datadir}/%{name}/ >> %{username}-user-owned-files.list
cp %{username}-user-owned-files.list %{buildroot}%{_datadir}/%{name}/
cp %{buildroot}%{_datadir}/%{name}/%{username}-user-owned-files.list %{buildroot}%{_datadir}/%{name}/%{groupname}-group-owned-files.list


%clean
rm -rf %{buildroot}

%pre
/sbin/fedora-uidgidcheck.sh -g %{groupname} || GROUPCHECK=$?
if [ -n $GROUPCHECK ] ; then
if [ "$GROUPCHECK" == "75" ] ; then
groupadd -f -r %{groupname} && \
echo %{groupname}:%{name} >> %{_sysconfdir}/sysconfig/fedora-group-registry || :
fi
fi
/sbin/fedora-uidgidcheck.sh -u %{username} || USERCHECK=$?
if [ -n $USERCHECK ] ; then
if [ "$USERCHECK" == "75" ] ; then
useradd -r -g %{groupname} -d %{_datadir}/%{name} -s /sbin/nologin \
-c "Spot Test User" %{username} && \
echo %{username}:%{name} >> %{_sysconfdir}/sysconfig/fedora-user-registry || :
fi
fi

%post
/sbin/fedora-uidgidcheck.sh -u %{username} || USERCHECK=$?
if [ -n $USERCHECK ] ; then
if [ "$USERCHECK" == "70" ] ; then
for i in <code>cat %{_datadir}/%{name}/%{username}-user-owned-files.list</code>; do
chown --quiet root $i
done
fi
fi
/sbin/fedora-uidgidcheck.sh -g %{groupname} || GROUPCHECK=$?
if [ -n $GROUPCHECK ] ; then
if [ "$GROUPCHECK" == "70" ] ; then
for i in <code>cat %{_datadir}/%{name}/%{groupname}-group-owned-files.list</code>; do
chgrp --quiet root $i
done
fi
fi

%files
%defattr(-,root,root)
%attr(-,%{username},%{groupname}) %{_datadir}/test/

%changelog
* Wed Jun 13 2007 Tom "spot" Callaway <tcallawa@redhat.com> 1.0-1
- test

Notes

The homedir passed to useradd should be a directory created and owned by the package, with appropriately restrictive permissions. One good choice for the location of the directory is the package's data directory in case it has one.

User accounts created by packages are rarely used for interactive logons, and should thus almost always use /sbin/nologin as the user's shell.

We want to invoke groupadd explicitly instead of relying on useradd to create the group for us. This is because useradd alone would fail if the group it tries to create already existed. Note: even though the useradd manual page doesn't mention it (as of FC6), -g GROUPNAME appears to imply -n.

We never remove users or groups created by packages. There's no sane way to check if files owned by those users/groups are left behind (and even if there would, what would we do to them?), and leaving those behind with ownerships pointing to now nonexistent users/groups may result in security issues when a semantically unrelated user/group is created later and reuses the UID/GID. Also, in some setups deleting the user/group might not be possible or/nor desirable (eg. when using a shared remote user/group database). Cleanup of unused users/groups is left to the system administrators to take care of if they so desire.

In some cases it is desirable to create only a group without a user account. Usually this is because there are some system resources to which we want to control access by using that group, and a separate user account would add no value. Examples of common such cases include (but are not limited to) games whose executables are setgid for the purpose of sharing high score files or the like, and/or software that needs exceptional permissions to some hardware devices and it wouldn't be appropriate to grant those to all system users nor even only those logged in on the console. In these cases, apply only the group sections as described in the example.