158 lines
5.6 KiB
Python
158 lines
5.6 KiB
Python
# Copyright (c) 2015 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.
|
|
|
|
import ConfigParser
|
|
import errno
|
|
import os
|
|
import sys
|
|
|
|
from eventlet.green import subprocess
|
|
from oslo.config import cfg
|
|
from oslo.rootwrap import wrapper
|
|
|
|
from neutron.common import config
|
|
from neutron.common import utils
|
|
from neutron.i18n import _LE, _LI
|
|
from neutron.openstack.common import log as logging
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def setup_conf():
|
|
cli_opts = [
|
|
cfg.DictOpt('mount_paths',
|
|
required=True,
|
|
help=_('Dict of paths to bind-mount (source:target) '
|
|
'prior to launch subprocess.')),
|
|
cfg.ListOpt(
|
|
'cmd',
|
|
required=True,
|
|
help=_('Command line to execute as a subprocess '
|
|
'provided as comma-separated list of arguments.')),
|
|
cfg.StrOpt('rootwrap_config', default='/etc/neutron/rootwrap.conf',
|
|
help=_('Rootwrap configuration file.')),
|
|
]
|
|
conf = cfg.CONF
|
|
conf.register_cli_opts(cli_opts)
|
|
return conf
|
|
|
|
|
|
def execute(cmd):
|
|
if not cmd:
|
|
return
|
|
cmd = map(str, cmd)
|
|
LOG.debug("Running command: %s", cmd)
|
|
env = os.environ.copy()
|
|
obj = utils.subprocess_popen(cmd, shell=False,
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
env=env)
|
|
|
|
_stdout, _stderr = obj.communicate()
|
|
LOG.debug('Command: %(cmd)s Exit code: %(returncode)s '
|
|
'Stdout: %(stdout)s Stderr: %(stderr)s',
|
|
{'cmd': cmd,
|
|
'returncode': obj.returncode,
|
|
'stdout': _stdout,
|
|
'stderr': _stderr})
|
|
obj.stdin.close()
|
|
return obj.returncode
|
|
|
|
|
|
def filter_command(command, rootwrap_config):
|
|
# Load rootwrap configuration
|
|
try:
|
|
rawconfig = ConfigParser.RawConfigParser()
|
|
rawconfig.read(rootwrap_config)
|
|
rw_config = wrapper.RootwrapConfig(rawconfig)
|
|
except ValueError as exc:
|
|
LOG.error(_LE('Incorrect value in %(config)s: %(exc)s'),
|
|
{'config': rootwrap_config, 'exc': exc.message})
|
|
sys.exit(errno.EINVAL)
|
|
except ConfigParser.Error:
|
|
LOG.error(_LE('Incorrect configuration file: %(config)s'),
|
|
{'config': rootwrap_config})
|
|
sys.exit(errno.EINVAL)
|
|
|
|
# Check if command matches any of the loaded filters
|
|
filters = wrapper.load_filters(rw_config.filters_path)
|
|
try:
|
|
wrapper.match_filter(filters, command, exec_dirs=rw_config.exec_dirs)
|
|
except wrapper.FilterMatchNotExecutable as exc:
|
|
LOG.error(_LE('Command %(command)s is not executable: '
|
|
'%(path)s (filter match = %(name)s)'),
|
|
{'command': command,
|
|
'path': exc.match.exec_path,
|
|
'name': exc.match.name})
|
|
sys.exit(errno.EINVAL)
|
|
except wrapper.NoFilterMatched:
|
|
LOG.error(_LE('Unauthorized command: %(cmd)s (no filter matched)'),
|
|
{'cmd': command})
|
|
sys.exit(errno.EPERM)
|
|
|
|
|
|
def execute_with_mount():
|
|
conf = setup_conf()
|
|
conf()
|
|
config.setup_logging()
|
|
if not conf.cmd:
|
|
LOG.error(_LE('No command provided, exiting'))
|
|
return errno.EINVAL
|
|
|
|
if not conf.mount_paths:
|
|
LOG.error(_LE('No mount path provided, exiting'))
|
|
return errno.EINVAL
|
|
|
|
# Both sudoers and rootwrap.conf will not exist in the directory /etc
|
|
# after bind-mount, so we can't use utils.execute(conf.cmd,
|
|
# conf.root_helper). That's why we have to check here if cmd matches
|
|
# CommandFilter
|
|
filter_command(conf.cmd, conf.rootwrap_config)
|
|
|
|
# Make sure the process is running in net namespace invoked by ip
|
|
# netns exec(/proc/[pid]/ns/net) which is since Linux 3.0,
|
|
# as we can't check mount namespace(/proc/[pid]/ns/mnt)
|
|
# which is since Linux 3.8. For more detail please refer the link
|
|
# http://man7.org/linux/man-pages/man7/namespaces.7.html
|
|
if os.path.samefile(os.path.join('/proc/1/ns/net'),
|
|
os.path.join('/proc', str(os.getpid()), 'ns/net')):
|
|
LOG.error(_LE('Cannot run without netns, exiting'))
|
|
return errno.EINVAL
|
|
|
|
for path, new_path in conf.mount_paths.iteritems():
|
|
if not os.path.isdir(new_path):
|
|
# Sometimes all directories are not ready
|
|
LOG.debug('%s is not directory', new_path)
|
|
continue
|
|
if os.path.isdir(path) and os.path.isabs(path):
|
|
return_code = execute(['mount', '--bind', new_path, path])
|
|
if return_code == 0:
|
|
LOG.info(_LI('%(new_path)s has been '
|
|
'bind-mounted in %(path)s'),
|
|
{'new_path': new_path, 'path': path})
|
|
else:
|
|
LOG.error(_LE('Failed to bind-mount '
|
|
'%(new_path)s in %(path)s'),
|
|
{'new_path': new_path, 'path': path})
|
|
return execute(conf.cmd)
|
|
|
|
|
|
def main():
|
|
sys.exit(execute_with_mount())
|
|
|
|
if __name__ == "__main__":
|
|
main()
|