neutron-vpnaas/neutron_vpnaas/services/vpn/common/netns_wrapper.py

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