[SSSD] Design discussion: SSSD running as a non-root user

Jakub Hrozek jhrozek at redhat.com
Wed Jul 23 13:38:13 UTC 2014


Hi,

we'd like the SSSD in 1.12.1 to run as a non-privileged user. To
summarize the discussions we had, I created the following design page:
    https://fedorahosted.org/sssd/wiki/DesignDocs/NotRootSSSD

For your convenience, the text of the page is also included below.

I'll be glad for comments and another round of discussion.

= Running SSSD as a non-root user =

Related ticket(s):
 * https://fedorahosted.org/sssd/ticket/2370

=== Problem statement ===
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.

== Scope of the changes ==
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.
    * It is important that no code linked from libkrb5 runs as root. This has consequences for accessing the system keytab, which must be readable by the sssd user.
    * 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.
    * Places that blindly check for UID==0 must be converted to handling EPERM/EACCESS gracefully.

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 krb5_child.

== A new system user ==
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.

The name of the user should be configurable at configure-time as different distributions might decide on using different users.

== Dropping privileges of the SSSD processes ==
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.

=== Using libcap-ng to drop the privileges ===
Even though we already have some code to drop privileges in the `krb5`provider, we should leverage the [https://people.redhat.com/sgrubb/libcap-ng/ libcap-ng] project for privilege drop. The added benefits over doing the privilege drop ourselves are:
 * libcap-ng is tested and used by many other packages already. For security-sensitive code, it's advisable to re-use existing code rather than hit the same mistakes someone else did already.
 * libcap-ng also allows to work with capabilities. For some cases, this might be beneficial in future, for instance, we might need to retain the auditing capability.

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 Linux installations, such as dbus.

We should 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), [http://cwrap.org/uid_wrapper.html uid_wrapper] and [http://cwrap.org/nss_wrapper.html nss_wrapper] are the best choice.
 
=== Monitor (sssd) ===
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).

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

==== NSS responder ====
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.

==== PAM responder ====
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.

==== !InfoPipe responder ====
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.

Moreover, as the !InfoPipe responder allows to change the configuration
(this functionality is used by the OpenLMI SSSD provider), the !InfoPipe
responder should be able to write to the `sssd.conf` file. Therefore, the
`sssd.conf` file should be writable by the sssd user, too. Alternatively,
another setuid helper can be created to write the configuration changes
with augeas calls.

==== Autofs, SUDO and SSH responders ====
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.

=== Providers  ===
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.

We have two options here - either drop the privileges in the provider library
itself or directly in the `sssd_be` process. Dropping privileges in the
`sssd_be` process has the advantage of modifying only one place and being
sure that no matter the back end, the privileges would always be dropped.

On the other hand, becoming user in the library itself might be
beneficial for scenarios where the back end requires root access for
initialization. Also, if some third-party proprietary module absolutely
requires to run as root, we shouldn't enforce the privilege drop.

If we don't care about the third party modules, we could take an approach
where the provider would drop privilege as soon as it can and after all
the initialization was completed, the sssd_be process would ensure the
privileges are indeed dropped. Because there are no third party back ends
so far and there was no attempt to write one, this is currently safe.

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

==== ldap_child ====
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 needs to have its group ownership set
to the sssd group and have the appropriate access rights. The ldap_child
process does not have to be setuid. The resulting ccache would be owned
by the sssd user and thus accessible to the rest of the SSSD.

==== krb5_child ====
The krb5_child would be split into two parts - one that would perform most
of the Kerberos-related tasks, such as reading the keytab or creating the
ccache on behalf of the sssd user.  Another part, that would be setuid root
would change the ccache ownership to the user who logs in. The new process
(krb5_child_user perhaps) would have the permissions and ownership set to
4750 and root.sssd respectively. That way only the sssd user would be able
to run the new helper.

==== proxy_child ====
The proxy child performs the PAM conversation with the module being
proxied. Normally there is no restriction on the UID to initiate the PAM
conversation, so the proxy child can run unprivileged.

==== gpo_child ====
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.

==== ssh helpers ====
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.

=== Command line tools ===
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.

==== Local back end tools ====
The tools either write (`sss_useradd, userdel, usermod, sss_groupadd,
groupdel, groupmod`) or read  (`sss_groupshow`) the `sssd.ldb` file. Since
the file will be owned by the sssd user, the tools can also run as sssd.

==== sss_seed and sss_cache ====
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 it's safe to drop privileges here, too.

==== sss_debuglevel ====
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 (owned and writable by sssd as well). The tool can drop
privileges to sssd after startup.

==== sss_obfuscate ====
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.

== External resources currently requiring root access ==
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.

=== SSSD configuration file ===
* 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: In order to allow the !InfoPipe responder to make changes to the config file, the file must be owned by sssd.sssd and made writable by the sssd user''
* ''New owner and permissions: sssd.sssd 0600''

=== Debug logs ===
* 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''

=== The configuration database ===
* 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''

=== The on-disk cache (sysdb) ===
* 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''

=== Memory Cache ===
* 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 and permissions: sssd.sssd 0600''

=== Kerberos keytab ===
* 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: Since no Kerberos code should run as root, the keytab must be readable by the sssd group''
* ''New owner and permissions: root.sssd 0640''
* Special care must be taken to make sure other the keytabs as specified in sssd.conf have the right ownership. We already have an upgrade script in %post, we can use it to chown the keytabs

=== Kerberos user credential cache ===
* 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''

=== Kerberos LDAP credential cache ===
* 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''

=== Kerberos kdcinfo files ===
* 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''

=== SELinux user mappings ===
* 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: We need to see if we can use libselinux to set the label first. Either way, the code to set the label needs to run privileged, the SELinux policy mainainer didn't like the idea of changing permissions of the directory''

=== UNIX pipes ===
* 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''

=== UNIX PAM private pipe ===
* 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''

=== Data Provider private pipes ===
* Filesystem path: `/var/lib/sss/pipes/private/sbus-dp_$domain`
* 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: 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''

== Kerberos configuration file ==
* 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''

== How to test ==
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.

== Authors ==
 * Sumit Bose <sbose at redhat.com>
 * Jakub Hrozek <jhrozek at redhat.com>
 * Simo Sorce <simo at redhat.com



More information about the sssd-devel mailing list