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
This commit is contained in:
Michael McCune 2014-10-01 17:57:52 -04:00
parent 4d150db7d5
commit 04502de481
12 changed files with 188 additions and 22 deletions

View File

@ -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 <https://wiki.openstack.org/wiki/Rootwrap>`_

View File

@ -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
----------------------

34
etc/sahara/rootwrap.conf Normal file
View File

@ -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=<None>
xenapi_connection_username=root
xenapi_connection_password=<None>

View File

@ -0,0 +1,4 @@
[Filters]
ip: IpNetnsExecFilter, ip, root
nc: CommandFilter, nc, root
kill: CommandFilter, kill, root

View File

@ -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

View File

@ -0,0 +1 @@
sahara ALL=(root) NOPASSWD: /usr/bin/sahara-rootwrap /etc/sahara/rootwrap.conf *

View File

@ -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

21
sahara/cli/sahara_rootwrap.py Executable file
View File

@ -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()

View File

@ -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")
]

View File

@ -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')

View File

@ -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

View File

@ -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 =