From d412de7100058f4a362cf58bd11f88b31b500f77 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Wed, 14 Feb 2018 02:55:07 +0000 Subject: [PATCH] Introduce rootwrap and filter If the zun-compute process is owned by a user who doesn't have passwordless sudo privilege, zun-compute will fail to run privileged command (e.g. sudo privsep-helper ...). A native solution is to grant passwordless sudo to the user who owns the zun process, but the best practice is to leverage Rootwrap [1], which can restrict the privilege escalation. This patch make Zun leverage Rootwrap. In particular, it does the following: * Setup Rootwrap in the Zun devstack plugin * Introduce a sample rootwrap config file * Introduce sample rootwrap filters for executing privsep-helper * Introduce a root helper which basically adds "sudo zun-rootwrap" to the beginning of the command to be execute. * Initialize privsep to use the Zun's root helper [1] https://wiki.openstack.org/wiki/Rootwrap Closes-Bug: #1749342 Needed-By: I69c47d25fa53f8e08efad9daa71d2f550425a5e7 Change-Id: I3ca5d853588b3705cb6cb2410df16e16a621c030 --- devstack/lib/zun | 2 ++ etc/zun/rootwrap.conf | 27 +++++++++++++++++++++++++++ etc/zun/rootwrap.d/zun.filters | 8 ++++++++ setup.cfg | 1 + zun/cmd/compute.py | 4 ++++ zun/common/utils.py | 4 ++++ zun/conf/__init__.py | 2 ++ zun/conf/utils.py | 31 +++++++++++++++++++++++++++++++ 8 files changed, 79 insertions(+) create mode 100644 etc/zun/rootwrap.conf create mode 100644 etc/zun/rootwrap.d/zun.filters create mode 100644 zun/conf/utils.py diff --git a/devstack/lib/zun b/devstack/lib/zun index dcb42d190..759a55e52 100644 --- a/devstack/lib/zun +++ b/devstack/lib/zun @@ -112,6 +112,8 @@ function configure_zun { sudo chown $STACK_USER $ZUN_CONF_DIR fi + configure_rootwrap zun + # Rebuild the config file from scratch create_zun_conf diff --git a/etc/zun/rootwrap.conf b/etc/zun/rootwrap.conf new file mode 100644 index 000000000..241642618 --- /dev/null +++ b/etc/zun/rootwrap.conf @@ -0,0 +1,27 @@ +# Configuration for zun-rootwrap +# This file should be owned by (and only-writable by) the root user + +[DEFAULT] +# List of directories to load filter definitions from (separated by ','). +# These directories MUST all be only writable by root ! +filters_path=/etc/zun/rootwrap.d + +# List of directories to search executables in, in case filters do not +# explicitely specify a full path (separated by ',') +# If not specified, defaults to system PATH environment variable. +# These directories MUST all be only writable by root ! +exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin + +# Enable logging to syslog +# Default value is False +use_syslog=False + +# Which syslog facility to use. +# Valid values include auth, authpriv, syslog, local0, local1... +# Default value is 'syslog' +syslog_log_facility=syslog + +# Which messages to log. +# INFO means log all usage +# ERROR means only log unsuccessful attempts +syslog_log_level=ERROR diff --git a/etc/zun/rootwrap.d/zun.filters b/etc/zun/rootwrap.d/zun.filters new file mode 100644 index 000000000..890a3183b --- /dev/null +++ b/etc/zun/rootwrap.d/zun.filters @@ -0,0 +1,8 @@ +# zun command filters +# This file should be owned by (and only-writeable by) the root user + +[Filters] +# privileged/__init__.py: priv_context.PrivContext(default) +# This line ties the superuser privs with the config files, context name, +# and (implicitly) the actual python code invoked. +privsep-rootwrap: RegExpFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, os_brick.privileged.default, --privsep_sock_path, /tmp/.* diff --git a/setup.cfg b/setup.cfg index 1dbae0da9..4f1e23cc9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,6 +54,7 @@ console_scripts = zun-compute = zun.cmd.compute:main zun-db-manage = zun.cmd.db_manage:main zun-wsproxy = zun.cmd.wsproxy:main + zun-rootwrap = oslo_rootwrap.cmd:main wsgi_scripts = zun-api-wsgi = zun.api.wsgi:init_application diff --git a/zun/cmd/compute.py b/zun/cmd/compute.py index 05c0d38a9..c7d74e426 100644 --- a/zun/cmd/compute.py +++ b/zun/cmd/compute.py @@ -13,13 +13,16 @@ # under the License. import os +import shlex import sys from oslo_log import log as logging +from oslo_privsep import priv_context from oslo_service import service from zun.common import rpc_service from zun.common import service as zun_service +from zun.common import utils import zun.conf CONF = zun.conf.CONF @@ -27,6 +30,7 @@ LOG = logging.getLogger(__name__) def main(): + priv_context.init(root_helper=shlex.split(utils.get_root_helper())) zun_service.prepare_service(sys.argv) LOG.info('Starting server in PID %s', os.getpid()) diff --git a/zun/common/utils.py b/zun/common/utils.py index 81f52c752..52a20b62f 100644 --- a/zun/common/utils.py +++ b/zun/common/utils.py @@ -312,6 +312,10 @@ def custom_execute(*cmd, **kwargs): error=six.text_type(e)) +def get_root_helper(): + return 'sudo zun-rootwrap %s' % CONF.rootwrap_config + + @privileged.default.entrypoint def execute_root(*cmd, **kwargs): # NOTE(kiennt): Set run_as_root=False because if it is set to True, the diff --git a/zun/conf/__init__.py b/zun/conf/__init__.py index d6f4fc89c..07e28ec72 100644 --- a/zun/conf/__init__.py +++ b/zun/conf/__init__.py @@ -34,6 +34,7 @@ from zun.conf import profiler from zun.conf import scheduler from zun.conf import services from zun.conf import ssl +from zun.conf import utils from zun.conf import volume from zun.conf import websocket_proxy from zun.conf import zun_client @@ -63,3 +64,4 @@ volume.register_opts(CONF) cinder_client.register_opts(CONF) netconf.register_opts(CONF) availability_zone.register_opts(CONF) +utils.register_opts(CONF) diff --git a/zun/conf/utils.py b/zun/conf/utils.py new file mode 100644 index 000000000..a632fdaf4 --- /dev/null +++ b/zun/conf/utils.py @@ -0,0 +1,31 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from oslo_config import cfg + + +utils_opts = [ + cfg.StrOpt('rootwrap_config', + default="/etc/zun/rootwrap.conf", + help='Path to the rootwrap configuration file to use for ' + 'running commands as root.'), +] + + +def register_opts(conf): + conf.register_opts(utils_opts) + + +def list_opts(): + return { + "DEFAULT": utils_opts + }