Warning

This is a design page. It was used to design and discuss the initial implementation of the change. However, the state of this document does not necessarily correspond to the current state of the implementation since we do not keep this document up to date with further changes and bug fixes.

Running SSSD as a non-root user

Related ticket(s):

Currently, all SSSD processes run as the root user. However, if one of the processes was compromised, this might lead to compromising the whole system, especially if additional measures like SELinux were not enabled. It would improve security if instead SSSD was running as its own private user, This design page summarizes what would be needed to run sssd as a non-privileged user and all the cases that currently require a root user.

This is a general use-case, following the principle of least privilege. The processes should not run as root unless they really need the root privileges.

At a higher level, the changes would amount to:

  • A new system user would be created. This user must be added in sssd.spec during the %pre section.

  • Files that were used by sssd and previously owned by root should now be owned as the sssd user. This includes the LDB databases.

  • Responders and back ends would drop privileges and become the sssd user as soon as possible, ideally as the first action after startup.

  • Short-lived processes that are spawned by sssd_be but might still require elevated privileges would be setuid root.

The changes to individual binaries and files are described in more detail below. After the changes are implemented, the code that runs as root will be reduced to the monitor process and the setuid helpers.

The sssd will run as a new system user called simply sssd. We do not need to have the UID fixed across systems as no files owned by SSSD are shared among different systems. The user will be simply added during the %pre phase:

%pre
getent group sssd >/dev/null || groupadd -r sssd
getent passwd sssd >/dev/null || useradd -r -g sssd -d / -s /sbin/nologin -c "User for sssd" sssd

As it’s common practice for system users, the shell will be /sbin/nologin so the user cannot log in into the system.

To be on the safe side, sssd will allow configuring the user to run as. This option will also allow root, to allow users to keep the old behaviour around in case they hit a bug with the unprivileged process. As a first step, we will include these options, but leave the default as ‘root’. When we’re certain the non-root sssd works for most users as a non-privileged user, we will switch the default to the sssd user.

The goal is for the “worker” processes (that is, both responders and providers) to drop the root privileges as soon as possible - typically right after startup, or alternatively after completing any work that requires root privileges such as opening a file. Because the processes might have to keep the root privileges after startup, the monitor process would still be running as root.

