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

166 lines
5.8 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 as ConfigParser
import errno
import os
import sys
from eventlet.green import subprocess
from neutron.common import config
from neutron.common import utils
from neutron_lib.utils import helpers
from oslo_config import cfg
from oslo_log import log as logging
from oslo_rootwrap import wrapper
from neutron_vpnaas._i18n import _
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 = list(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()
_stdout = helpers.safe_decode_utf8(_stdout)
_stderr = helpers.safe_decode_utf8(_stderr)
msg = ('Command: %(cmd)s Exit code: %(returncode)s '
'Stdout: %(stdout)s Stderr: %(stderr)s' %
{'cmd': cmd,
'returncode': obj.returncode,
'stdout': _stdout,
'stderr': _stderr})
LOG.debug(msg)
obj.stdin.close()
# Pass the output to calling process
sys.stdout.write(msg)
sys.stdout.flush()
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('Incorrect value in %(config)s: %(exc)s',
{'config': rootwrap_config, 'exc': exc})
sys.exit(errno.EINVAL)
except ConfigParser.Error:
LOG.error('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('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('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('No command provided, exiting')
return errno.EINVAL
if not conf.mount_paths:
LOG.error('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,
# run_as_root=True). 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('Cannot run without netns, exiting')
return errno.EINVAL
for path, new_path in conf.mount_paths.items():
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('%(new_path)s has been '
'bind-mounted in %(path)s',
{'new_path': new_path, 'path': path})
else:
LOG.error('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()