Difference between revisions of "Rootwrap"
m (→Rootwrap for users) |
(Much-needed refresh) |
||
Line 3: | Line 3: | ||
=== Purpose === | === Purpose === | ||
− | The goal of the root wrapper is to allow a service-specific unprivileged user to run a number of actions as the ''root'' user, in the safest manner possible. Historically, Nova used a specific ''sudoers'' file listing every command that the ''nova'' user was allowed to run, and just used '''sudo''' to run that command as ''root''. However this was difficult to maintain | + | The goal of the root wrapper is to allow a service-specific unprivileged user to run a number of actions as the ''root'' user, in the safest manner possible. Historically, Nova used a specific ''sudoers'' file listing every command that the ''nova'' user was allowed to run, and just used '''sudo''' to run that command as ''root''. However this was difficult to maintain. The ''sudoers'' file was part of (and its format depending on) specific packaging. It did not allow for complex filtering of parameters (advanced filters). The rootwrap was [http://fnords.wordpress.com/2011/11/23/improving-nova-privilege-escalation-model-part-1/ designed] to solve those issues. |
=== An Oslo incubator project === | === An Oslo incubator project === | ||
Line 15: | Line 15: | ||
=== Security model === | === Security model === | ||
− | The escalation path is fully controlled by the ''root'' user. A ''sudoers'' entry (owned by ''root'') allows ''nova'' to run (as ''root'') a specific rootwrap executable, and only with a specific configuration file (which should be owned by ''root''). nova-rootwrap imports the Python modules it needs from a cleaned (and system-default) PYTHONPATH. The configuration file (also ''root''-owned) points to ''root''-owned filter definition directories, which contain ''root''-owned filters definition files. This chain ensures that the ''nova'' user itself is not in control of the configuration or modules used by the nova-rootwrap executable. | + | The escalation path is fully controlled by the ''root'' user. A ''sudoers'' entry (owned by ''root'') allows ''nova'' to run (as ''root'') a specific rootwrap executable, and only with a specific configuration file (which should be owned by ''root''). nova-rootwrap imports the Python modules it needs from a cleaned (and system-default) PYTHONPATH. The configuration file (also ''root''-owned) points to ''root''-owned filter definition directories, which contain ''root''-owned filters definition files. This chain ensures that the ''nova'' user itself is '''not''' in control of the configuration or modules used by the nova-rootwrap executable. |
− | == | + | == Documentation for end-users == |
=== Service configuration === | === Service configuration === | ||
Line 30: | Line 30: | ||
The configuration file used here must match the one defined in the ''sudoers'' entry (see below), otherwise the commands will be rejected ! There is no need to specify the ''root_helper'' parameter anymore. | The configuration file used here must match the one defined in the ''sudoers'' entry (see below), otherwise the commands will be rejected ! There is no need to specify the ''root_helper'' parameter anymore. | ||
− | == | + | == Documentation for distribution packagers == |
=== Sudoers entry === | === Sudoers entry === | ||
Line 60: | Line 60: | ||
All filter definition files can be found in Nova source code under etc/nova/rootwrap.d. | All filter definition files can be found in Nova source code under etc/nova/rootwrap.d. | ||
− | == | + | == Documentation for plug-in writers == |
=== Adding new run-as-root commands === | === Adding new run-as-root commands === | ||
Line 68: | Line 68: | ||
The format of the filter file is defined below, in the Reference section. | The format of the filter file is defined below, in the Reference section. | ||
− | == | + | == Documentation for project developers == |
=== Adding new run-as-root commands === | === Adding new run-as-root commands === | ||
− | Core developers may need to have the ''nova'' user run additional commands as ''root''. They should use nova.utils.execute(run_as_root=True) to achieve that, and add a filter for the command they need in the corresponding etc/nova/rootwrap.d/ .filters file in Nova's source code. For example, to add a command that needs to be | + | Core developers may need to have the ''nova'' user run additional commands as ''root''. They should use nova.utils.execute(run_as_root=True) to achieve that, and add a filter for the command they need in the corresponding etc/nova/rootwrap.d/ .filters file in Nova's source code. For example, to add a command that needs to be run by compute nodes, they should modify the etc/nova/rootwrap.d/compute.filters file. |
The format of the filter file is defined below, in the Reference section. | The format of the filter file is defined below, in the Reference section. | ||
Line 80: | Line 80: | ||
The default filter type, CommandFilter, is pretty basic. It only checks that the command name matches, it does not perform advanced checks on the command arguments. A number of other more command-specific filter types are available, see the Reference section for details. | The default filter type, CommandFilter, is pretty basic. It only checks that the command name matches, it does not perform advanced checks on the command arguments. A number of other more command-specific filter types are available, see the Reference section for details. | ||
− | That said, you can easily define new filter types to further control what exact command you actually allow the ''nova'' user to run as ''root''. See | + | That said, you can easily define new filter types to further control what exact command you actually allow the ''nova'' user to run as ''root''. See [https://github.com/openstack/oslo-incubator/blob/master/openstack/common/rootwrap/filters.py filters.py code] for details. |
== Reference == | == Reference == | ||
Line 92: | Line 92: | ||
<table border=1 cellpadding=5><tr><td colspan="2">[DEFAULT]</td></tr><tr><td>filters_path</td> | <table border=1 cellpadding=5><tr><td colspan="2">[DEFAULT]</td></tr><tr><td>filters_path</td> | ||
<td>Comma-separated list of directories containing filter definition files. All directories listed must be owned and only writeable by ''root''<br> | <td>Comma-separated list of directories containing filter definition files. All directories listed must be owned and only writeable by ''root''<br> | ||
− | <code>filters_path=/etc/nova/rootwrap.d,/usr/share/nova/rootwrap</code></td></tr></table> | + | <code>filters_path=/etc/nova/rootwrap.d,/usr/share/nova/rootwrap</code></td></tr> |
+ | <tr><td>exec_dirs</td> | ||
+ | <td>Comma-separated list of directories to search executables in, in case filters do not explicitely specify a full path. If not specified, defaults to the system PATH environment variable. All directories listed must be owned and only writeable by ''root''<br> | ||
+ | <code>exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin</code></td></tr> | ||
+ | <tr><td>use_syslog</td> | ||
+ | <td>Enable logging to syslog. Default value is False<br> | ||
+ | <code>use_syslog=True</code></td></tr> | ||
+ | <tr><td>syslog_log_facility</td> | ||
+ | <td>Which syslog facility to use for syslog logging. Valid values include auth, authpriv, syslog, user0, user1... Default value is 'syslog'<br> | ||
+ | <code>syslog_log_facility=syslog</code></td></tr> | ||
+ | <tr><td>syslog_log_level</td> | ||
+ | <td>Which messages to log. INFO means log all usage, ERROR means only log unsuccessful attempts<br> | ||
+ | <code>syslog_log_level=ERROR</code></td></tr></table> | ||
=== .filters files === | === .filters files === | ||
Line 111: | Line 123: | ||
==== CommandFilter ==== | ==== CommandFilter ==== | ||
− | + | Basic filter that only checks the executable called. Parameters are: | |
# Executable allowed | # Executable allowed | ||
# User to run the command under | # User to run the command under | ||
− | Example: allow | + | |
− | <code>kpartx: CommandFilter, | + | Example: allow to run kpartx as the root user, with any parameters: |
+ | <code>kpartx: CommandFilter, kpartx, root</code> | ||
==== RegExpFilter ==== | ==== RegExpFilter ==== | ||
Line 126: | Line 139: | ||
# (and following) Regular expressions to use to match first (and subsequent) command arguments | # (and following) Regular expressions to use to match first (and subsequent) command arguments | ||
− | Example: allow | + | |
+ | Example: allow to run /usr/sbin/tunctl, but only with three parameters with the first two being -b and -t: | ||
<code>tunctl: /usr/sbin/tunctl, root, tunctl, -b, -t, .*</code> | <code>tunctl: /usr/sbin/tunctl, root, tunctl, -b, -t, .*</code> | ||
+ | |||
+ | ==== PathFilter ==== | ||
+ | |||
+ | Generic filter that lets you check that paths provided as parameters fall under a given directory. Parameters are: | ||
+ | # Executable allowed | ||
+ | # User to run the command under | ||
+ | # (and following) Command arguments. | ||
+ | |||
+ | |||
+ | There are three types of command arguments: 'pass' will accept any parameter value, a string will only accept the corresponding string as a parameter, except if the string starts with '/' in which case it will accept any path that resolves under the corresponding directory. | ||
+ | |||
+ | Example: allow to chown to the 'nova' user any file under /var/lib/images: | ||
+ | <code>chown: PathFilter, /bin/chown, root, nova, /var/lib/images</code> | ||
+ | |||
+ | ==== EnvFilter ==== | ||
+ | |||
+ | Filter allowing extra environment variables to be set by the calling code. Parameters are: | ||
+ | # 'env' | ||
+ | # User to run the command under | ||
+ | # (and following) name of the environment variables that can be set, suffixed by '=' | ||
+ | # Executable allowed | ||
+ | |||
+ | |||
+ | Example: allow to run CONFIG_FILE=foo NETWORK_ID=bar dnsmasq ... as root: | ||
+ | <code>dnsmasq: EnvFilter, env, root, CONFIG_FILE=, NETWORK_ID=, dnsmasq</code> | ||
==== ReadFileFilter ==== | ==== ReadFileFilter ==== | ||
Line 134: | Line 173: | ||
# Path to the file that you want to read as the ''root'' user. | # Path to the file that you want to read as the ''root'' user. | ||
− | Example: allow | + | |
+ | Example: allow to run "cat /etc/iscsi/initiatorname.iscsi" as ''root'': | ||
<code>read_initiator: ReadFileFilter, /etc/iscsi/initiatorname.iscsi</code> | <code>read_initiator: ReadFileFilter, /etc/iscsi/initiatorname.iscsi</code> | ||
Line 144: | Line 184: | ||
# (and following) Signals you're allowed to send | # (and following) Signals you're allowed to send | ||
− | Example: allow | + | |
+ | Example: allow to send -9 or -HUP signals to /usr/sbin/dnsmasq processes: | ||
<code>kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP</code> | <code>kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP</code> | ||
− | ==== | + | ==== IpFilter ==== |
+ | |||
+ | ip-specific filter that allows to run any ''ip'' command, except for ''ip netns'' (in which case it only allows the ''list'', ''add'' and ''delete'' subcommands). Parameters are: | ||
+ | # Executable allowed ('ip') | ||
+ | # User to run ip under | ||
+ | |||
+ | |||
+ | Example: allow to run any ''ip'' command except ''ip netns exec'' and ''ip netns monitor'': | ||
+ | <code>ip: IpFilter, ip, root</code> | ||
+ | |||
+ | ==== IpNetnsExecFilter ==== | ||
+ | |||
+ | ip-specific filter that allows to run any otherwise-allowed command under ''ip netns exec''. The command specified to ''ip netns exec'' must match another filter for this filter to accept it. Parameters are: | ||
+ | # Executable allowed ('ip') | ||
+ | # User to run ip under | ||
− | |||
− | |||
− | |||
− | Example: allow | + | Example: allow to run ''ip netns exec <namespace> <command>'' as long as ''<command>'' matches another filter: |
− | <code> | + | <code>ip: IpNetnsExecFilter, ip, root</code> |
Revision as of 16:02, 3 September 2013
Contents
Architecture
Purpose
The goal of the root wrapper is to allow a service-specific unprivileged user to run a number of actions as the root user, in the safest manner possible. Historically, Nova used a specific sudoers file listing every command that the nova user was allowed to run, and just used sudo to run that command as root. However this was difficult to maintain. The sudoers file was part of (and its format depending on) specific packaging. It did not allow for complex filtering of parameters (advanced filters). The rootwrap was designed to solve those issues.
An Oslo incubator project
Rootwrap code is now maintained in the oslo-incubator. It is reused in multiple OpenStack projects (Nova, Cinder, Neutron...). To keep it simple, this documentation will talk about the nova copy of the code (nova-rootwrap). To apply this documentation to $PROJECT, just replace 'nova' by $PROJECT in every mention below.
How rootwrap works
Instead of just calling sudo make me a sandwich, Nova calls sudo nova-rootwrap /etc/nova/rootwrap.conf make me a sandwich. A generic sudoers entry lets the nova user run nova-rootwrap as root. nova-rootwrap looks for filter definition directories in its configuration file, and loads command filters from them. Then it checks if the command requested by Nova matches one of those filters, in which case it executes the command (as root). If no filter matches, it denies the request.
Security model
The escalation path is fully controlled by the root user. A sudoers entry (owned by root) allows nova to run (as root) a specific rootwrap executable, and only with a specific configuration file (which should be owned by root). nova-rootwrap imports the Python modules it needs from a cleaned (and system-default) PYTHONPATH. The configuration file (also root-owned) points to root-owned filter definition directories, which contain root-owned filters definition files. This chain ensures that the nova user itself is not in control of the configuration or modules used by the nova-rootwrap executable.
Documentation for end-users
Service configuration
You must provide the location of the rootwrap configuration file to Nova, by setting the following in nova.conf:
rootwrap_config=/etc/nova/rootwrap.conf
The configuration file used here must match the one defined in the sudoers entry (see below), otherwise the commands will be rejected ! There is no need to specify the root_helper parameter anymore.
Documentation for distribution packagers
Sudoers entry
Packagers need to make sure that Nova nodes contain a sudoers entry that lets the nova user run nova-rootwrap as root, pointing to the root-owned rootwrap.conf configuration file and allowing any parameter after that:
nova ALL = (root) NOPASSWD: /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf *
Filters path
Nova looks for a filters_path in rootwrap.conf, which contains the directories it should load filter definition files from. It is recommended that Nova-provided filters files are loaded from /usr/share/nova/rootwrap and extra user filters files are loaded from /etc/nova/rootwrap.d.
[DEFAULT] filters_path=/etc/nova/rootwrap.d,/usr/share/nova/rootwrap
Directories defined on this line should all exist, be owned and writeable only by the root user.
Filter definitions
Finally, packaging needs to install, for each node, the filters definition file that corresponds to it. You should not install any other filters file on that node, otherwise you would allow extra unneeded commands to be run by nova as root.
The filter file corresponding to the node must be installed in one of the filters_path directories (preferably /usr/share/nova/rootwrap). For example, on compute nodes, you should only have /usr/share/nova/rootwrap/compute.filters. The file should be owned and writeable only by the root user.
All filter definition files can be found in Nova source code under etc/nova/rootwrap.d.
Documentation for plug-in writers
Adding new run-as-root commands
Plug-in writers may need to have the nova user run additional commands as root. They should use nova.utils.execute(run_as_root=True) to achieve that. They should create their own filter definition file and install it (owned and writeable only by the root user !) into one of the filters_path directories (preferably /etc/nova/rootwrap.d). For example the foobar plugin could define its extra filters in a /etc/nova/rootwrap.d/foobar.filters file.
The format of the filter file is defined below, in the Reference section.
Documentation for project developers
Adding new run-as-root commands
Core developers may need to have the nova user run additional commands as root. They should use nova.utils.execute(run_as_root=True) to achieve that, and add a filter for the command they need in the corresponding etc/nova/rootwrap.d/ .filters file in Nova's source code. For example, to add a command that needs to be run by compute nodes, they should modify the etc/nova/rootwrap.d/compute.filters file.
The format of the filter file is defined below, in the Reference section.
Adding your own filter types
The default filter type, CommandFilter, is pretty basic. It only checks that the command name matches, it does not perform advanced checks on the command arguments. A number of other more command-specific filter types are available, see the Reference section for details.
That said, you can easily define new filter types to further control what exact command you actually allow the nova user to run as root. See filters.py code for details.
Reference
rootwrap.conf
The rootwrap.conf file is used to influence how nova-rootwrap works. Since it's in the trusted security path, it needs to be owned and writeable only by the root user. Its location is specified both in the sudoers entry and in the Nova configuration file.
It uses an INI file format with the following sections and parameters:
[DEFAULT] | |
filters_path | Comma-separated list of directories containing filter definition files. All directories listed must be owned and only writeable by rootfilters_path=/etc/nova/rootwrap.d,/usr/share/nova/rootwrap |
exec_dirs | Comma-separated list of directories to search executables in, in case filters do not explicitely specify a full path. If not specified, defaults to the system PATH environment variable. All directories listed must be owned and only writeable by rootexec_dirs=/sbin,/usr/sbin,/bin,/usr/bin |
use_syslog | Enable logging to syslog. Default value is Falseuse_syslog=True |
syslog_log_facility | Which syslog facility to use for syslog logging. Valid values include auth, authpriv, syslog, user0, user1... Default value is 'syslog'syslog_log_facility=syslog |
syslog_log_level | Which messages to log. INFO means log all usage, ERROR means only log unsuccessful attemptssyslog_log_level=ERROR |
.filters files
Filters definition files contain lists of filters that nova-rootwrap will use to allow or deny a specific command. They are generally suffixed by .filters
. Since they are in the trusted security path, they need to be owned and writeable only by the root user. Their location is specified in the rootwrap.conf file.
It uses an INI file format with a [Filters] section and several lines, each with a unique parameter name (different for each filter you define):
[Filters] | |
filter_name (different for each filter) | Comma-separated list containing first the Filter class to use, followed by that Filter arguments (which vary depending on the Filter class selected).
kpartx: CommandFilter, /sbin/kpartx, root |
See below for parameters to each Filter classes.
Available Filter classes
CommandFilter
Basic filter that only checks the executable called. Parameters are:
- Executable allowed
- User to run the command under
Example: allow to run kpartx as the root user, with any parameters:
kpartx: CommandFilter, kpartx, root
RegExpFilter
Generic filter that checks the executable called, then uses a list of regular expressions to check all subsequent arguments. Parameters are:
- Executable allowed
- User to run the command under
- (and following) Regular expressions to use to match first (and subsequent) command arguments
Example: allow to run /usr/sbin/tunctl, but only with three parameters with the first two being -b and -t:
tunctl: /usr/sbin/tunctl, root, tunctl, -b, -t, .*
PathFilter
Generic filter that lets you check that paths provided as parameters fall under a given directory. Parameters are:
- Executable allowed
- User to run the command under
- (and following) Command arguments.
There are three types of command arguments: 'pass' will accept any parameter value, a string will only accept the corresponding string as a parameter, except if the string starts with '/' in which case it will accept any path that resolves under the corresponding directory.
Example: allow to chown to the 'nova' user any file under /var/lib/images:
chown: PathFilter, /bin/chown, root, nova, /var/lib/images
EnvFilter
Filter allowing extra environment variables to be set by the calling code. Parameters are:
- 'env'
- User to run the command under
- (and following) name of the environment variables that can be set, suffixed by '='
- Executable allowed
Example: allow to run CONFIG_FILE=foo NETWORK_ID=bar dnsmasq ... as root:
dnsmasq: EnvFilter, env, root, CONFIG_FILE=, NETWORK_ID=, dnsmasq
ReadFileFilter
Specific filter that lets you read files as root using cat. Parameters are:
- Path to the file that you want to read as the root user.
Example: allow to run "cat /etc/iscsi/initiatorname.iscsi" as root:
read_initiator: ReadFileFilter, /etc/iscsi/initiatorname.iscsi
KillFilter
Kill-specific filter that checks the affected process and the signal sent before allowing the command. Parameters are:
- User to run kill under
- Only affect processes running that executable
- (and following) Signals you're allowed to send
Example: allow to send -9 or -HUP signals to /usr/sbin/dnsmasq processes:
kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP
IpFilter
ip-specific filter that allows to run any ip command, except for ip netns (in which case it only allows the list, add and delete subcommands). Parameters are:
- Executable allowed ('ip')
- User to run ip under
Example: allow to run any ip command except ip netns exec and ip netns monitor:
ip: IpFilter, ip, root
IpNetnsExecFilter
ip-specific filter that allows to run any otherwise-allowed command under ip netns exec. The command specified to ip netns exec must match another filter for this filter to accept it. Parameters are:
- Executable allowed ('ip')
- User to run ip under
Example: allow to run ip netns exec <namespace> <command> as long as <command> matches another filter:
ip: IpNetnsExecFilter, ip, root