From 04502de481bb79345adc3053dfc08e2b909f2af3 Mon Sep 17 00:00:00 2001 From: Michael McCune Date: Wed, 1 Oct 2014 17:57:52 -0400 Subject: [PATCH] Adding support for oslo.rootwrap to namespace access Changes * adding configuration options for rootwrap * refactoring ssh connection to use rootwrap as a proxy when requested * adding documentation for rootwrap configuration * adding default rootwrap filters file * adding default sudoers conf file for sahara user * adding default rootwrap conf file for sahara-rootwrap * adding sahara-rootwrap cli script * adding requirement for oslo.rootwrap Change-Id: I7871400b2342a4cd1a8910ae5121b1bfdc46078d Closes-Bug: #1271349 --- .../userdoc/advanced.configuration.guide.rst | 57 +++++++++++++++++++ doc/source/userdoc/features.rst | 22 ++++--- etc/sahara/rootwrap.conf | 34 +++++++++++ etc/sahara/rootwrap.d/sahara.filters | 4 ++ etc/sahara/sahara.conf.sample | 10 ++++ etc/sudoers.d/sahara-rootwrap | 1 + requirements.txt | 1 + sahara/cli/sahara_rootwrap.py | 21 +++++++ sahara/config.py | 12 +++- sahara/utils/openstack/neutron.py | 35 ++++++++---- sahara/utils/ssh_remote.py | 12 +++- setup.cfg | 1 + 12 files changed, 188 insertions(+), 22 deletions(-) create mode 100644 etc/sahara/rootwrap.conf create mode 100644 etc/sahara/rootwrap.d/sahara.filters create mode 100644 etc/sudoers.d/sahara-rootwrap create mode 100755 sahara/cli/sahara_rootwrap.py diff --git a/doc/source/userdoc/advanced.configuration.guide.rst b/doc/source/userdoc/advanced.configuration.guide.rst index f3da974a2b..9c3576e53e 100644 --- a/doc/source/userdoc/advanced.configuration.guide.rst +++ b/doc/source/userdoc/advanced.configuration.guide.rst @@ -67,3 +67,60 @@ integration see the Sahara documentation sections :ref:`diskimage-builder-label` and :ref:`swift-integration-label`. .. _Sahara extra repository: http://github.com/openstack/sahara-extra + +Namespaces and non-root users +----------------------------- + +In cases where namespaces are being used to access cluster VMs via private IPs, +rootwrap functionality is provided to allow users other than ``root`` access +to the namespace related OS facilities. To use rootwrap the following +configuration property is required to be set: + +.. sourcecode:: cfg + + [DEFAULT] + use_rootwrap=True + + +Assuming you elect to leverage the default rootwrap command +(``sahara-rootwrap``), you will need to perform the following additional setup +steps: + +* Copy the provided sudoers configuration file from the local project file + ``etc/sudoers.d/sahara-rootwrap`` to the system specific location, usually + ``/etc/sudoers.d``. This file is setup to allow a user named ``sahara`` + access to the rootwrap script. It contains the following: + +.. sourcecode:: cfg + + sahara ALL = (root) NOPASSWD: /usr/bin/sahara-rootwrap /etc/sahara/rootwrap.conf * + + +* Copy the provided rootwrap configuration file from the local project file + ``etc/sahara/rootwrap.conf`` to the system specific location, usually + ``/etc/sahara``. This file contains the default configuration for rootwrap. + +* Copy the provided rootwrap filers file from the local project file + ``etc/sahara/rootwrap.d/sahara.filters`` to the location specified in the + rootwrap configuration file, usually ``/etc/sahara/rootwrap.d``. This file + contains the filters that will allow the ``sahara`` user to acces the + ``ip netns exec``, ``nc``, and ``kill`` commands through the rootwrap. It + should look similar to the followings: + +.. sourcecode:: cfg + + [Filters] + ip: IpNetnsExecFilter, ip, root + nc: CommandFilter, nc, root + kill: CommandFilter, kill, root + +If you wish to use a rootwrap command other than ``sahara-rootwrap`` you can +set the following configuration property in your sahara configuration file: + +.. sourcecode:: cfg + + [DEFAULT] + rootwrap_command='sudo sahara-rootwrap /etc/sahara/rootwrap.conf' + +For more information on rootwrap please refer to the +`official Rootwrap documentation `_ diff --git a/doc/source/userdoc/features.rst b/doc/source/userdoc/features.rst index c7c549578d..8540975a69 100644 --- a/doc/source/userdoc/features.rst +++ b/doc/source/userdoc/features.rst @@ -33,20 +33,26 @@ All volumes are attached during Cluster creation/scaling operations. Neutron and Nova Network support -------------------------------- -OpenStack Cluster may use Nova Network or Neutron as a networking service. -Sahara supports both, but when deployed, -a special configuration for networking should be set explicitly. By default -Sahara will behave as if Nova Network is used. -If OpenStack Cluster uses Neutron, then ``use_neutron`` option should be set -to ``True`` in Sahara configuration file. In -addition, if the OpenStack Cluster supports network namespaces, set the -``use_namespaces`` option to ``True`` +OpenStack clusters may use Nova or Neutron as a networking service. Sahara +supports both, but when deployed a special configuration for networking +should be set explicitly. By default Sahara will behave as if Nova is used. +If an OpenStack cluster uses Neutron, then the ``use_neutron`` property should +be set to ``True`` in the Sahara configuration file. Additionally, if the +cluster supports network namespaces the ``use_namespaces`` property can be +used to enable their usage. .. sourcecode:: cfg + [DEFAULT] use_neutron=True use_namespaces=True +.. note:: + If a user other than ``root`` will be running the Sahara server + instance and namespaces are used, some additional configuration is + required, please see the :doc:`advanced.configuration.guide` for more + information. + Floating IP Management ---------------------- diff --git a/etc/sahara/rootwrap.conf b/etc/sahara/rootwrap.conf new file mode 100644 index 0000000000..547c87db0c --- /dev/null +++ b/etc/sahara/rootwrap.conf @@ -0,0 +1,34 @@ +# Configuration for sahara-rootwrap +# This file should be owned by (and only-writeable by) the root user + +[DEFAULT] +# List of directories to load filter definitions from (separated by ','). +# These directories MUST all be only writeable by root ! +filters_path=/etc/sahara/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 writeable by root ! +exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin + +# 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 + +[xenapi] +# XenAPI configuration is only required by the L2 agent if it is to +# target a XenServer/XCP compute host's dom0. +xenapi_connection_url= +xenapi_connection_username=root +xenapi_connection_password= diff --git a/etc/sahara/rootwrap.d/sahara.filters b/etc/sahara/rootwrap.d/sahara.filters new file mode 100644 index 0000000000..207b1536fc --- /dev/null +++ b/etc/sahara/rootwrap.d/sahara.filters @@ -0,0 +1,4 @@ +[Filters] +ip: IpNetnsExecFilter, ip, root +nc: CommandFilter, nc, root +kill: CommandFilter, kill, root diff --git a/etc/sahara/sahara.conf.sample b/etc/sahara/sahara.conf.sample index 5236ecef80..60798a32bf 100644 --- a/etc/sahara/sahara.conf.sample +++ b/etc/sahara/sahara.conf.sample @@ -224,6 +224,16 @@ # in conjunction with use_neutron=True). (boolean value) #use_namespaces=false +# Use rootwrap facility to allow non-root users to run the +# sahara-all server instance and access private network IPs +# (only valid to use in conjunction with use_namespaces=True) +# (boolean value) +#use_rootwrap=false + +# Rootwrap command to leverage. Use in conjunction with +# use_rootwrap=True (string value) +#rootwrap_command=sudo sahara-rootwrap /etc/sahara/rootwrap.conf + # # Options defined in sahara.main diff --git a/etc/sudoers.d/sahara-rootwrap b/etc/sudoers.d/sahara-rootwrap new file mode 100644 index 0000000000..559edf4a0a --- /dev/null +++ b/etc/sudoers.d/sahara-rootwrap @@ -0,0 +1 @@ +sahara ALL=(root) NOPASSWD: /usr/bin/sahara-rootwrap /etc/sahara/rootwrap.conf * diff --git a/requirements.txt b/requirements.txt index 81b2cb92a7..01cde6c986 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ oslo.config>=1.4.0 # Apache-2.0 oslo.db>=1.0.0 # Apache-2.0 oslo.i18n>=1.0.0 # Apache-2.0 oslo.messaging>=1.4.0 +oslo.rootwrap>=1.3.0 oslo.serialization>=1.0.0 # Apache-2.0 oslo.utils>=1.0.0 # Apache-2.0 paramiko>=1.13.0 diff --git a/sahara/cli/sahara_rootwrap.py b/sahara/cli/sahara_rootwrap.py new file mode 100755 index 0000000000..b626abea90 --- /dev/null +++ b/sahara/cli/sahara_rootwrap.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# Copyright (c) 2014 OpenStack Foundation. +# All Rights Reserved. +# +# 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.rootwrap import cmd + + +def main(): + cmd.main() diff --git a/sahara/config.py b/sahara/config.py index 564a83f83f..2c4412869b 100644 --- a/sahara/config.py +++ b/sahara/config.py @@ -59,7 +59,17 @@ networking_opts = [ cfg.BoolOpt('use_namespaces', default=False, help="Use network namespaces for communication (only valid to " - "use in conjunction with use_neutron=True).") + "use in conjunction with use_neutron=True)."), + cfg.BoolOpt('use_rootwrap', + default=False, + help="Use rootwrap facility to allow non-root users to run " + "the sahara-all server instance and access private " + "network IPs (only valid to use in conjunction with " + "use_namespaces=True)"), + cfg.StrOpt('rootwrap_command', + default='sudo sahara-rootwrap /etc/sahara/rootwrap.conf', + help="Rootwrap command to leverage. Use in conjunction with " + "use_rootwrap=True") ] diff --git a/sahara/utils/openstack/neutron.py b/sahara/utils/openstack/neutron.py index 91ccd2db47..c1072b9f15 100644 --- a/sahara/utils/openstack/neutron.py +++ b/sahara/utils/openstack/neutron.py @@ -80,15 +80,20 @@ class NeutronClientRemoteWrapper(): return matching_router['id'] - def get_http_session(self, host, port=None, *args, **kwargs): + def get_http_session(self, host, port=None, use_rootwrap=False, + rootwrap_command=None, *args, **kwargs): session = requests.Session() - adapters = self._get_adapters(host, port=port, *args, **kwargs) + adapters = self._get_adapters(host, port=port, + use_rootwrap=use_rootwrap, + rootwrap_command=rootwrap_command, + *args, **kwargs) for adapter in adapters: session.mount('http://{0}:{1}'.format(host, adapter.port), adapter) return session - def _get_adapters(self, host, port=None, *args, **kwargs): + def _get_adapters(self, host, port=None, use_rootwrap=False, + rootwrap_command=None, *args, **kwargs): LOG.debug('Retrieving neutron adapters for {0}:{1}'.format(host, port)) adapters = [] if not port: @@ -103,7 +108,9 @@ class NeutronClientRemoteWrapper(): .format(host, port)) qrouter = self.get_router() adapter = ( - NeutronHttpAdapter(qrouter, host, port)) + NeutronHttpAdapter(qrouter, host, port, + use_rootwrap=use_rootwrap, + rootwrap_command=rootwrap_command)) self.adapters[(host, port)] = adapter adapters = [adapter] @@ -114,14 +121,17 @@ class NeutronHttpAdapter(adapters.HTTPAdapter): port = None host = None - def __init__(self, qrouter, host, port, *args, **kwargs): + def __init__(self, qrouter, host, port, use_rootwrap=False, + rootwrap_command=None, *args, **kwargs): super(NeutronHttpAdapter, self).__init__(*args, **kwargs) - command = 'ip netns exec qrouter-{0} nc {1} {2}'.format(qrouter, - host, port) + command = '{0} ip netns exec qrouter-{1} nc {2} {3}'.format( + rootwrap_command if use_rootwrap else '', + qrouter, host, port) LOG.debug('Neutron adapter created with cmd {0}'.format(command)) self.cmd = shlex.split(command) self.port = port self.host = host + self.rootwrap_command = rootwrap_command if use_rootwrap else None def get_connection(self, url, proxies=None): pool_conn = ( @@ -152,7 +162,7 @@ class NeutronHttpAdapter(adapters.HTTPAdapter): def _connect(self): LOG.debug('returning netcat socket with command {0}' .format(self.cmd)) - return NetcatSocket(self.cmd) + return NetcatSocket(self.cmd, rootwrap_command=self.rootwrap_command) class NetcatSocket: @@ -163,8 +173,9 @@ class NetcatSocket: stdout=e_subprocess.PIPE, stderr=e_subprocess.PIPE) - def __init__(self, cmd): + def __init__(self, cmd, rootwrap_command=None): self.cmd = cmd + self.rootwrap_command = rootwrap_command self._create_process() def send(self, content): @@ -191,7 +202,11 @@ class NetcatSocket: raise ex.SystemError(e) def _terminate(self): - self.process.terminate() + if self.rootwrap_command: + os.system('{0} kill {1}'.format(self.rootwrap_command, + self.process.pid)) + else: + self.process.terminate() def close(self): LOG.debug('Socket close called') diff --git a/sahara/utils/ssh_remote.py b/sahara/utils/ssh_remote.py index bb3f327a61..60f96ece44 100644 --- a/sahara/utils/ssh_remote.py +++ b/sahara/utils/ssh_remote.py @@ -75,8 +75,11 @@ def _get_proxy(neutron_info): neutron_info['token'], neutron_info['tenant']) qrouter = client.get_router() - proxy = paramiko.ProxyCommand('ip netns exec qrouter-{0} nc {1} 22' - .format(qrouter, neutron_info['host'])) + proxy = paramiko.ProxyCommand('{0} ip netns exec qrouter-{1} nc {2} 22' + .format(neutron_info['rootwrap_command'] + if neutron_info['use_rootwrap'] + else '', + qrouter, neutron_info['host'])) return proxy @@ -158,7 +161,8 @@ def _get_http_client(host, port, neutron_info, *args, **kwargs): # the same adapter (and same connection pools) for a given # host and port tuple _http_session = neutron_client.get_http_session( - host, port=port, *args, **kwargs) + host, port=port, use_rootwrap=CONF.use_rootwrap, + rootwrap_command=CONF.rootwrap_command, *args, **kwargs) LOG.debug('created neutron based HTTP session for {0}:{1}' .format(host, port)) else: @@ -336,6 +340,8 @@ class InstanceInteropHelper(remote.Remote): neutron_info['token'] = ctx.token neutron_info['tenant'] = ctx.tenant_name neutron_info['host'] = self.instance.management_ip + neutron_info['use_rootwrap'] = CONF.use_rootwrap + neutron_info['rootwrap_command'] = CONF.rootwrap_command LOG.debug('Returning neutron info: {0}'.format(neutron_info)) return neutron_info diff --git a/setup.cfg b/setup.cfg index 5d01b42f9e..f538bf4cf7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ console_scripts = sahara-api = sahara.cli.sahara_api:main sahara-engine = sahara.cli.sahara_engine:main sahara-db-manage = sahara.db.migration.cli:main + sahara-rootwrap = sahara.cli.sahara_rootwrap:main _sahara-subprocess = sahara.cli.sahara_subprocess:main sahara.cluster.plugins =