The monitor process would keep running as root. This is in order to be able to fork and exec processes that are initially privileged without making them all setuid. As a future enhancement, the process management functionality of the monitor will be delegated to systemd (see ticket #2243).

The responder processes are by nature ‘readers’ that mostly read data from cache and request cache updates from the back end processes.

The NSS responder can drop privileges after startup. The files that the NSS responder reads (sysdb, confdb, NSS pipe) and writes (memory cache, debug logs, NSS pipe) will be owned by the sssd user.

The PAM responder can drop privileges after startup. The files that the PAM responder reads (sysdb, confdb, PAM public pipe) and writes (debug logs, PAM pipe) will be owned by the sssd user.

In order to keep the privileged pipe only owned by the root user, we would open the pipe prior to becoming user and pass the file descriptor.

The InfoPipe responder can drop privileges after startup. The files that the InfoPipe responder reads (sysdb, confdb) and writes (debug logs, PAM pipe) will be owned by the sssd user.

Contrary to other responders, the InfoPipe responder doesn’t have a public pipe. The InfoPipe responder also binds to the system bus, we must also convert the bus policy file to allow the sssd user to bind to the bus.

Currently there is also functionality to modify sssd.conf from the InfoPipe. During the feature design review, it was suggested that a configuration interface doesn’t belong to the InfoPipe code at all and should be moved to a separate helper. Until that is done, the InfoPipe responder must keep running as root. The work on splitting the InfoPipe is tracked by https://pagure.io/SSSD/sssd/issue/2395

The Autofs, SUDO and SSH responders only read from the sysdb, confdb and their respective UNIX public pipes. These responders also only write to the debug logs and the public pipe, all of which would be owned by the sssd user. This means the Autofs, SUDO and SSH responders can drop privileges right after startup.

The providers are dynamically loadable libraries that are loaded by the sssd_be process. After startup, the sssd_be process dlopens the provider library and dlsyms the handlers. During sssd operation, the sssd_be process mostly unpacks requests arriving on the SBUS and calls the provider-specific handlers.

Several areas of the initialization still require elevated privileges:

  • Checking for principals in the keytab

  • Checking for user TGTs to be renewed

Therefore, the initialization is still performed with root privileges and sssd_be drops to a non-root user post initialization. See the “Future Enhancements” section for ideas on reducing the code that runs as root during initialization even further.

The purpose of the short-lived processes is to avoid blocking calls by performing an otherwise blocking action in a completely separate process.

The ldap_child subprocess primes the credential cache used to establish GSSAPI-encrypted connection. In order to do so, the ldap_child process needs to be able to read the keytab, which is readable by root only. Therefore, the ldap_child process is setuid root, with permissions set to 4750 to make sure only the sssd user can run the ldap_child process. As soon as the credentials are obtained, the ldap_child drops privileges and continues running as the sssd user – hence also the resulting ccache is owned by the sssd user.

The user krb5_child runs as depends on how the SSSD back end is set up. In the simplest case, where neither validation nor FAST are used, the krb5_child can drop privileges to the user who is logging in after startup and runs unprivileged except for the initialization part.

In case either validation or FAST are used, part of the krb5_child runs as root. Once the resulting ccache is validated using the keytab, the krb5_child process drops privileges to the user who is logging in.

See the “Future Enhancements section” for discussion of using the MEMORY ccache to reduce the time krb5_child runs as root.

In general, we can’t make assumptions on what the PAM module we wrap using the proxy backend requires, so at least the part of proxy child that runs the PAM conversation should run as root. During development, we should consider splitting the proxy_child into a small setuid helper that would still run privileged and only wrap the PAM module and the rest of the proxy_child that would run unprivileged.

The gpo_child process connects to a SMB share, downloads a GPO policy file and stores it locally, by default in /var/lib/sss/gpo_cache. The gpo_child authenticates to the SMB share using Kerberos; the ccache, as created by ldap_child is already accessible to the sssd user. Since that directory would be owned by the sssd user, the gpo_child could run unprivileged.

The SSH helpers already run non-privileged. sss_ssh_knownhostsproxy runs as the user who initiated the SSH session. sss_ssh_authorizedkeys runs as the user specified with the AuthorizedKeysCommandUser directive in sshd_config.

There are two general kinds of command line tools we ship with the SSSD - tools that manage accounts in the local backend and SSSD management tools. All tools check if they are executed by root currently. I think this check makes sense and should stay because all the tool are intended for administrative purposes only.

Some of the tools can be changed to drop privileges. However, the attach surface of these tools is small, so changing them is not a priority. This effort is rather tracked in the Future Enhancements.

The tools either write (sss_useradd, userdel, usermod, sss_groupadd, groupdel, groupmod) or read (sss_groupshow) the sssd.ldb file. But additionally, these tools also set the SELinux context of the user. Since there is no capability to call semanage, setting the context still requires root privileges.

These two tools function similarly to the local backend management tools, except they manipulate the domain cache. The cache is also owned and writable by the sssd user, so would be safe to drop privileges here, too.

The sss_debuglevel tool changes the debug level of sssd on the fly. The tool writes new debug level values to the confdb (owned by sssd) and touches sssd.conf (ownership tbd). The tool can drop privileges to sssd after startup.

The sss_obfuscate tool is written in Python and manipulates the sssd.conf file by obfuscating the input and using it as a value of the ldap_default_authtok configuration option. For dropping privileges of the sss_obfuscate tool, we can use the python bindings of libcap-ng. Again, making this tool non-privileged is not a priority.

This part of the design page summarizes which external resources, typically file system objects currently require SSSD to have elevated privileges.

For filesystem objects, we can either change their owner to the sssd local user, add an ACL or open them as the privileged process and pass the file descriptor.

  • Filesystem path: /etc/sssd/sssd.conf

  • Current owner and permissions: root.root 0600

  • Read by: The monitor process

  • Written to by: The InfoPipe responder and users of the configAPI, such as sss_obfuscate or authconfig

  • Change: Currently the permissions will stay the same as the monitor process and the InfoPipe still run as root

  • Filesystem path: /var/log/sssd/*.log

  • Current owner and permissions: root.root 0600

  • Read by: N/A, only externally by admin

  • Written to by: monitor, providers, responders, child processes

  • New owner and permissions: sssd.sssd 0600

  • Filesystem path: /var/lib/sss/db/config.ldb

  • Current owner and permissions: root.root 0600

  • Read by: responders, providers, monitor, command-line tools

  • Written to by: The monitor process, sssd-ad (a single confdb_set call), sss_debuglevel, sssd_ifp

  • New owner and permissions: sssd.sssd 0600

  • Filesystem path: /var/lib/sss/db/cache_$domain.ldb

  • Current owner and permissions: root.root 0600

  • Read by: responders, providers, command-line tools

  • Written to by: sssd_be, the CLI tools

  • New owner and permissions: sssd.sssd 0600

  • Filesystem path: /var/lib/sss/mc/{passwd,group}

  • Current owner and permissions: root.root 0644

  • Read by: The SSS NSS module

  • Written to by: The NSS responder

  • New owner: sssd.sssd permissions will stay the same

  • Filesystem path: configurable, /etc/krb5.keytab by default

  • Current owner and permissions: root.root 0600

  • Read by: LDAP, KRB5, IPA, AD providers, krb5_child, ldap_child

  • Written to by: sssd_be, the CLI tools

  • Change: No change at the moment. The keytab will be kept readable by the root user only

  • Filesystem path: Configurable, only if FILE or DIR based cache is used, which is not the default anymore

  • Current owner and permissions: the user who logged in, 0600

  • Read by: KRB5, AD, IPA, krb5_child, libkrb5 externally

  • Written to by: krb5_child

  • Change: No change, the credential cache will still be written as the user in question

  • Filesystem path: /var/lib/sss/db/ccache_$domain

  • Current owner and permissions: root.root 0600

  • Read by: AD, IPA and LDAP providers (coded up in LDAP provider tree)

  • Written to by: ldap_child

  • No change needed since ldap_child will run as the sssd user in the new design

  • New owner and permissions: sssd.sssd 0600

  • Filesystem path: /var/lib/sss/pubconf/*

  • Current owner and permissions: root.root. The directory has permissions of 0755, the files 0644

  • Read by: libkrb5

  • Written to by: LDAP, KRB5, IPA, AD providers, krb5_child, ldap_child

  • New owner and permissions: Both directory and files will be owned by sssd.sssd, the permissions will stay the same

  • Filesystem path: /etc/selinux/targeted/logins

  • Current owner and permissions: root.root. The directory has permissions of 0755, the files 0644

  • Read by: pam_selinux

  • Written to by: IPA provider

  • Change: libsemanage will be used to set the labels instead. Since setting the label is a privileged operation and sssd_be runs unprivileged, setting the label was moved to a separate child process, selinux_child

  • Filesystem path: /var/lib/sss/pipes/

  • Current owner and permissions: root.root. The directory has permissions of 0755, the files 0666. There is one pipe per responder.

  • Read by: client modules, all responders except InfoPipe

  • Written to by: client modules, responders

  • New owner and permissions: Both directory and files will be owned by sssd.sssd, the permissions will stay the same

  • Filesystem path: /var/lib/sss/pipes/private/pam

  • Current owner and permissions: root.root. The directory has permissions of 0700, the files 0600. Only the PAM responder uses the private pipe.

  • Read by: PAM responder

  • Written to by: PAM client module

  • New owner and permissions: The directory will be owned by sssd.sssd, the file will stay the same

  • Filesystem path: /var/lib/sss/pipes/private/sbus-dp_$domain.$PID

  • Current owner and permissions: root.root. The directory has permissions of 0700, the files 0600.

  • Read by: Responders

  • Written to by: Data Provider

  • New owner and permissions: Both directory and files will be owned by sssd.sssd, the permissions will stay the same

  • Filesystem path: /etc/krb5.conf

  • Read by: libkrb5

  • Written to by: The IPA and AD providers “touch” the file in order to make libkrb5 re-read it

  • Change: The file can be opened before dropping privileges and we can keep the fd around. Alternatively, the modification can be performed with a setuid helper

There is a new option called user that allows the administrator to configure the user sssd runs as. Please note that it makes sense to only use either root or the user sssd was configured with.

Test ordinary SSSD operations. Everything must work as it used to before. Pay special attention to operations that involve the short-lived processes, like GSSAPI LDAP provider authentication or Kerberos user authentication.

Upgrade testing must be performed as well.

During the design or implementation, we identified several ideas for improvement. Even though we don’t need to implement these now, it makes sense to keep the description in this design page for future.

In 1.12.3, sssd.conf is still owned by root, mostly because there is a number of programs like authconfig that generate sssd.conf as root. Moreover, in enterprise setups, the sssd.conf would be pushed to the client with a tool such as puppet that would still use the same privileges.

Therefore, even rootless sssd needs to handle sssd.conf owned by root, at least for the time being. We can even chown the file to sssd user after startup or move the write-operation in InfoPipe? to a privileged helper.

Some command line tools can be run unprivileged - see the section called “Command Line Tools”. However, changing them is not a priority as they are short-lived and in general only accept switches, not free-form input.

It was proposed on the sssd-devel list that the initialization of the sssd_be process is split into a privileged and non-privileged function. The back end would open all providers, call the privileged initialization functions and then drop privileges. Currently all initialization is done as root, which is not strictly required in many setups.

Sumit proposed that the keytab is read to a MEMORY type after child process startup so krb5_child and ldap_child can drop root privileges sooner. There are even some proof-of-concept patches on sssd-devel

Once we need to not only drop privileges but also retain some capability (CAP_AUDIT comes to mind), we’ll need to use something like libcap-ng instead of handling capabilities ourselves with prctl

The downside is obviously the extra dependency, but libcap-ng has a small footprint and is already used by packages that are present on most, if not all, modern GNU/Linux installations, such as dbus.

We would keep the existing code around as a fallback for environments that don’t have the libcap-ngs library available, such as non-Linux systems or embedded systems. Because the code wouldn’t be enabled by default, it’s important to have unit tests for the privilege drop. For unit testing both options (libcap-ng and our own code), uid_wrapper and nss_wrapper are the best choice.

During design review, it was also proposed to look into merging the ldap_child and krb5_child as the code performs similar tasks The new krb5_child would act as an ldap_child based on a command line option value.