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

Sumit Bose sbose at redhat.com
Wed Jul 23 14:47:58 UTC 2014


On Wed, Jul 23, 2014 at 03:38:13PM +0200, Jakub Hrozek wrote:
> 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.

Thank you Jakub for setting up this page and collecting all the details.

I have a couple of general comments which you might want to put on this
page or can be added to a 'Running SSSD as a non-root user - Step 2'
page later. As a first step we should try to make SSSD able to run as
unprivileged user but do not do it by default. This means that e.g. we
do not change the permissions of the host keytab but describe on a wiki
page what has to be done to run SSSD as non-root user. Additionally this
page will be our task list about which setuid helpers are still needed
or which permission have to be set during installation.

We should try to be more ambitious here and say that SSSD can be started
as unprivileged user i.e. none of the long running daemons run as root
at any time. systemd offer option like User= and Group= start start
daemons as any use, additionally it offers Capabilities= so the we can
keep some capabilities, e.g. to send audit messages.

Small and simple helper binary with setuid bit set will do any task that
require root privileges like touching file like /etc/krb5.conf or
changing the ownership of credential caches.

A helper for accessing the
host keytab would be nice as well. But I think we need a bit of
additional support in libkrb5 for this. There already is a MEMORY keytab
type which can be used inside the unprivileged processes instead of the
FILE type. The helper can just read the content of the and pass it back
to the caller. But there is no libkrb5 call to pass a memory copy of the keytab
file content into the related structs or into a MEMORY type keytab (at
least I haven't found a way so far). So the for the time being the host
keytab should be made available to the sssd user if SSSD should run
unprivileged.

About the sssd users. If SSSD can be started unprivileged the user
basically does not matter. We should only check in SSSD if the ownership
of the files and directories SSSD is using  have save permissions, i.e.
belong to the user sssd is started as and have permissions set as you
described below. If SSSD stops or just logs a warning if some of the
permissions are unsafe can be configurable. Distributions most certainly
will create a special user for SSSD  as upstream we should only make
sure that it is possible the 'make install' creates files and
directories with a configurable owner other than root where needed.

Allow SSSD to run as the user as it is started would make testing easier
as well because we can just start SSSD as the current user during make
test (uid_wrapper would help here as well).

About the PAM privileged pipe. I think we can remove it at least on
platform where the SO_PEERCRED option for getsockopt() is available.
With this we can reliable determine the UID of the caller, with the pipe
in the private directory we depend on correctly set file system
permissions. Maybe we can use the private pipe conditionally on
platforms where SO_PEERCRED is not available (if any)?

About the proxy child. Some PAM modules, like e.g. pam_unix require root
access, so I guess the proxy_child has to get a setuid bit.

bye,
Sumit

> 
> == 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
> _______________________________________________
> sssd-devel mailing list
> sssd-devel at lists.fedorahosted.org
> https://lists.fedorahosted.org/mailman/listinfo/sssd-devel



More information about the sssd-devel mailing list