Use oslo.rootwrap library instead of local copy
Remove rootwrap code copied from oslo-incubator, make manila-rootwrap a console_script entrypoint pointing in oslo.rootwrap instead. Partially-implements blueprint use-common-code Change-Id: I519e8dec24dc9c48243af41dc0f423c05a0d92e2
This commit is contained in:
parent
ca079b22e8
commit
13f0446ed5
@ -1,130 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright (c) 2011 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.
|
|
||||||
|
|
||||||
"""Root wrapper for OpenStack services
|
|
||||||
|
|
||||||
Filters which commands a service is allowed to run as another user.
|
|
||||||
|
|
||||||
To use this with manila, you should set the following in
|
|
||||||
manila.conf:
|
|
||||||
rootwrap_config=/etc/manila/rootwrap.conf
|
|
||||||
|
|
||||||
You also need to let the manila user run manila-rootwrap
|
|
||||||
as root in sudoers:
|
|
||||||
manila ALL = (root) NOPASSWD: /usr/bin/manila-rootwrap
|
|
||||||
/etc/manila/rootwrap.conf *
|
|
||||||
|
|
||||||
Service packaging should deploy .filters files only on nodes where
|
|
||||||
they are needed, to avoid allowing more than is necessary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import ConfigParser
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import pwd
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
RC_UNAUTHORIZED = 99
|
|
||||||
RC_NOCOMMAND = 98
|
|
||||||
RC_BADCONFIG = 97
|
|
||||||
RC_NOEXECFOUND = 96
|
|
||||||
|
|
||||||
|
|
||||||
def _subprocess_setup():
|
|
||||||
# Python installs a SIGPIPE handler by default. This is usually not what
|
|
||||||
# non-Python subprocesses expect.
|
|
||||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
||||||
|
|
||||||
|
|
||||||
def _exit_error(execname, message, errorcode, log=True):
|
|
||||||
print("%s: %s" % (execname, message))
|
|
||||||
if log:
|
|
||||||
logging.error(message)
|
|
||||||
sys.exit(errorcode)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Split arguments, require at least a command
|
|
||||||
execname = sys.argv.pop(0)
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
_exit_error(execname, "No command specified", RC_NOCOMMAND, log=False)
|
|
||||||
|
|
||||||
configfile = sys.argv.pop(0)
|
|
||||||
userargs = sys.argv[:]
|
|
||||||
|
|
||||||
# Add ../ to sys.path to allow running from branch
|
|
||||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname),
|
|
||||||
os.pardir, os.pardir))
|
|
||||||
if os.path.exists(os.path.join(possible_topdir, "manila", "__init__.py")):
|
|
||||||
sys.path.insert(0, possible_topdir)
|
|
||||||
|
|
||||||
from manila.openstack.common.rootwrap import wrapper
|
|
||||||
|
|
||||||
# Load configuration
|
|
||||||
try:
|
|
||||||
rawconfig = ConfigParser.RawConfigParser()
|
|
||||||
rawconfig.read(configfile)
|
|
||||||
config = wrapper.RootwrapConfig(rawconfig)
|
|
||||||
except ValueError as exc:
|
|
||||||
msg = "Incorrect value in %s: %s" % (configfile, exc.message)
|
|
||||||
_exit_error(execname, msg, RC_BADCONFIG, log=False)
|
|
||||||
except ConfigParser.Error:
|
|
||||||
_exit_error(execname, "Incorrect configuration file: %s" % configfile,
|
|
||||||
RC_BADCONFIG, log=False)
|
|
||||||
|
|
||||||
if config.use_syslog:
|
|
||||||
wrapper.setup_syslog(execname,
|
|
||||||
config.syslog_log_facility,
|
|
||||||
config.syslog_log_level)
|
|
||||||
|
|
||||||
# Execute command if it matches any of the loaded filters
|
|
||||||
filters = wrapper.load_filters(config.filters_path)
|
|
||||||
try:
|
|
||||||
filtermatch = wrapper.match_filter(filters, userargs,
|
|
||||||
exec_dirs=config.exec_dirs)
|
|
||||||
if filtermatch:
|
|
||||||
command = filtermatch.get_command(userargs,
|
|
||||||
exec_dirs=config.exec_dirs)
|
|
||||||
if config.use_syslog:
|
|
||||||
logging.info("(%s > %s) Executing %s (filter match = %s)" % (
|
|
||||||
os.getlogin(), pwd.getpwuid(os.getuid())[0],
|
|
||||||
command, filtermatch.name))
|
|
||||||
|
|
||||||
obj = subprocess.Popen(command,
|
|
||||||
stdin=sys.stdin,
|
|
||||||
stdout=sys.stdout,
|
|
||||||
stderr=sys.stderr,
|
|
||||||
preexec_fn=_subprocess_setup,
|
|
||||||
env=filtermatch.get_environment(userargs))
|
|
||||||
obj.wait()
|
|
||||||
sys.exit(obj.returncode)
|
|
||||||
|
|
||||||
except wrapper.FilterMatchNotExecutable as exc:
|
|
||||||
msg = ("Executable not found: %s (filter match = %s)"
|
|
||||||
% (exc.match.exec_path, exc.match.name))
|
|
||||||
_exit_error(execname, msg, RC_NOEXECFOUND, log=config.use_syslog)
|
|
||||||
|
|
||||||
except wrapper.NoFilterMatched:
|
|
||||||
msg = ("Unauthorized command: %s (no filter matched)"
|
|
||||||
% ' '.join(userargs))
|
|
||||||
_exit_error(execname, msg, RC_UNAUTHORIZED, log=config.use_syslog)
|
|
@ -1,16 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright (c) 2011 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.
|
|
@ -1,128 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright (c) 2011 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.
|
|
||||||
|
|
||||||
"""Root wrapper for OpenStack services
|
|
||||||
|
|
||||||
Filters which commands a service is allowed to run as another user.
|
|
||||||
|
|
||||||
To use this with manila, you should set the following in
|
|
||||||
manila.conf:
|
|
||||||
rootwrap_config=/etc/manila/rootwrap.conf
|
|
||||||
|
|
||||||
You also need to let the manila user run manila-rootwrap
|
|
||||||
as root in sudoers:
|
|
||||||
manila ALL = (root) NOPASSWD: /usr/bin/manila-rootwrap
|
|
||||||
/etc/manila/rootwrap.conf *
|
|
||||||
|
|
||||||
Service packaging should deploy .filters files only on nodes where
|
|
||||||
they are needed, to avoid allowing more than is necessary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import ConfigParser
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import pwd
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
RC_UNAUTHORIZED = 99
|
|
||||||
RC_NOCOMMAND = 98
|
|
||||||
RC_BADCONFIG = 97
|
|
||||||
RC_NOEXECFOUND = 96
|
|
||||||
|
|
||||||
|
|
||||||
def _subprocess_setup():
|
|
||||||
# Python installs a SIGPIPE handler by default. This is usually not what
|
|
||||||
# non-Python subprocesses expect.
|
|
||||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
||||||
|
|
||||||
|
|
||||||
def _exit_error(execname, message, errorcode, log=True):
|
|
||||||
print "%s: %s" % (execname, message)
|
|
||||||
if log:
|
|
||||||
logging.error(message)
|
|
||||||
sys.exit(errorcode)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Split arguments, require at least a command
|
|
||||||
execname = sys.argv.pop(0)
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
_exit_error(execname, "No command specified", RC_NOCOMMAND, log=False)
|
|
||||||
|
|
||||||
configfile = sys.argv.pop(0)
|
|
||||||
userargs = sys.argv[:]
|
|
||||||
|
|
||||||
# Add ../ to sys.path to allow running from branch
|
|
||||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname),
|
|
||||||
os.pardir, os.pardir))
|
|
||||||
if os.path.exists(os.path.join(possible_topdir, "manila", "__init__.py")):
|
|
||||||
sys.path.insert(0, possible_topdir)
|
|
||||||
|
|
||||||
from manila.openstack.common.rootwrap import wrapper
|
|
||||||
|
|
||||||
# Load configuration
|
|
||||||
try:
|
|
||||||
rawconfig = ConfigParser.RawConfigParser()
|
|
||||||
rawconfig.read(configfile)
|
|
||||||
config = wrapper.RootwrapConfig(rawconfig)
|
|
||||||
except ValueError as exc:
|
|
||||||
msg = "Incorrect value in %s: %s" % (configfile, exc.message)
|
|
||||||
_exit_error(execname, msg, RC_BADCONFIG, log=False)
|
|
||||||
except ConfigParser.Error:
|
|
||||||
_exit_error(execname, "Incorrect configuration file: %s" % configfile,
|
|
||||||
RC_BADCONFIG, log=False)
|
|
||||||
|
|
||||||
if config.use_syslog:
|
|
||||||
wrapper.setup_syslog(execname,
|
|
||||||
config.syslog_log_facility,
|
|
||||||
config.syslog_log_level)
|
|
||||||
|
|
||||||
# Execute command if it matches any of the loaded filters
|
|
||||||
filters = wrapper.load_filters(config.filters_path)
|
|
||||||
try:
|
|
||||||
filtermatch = wrapper.match_filter(filters, userargs,
|
|
||||||
exec_dirs=config.exec_dirs)
|
|
||||||
if filtermatch:
|
|
||||||
command = filtermatch.get_command(userargs,
|
|
||||||
exec_dirs=config.exec_dirs)
|
|
||||||
if config.use_syslog:
|
|
||||||
logging.info("(%s > %s) Executing %s (filter match = %s)" % (
|
|
||||||
os.getlogin(), pwd.getpwuid(os.getuid())[0],
|
|
||||||
command, filtermatch.name))
|
|
||||||
|
|
||||||
obj = subprocess.Popen(command,
|
|
||||||
stdin=sys.stdin,
|
|
||||||
stdout=sys.stdout,
|
|
||||||
stderr=sys.stderr,
|
|
||||||
preexec_fn=_subprocess_setup,
|
|
||||||
env=filtermatch.get_environment(userargs))
|
|
||||||
obj.wait()
|
|
||||||
sys.exit(obj.returncode)
|
|
||||||
|
|
||||||
except wrapper.FilterMatchNotExecutable as exc:
|
|
||||||
msg = ("Executable not found: %s (filter match = %s)"
|
|
||||||
% (exc.match.exec_path, exc.match.name))
|
|
||||||
_exit_error(execname, msg, RC_NOEXECFOUND, log=config.use_syslog)
|
|
||||||
|
|
||||||
except wrapper.NoFilterMatched:
|
|
||||||
msg = ("Unauthorized command: %s (no filter matched)"
|
|
||||||
% ' '.join(userargs))
|
|
||||||
_exit_error(execname, msg, RC_UNAUTHORIZED, log=config.use_syslog)
|
|
@ -1,226 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright (c) 2011 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 os
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class CommandFilter(object):
|
|
||||||
"""Command filter only checking that the 1st argument matches exec_path"""
|
|
||||||
|
|
||||||
def __init__(self, exec_path, run_as, *args):
|
|
||||||
self.name = ''
|
|
||||||
self.exec_path = exec_path
|
|
||||||
self.run_as = run_as
|
|
||||||
self.args = args
|
|
||||||
self.real_exec = None
|
|
||||||
|
|
||||||
def get_exec(self, exec_dirs=[]):
|
|
||||||
"""Returns existing executable, or empty string if none found"""
|
|
||||||
if self.real_exec is not None:
|
|
||||||
return self.real_exec
|
|
||||||
self.real_exec = ""
|
|
||||||
if self.exec_path.startswith('/'):
|
|
||||||
if os.access(self.exec_path, os.X_OK):
|
|
||||||
self.real_exec = self.exec_path
|
|
||||||
else:
|
|
||||||
for binary_path in exec_dirs:
|
|
||||||
expanded_path = os.path.join(binary_path, self.exec_path)
|
|
||||||
if os.access(expanded_path, os.X_OK):
|
|
||||||
self.real_exec = expanded_path
|
|
||||||
break
|
|
||||||
return self.real_exec
|
|
||||||
|
|
||||||
def match(self, userargs):
|
|
||||||
"""Only check that the first argument (command) matches exec_path"""
|
|
||||||
if (os.path.basename(self.exec_path) == userargs[0]):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_command(self, userargs, exec_dirs=[]):
|
|
||||||
"""Returns command to execute (with sudo -u if run_as != root)."""
|
|
||||||
to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path
|
|
||||||
if (self.run_as != 'root'):
|
|
||||||
# Used to run commands at lesser privileges
|
|
||||||
return ['sudo', '-u', self.run_as, to_exec] + userargs[1:]
|
|
||||||
return [to_exec] + userargs[1:]
|
|
||||||
|
|
||||||
def get_environment(self, userargs):
|
|
||||||
"""Returns specific environment to set, None if none"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class RegExpFilter(CommandFilter):
|
|
||||||
"""Command filter doing regexp matching for every argument"""
|
|
||||||
|
|
||||||
def match(self, userargs):
|
|
||||||
# Early skip if command or number of args don't match
|
|
||||||
if (len(self.args) != len(userargs)):
|
|
||||||
# DENY: argument numbers don't match
|
|
||||||
return False
|
|
||||||
# Compare each arg (anchoring pattern explicitly at end of string)
|
|
||||||
for (pattern, arg) in zip(self.args, userargs):
|
|
||||||
try:
|
|
||||||
if not re.match(pattern + '$', arg):
|
|
||||||
break
|
|
||||||
except re.error:
|
|
||||||
# DENY: Badly-formed filter
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# ALLOW: All arguments matched
|
|
||||||
return True
|
|
||||||
|
|
||||||
# DENY: Some arguments did not match
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class PathFilter(CommandFilter):
|
|
||||||
"""Command filter checking that path arguments are within given dirs
|
|
||||||
|
|
||||||
One can specify the following constraints for command arguments:
|
|
||||||
1) pass - pass an argument as is to the resulting command
|
|
||||||
2) some_str - check if an argument is equal to the given string
|
|
||||||
3) abs path - check if a path argument is within the given base dir
|
|
||||||
|
|
||||||
A typical rootwrapper filter entry looks like this:
|
|
||||||
# cmdname: filter name, raw command, user, arg_i_constraint [, ...]
|
|
||||||
chown: PathFilter, /bin/chown, root, nova, /var/lib/images
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def match(self, userargs):
|
|
||||||
command, arguments = userargs[0], userargs[1:]
|
|
||||||
|
|
||||||
equal_args_num = len(self.args) == len(arguments)
|
|
||||||
exec_is_valid = super(PathFilter, self).match(userargs)
|
|
||||||
args_equal_or_pass = all(
|
|
||||||
arg == 'pass' or arg == value
|
|
||||||
for arg, value in zip(self.args, arguments)
|
|
||||||
if not os.path.isabs(arg) # arguments not specifying abs paths
|
|
||||||
)
|
|
||||||
paths_are_within_base_dirs = all(
|
|
||||||
os.path.commonprefix([arg, os.path.realpath(value)]) == arg
|
|
||||||
for arg, value in zip(self.args, arguments)
|
|
||||||
if os.path.isabs(arg) # arguments specifying abs paths
|
|
||||||
)
|
|
||||||
|
|
||||||
return (equal_args_num and
|
|
||||||
exec_is_valid and
|
|
||||||
args_equal_or_pass and
|
|
||||||
paths_are_within_base_dirs)
|
|
||||||
|
|
||||||
def get_command(self, userargs, exec_dirs=[]):
|
|
||||||
command, arguments = userargs[0], userargs[1:]
|
|
||||||
|
|
||||||
# convert path values to canonical ones; copy other args as is
|
|
||||||
args = [os.path.realpath(value) if os.path.isabs(arg) else value
|
|
||||||
for arg, value in zip(self.args, arguments)]
|
|
||||||
|
|
||||||
return super(PathFilter, self).get_command([command] + args,
|
|
||||||
exec_dirs)
|
|
||||||
|
|
||||||
|
|
||||||
class DnsmasqFilter(CommandFilter):
|
|
||||||
"""Specific filter for the dnsmasq call (which includes env)"""
|
|
||||||
|
|
||||||
CONFIG_FILE_ARG = 'CONFIG_FILE'
|
|
||||||
|
|
||||||
def match(self, userargs):
|
|
||||||
if (userargs[0] == 'env' and
|
|
||||||
userargs[1].startswith(self.CONFIG_FILE_ARG) and
|
|
||||||
userargs[2].startswith('NETWORK_ID=') and
|
|
||||||
userargs[3] == 'dnsmasq'):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_command(self, userargs, exec_dirs=[]):
|
|
||||||
to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path
|
|
||||||
dnsmasq_pos = userargs.index('dnsmasq')
|
|
||||||
return [to_exec] + userargs[dnsmasq_pos + 1:]
|
|
||||||
|
|
||||||
def get_environment(self, userargs):
|
|
||||||
env = os.environ.copy()
|
|
||||||
env[self.CONFIG_FILE_ARG] = userargs[1].split('=')[-1]
|
|
||||||
env['NETWORK_ID'] = userargs[2].split('=')[-1]
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
class DeprecatedDnsmasqFilter(DnsmasqFilter):
|
|
||||||
"""Variant of dnsmasq filter to support old-style FLAGFILE"""
|
|
||||||
CONFIG_FILE_ARG = 'FLAGFILE'
|
|
||||||
|
|
||||||
|
|
||||||
class KillFilter(CommandFilter):
|
|
||||||
"""Specific filter for the kill calls.
|
|
||||||
1st argument is the user to run /bin/kill under
|
|
||||||
2nd argument is the location of the affected executable
|
|
||||||
Subsequent arguments list the accepted signals (if any)
|
|
||||||
|
|
||||||
This filter relies on /proc to accurately determine affected
|
|
||||||
executable, so it will only work on procfs-capable systems (not OSX).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
super(KillFilter, self).__init__("/bin/kill", *args)
|
|
||||||
|
|
||||||
def match(self, userargs):
|
|
||||||
if userargs[0] != "kill":
|
|
||||||
return False
|
|
||||||
args = list(userargs)
|
|
||||||
if len(args) == 3:
|
|
||||||
# A specific signal is requested
|
|
||||||
signal = args.pop(1)
|
|
||||||
if signal not in self.args[1:]:
|
|
||||||
# Requested signal not in accepted list
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if len(args) != 2:
|
|
||||||
# Incorrect number of arguments
|
|
||||||
return False
|
|
||||||
if len(self.args) > 1:
|
|
||||||
# No signal requested, but filter requires specific signal
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
command = os.readlink("/proc/%d/exe" % int(args[1]))
|
|
||||||
# NOTE(dprince): /proc/PID/exe may have ' (deleted)' on
|
|
||||||
# the end if an executable is updated or deleted
|
|
||||||
if command.endswith(" (deleted)"):
|
|
||||||
command = command[:command.rindex(" ")]
|
|
||||||
if command != self.args[0]:
|
|
||||||
# Affected executable does not match
|
|
||||||
return False
|
|
||||||
except (ValueError, OSError):
|
|
||||||
# Incorrect PID
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class ReadFileFilter(CommandFilter):
|
|
||||||
"""Specific filter for the utils.read_file_as_root call"""
|
|
||||||
|
|
||||||
def __init__(self, file_path, *args):
|
|
||||||
self.file_path = file_path
|
|
||||||
super(ReadFileFilter, self).__init__("/bin/cat", "root", *args)
|
|
||||||
|
|
||||||
def match(self, userargs):
|
|
||||||
if userargs[0] != 'cat':
|
|
||||||
return False
|
|
||||||
if userargs[1] != self.file_path:
|
|
||||||
return False
|
|
||||||
if len(userargs) != 2:
|
|
||||||
return False
|
|
||||||
return True
|
|
@ -1,149 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright (c) 2011 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 logging
|
|
||||||
import logging.handlers
|
|
||||||
import os
|
|
||||||
import string
|
|
||||||
|
|
||||||
from manila.openstack.common.rootwrap import filters
|
|
||||||
|
|
||||||
|
|
||||||
class NoFilterMatched(Exception):
|
|
||||||
"""This exception is raised when no filter matched."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FilterMatchNotExecutable(Exception):
|
|
||||||
"""
|
|
||||||
This exception is raised when a filter matched but no executable was
|
|
||||||
found.
|
|
||||||
"""
|
|
||||||
def __init__(self, match=None, **kwargs):
|
|
||||||
self.match = match
|
|
||||||
|
|
||||||
|
|
||||||
class RootwrapConfig(object):
|
|
||||||
|
|
||||||
def __init__(self, config):
|
|
||||||
# filters_path
|
|
||||||
self.filters_path = config.get("DEFAULT", "filters_path").split(",")
|
|
||||||
|
|
||||||
# exec_dirs
|
|
||||||
if config.has_option("DEFAULT", "exec_dirs"):
|
|
||||||
self.exec_dirs = config.get("DEFAULT", "exec_dirs").split(",")
|
|
||||||
else:
|
|
||||||
# Use system PATH if exec_dirs is not specified
|
|
||||||
self.exec_dirs = os.environ["PATH"].split(':')
|
|
||||||
|
|
||||||
# syslog_log_facility
|
|
||||||
if config.has_option("DEFAULT", "syslog_log_facility"):
|
|
||||||
v = config.get("DEFAULT", "syslog_log_facility")
|
|
||||||
facility_names = logging.handlers.SysLogHandler.facility_names
|
|
||||||
self.syslog_log_facility = getattr(logging.handlers.SysLogHandler,
|
|
||||||
v, None)
|
|
||||||
if self.syslog_log_facility is None and v in facility_names:
|
|
||||||
self.syslog_log_facility = facility_names.get(v)
|
|
||||||
if self.syslog_log_facility is None:
|
|
||||||
raise ValueError('Unexpected syslog_log_facility: %s' % v)
|
|
||||||
else:
|
|
||||||
default_facility = logging.handlers.SysLogHandler.LOG_SYSLOG
|
|
||||||
self.syslog_log_facility = default_facility
|
|
||||||
|
|
||||||
# syslog_log_level
|
|
||||||
if config.has_option("DEFAULT", "syslog_log_level"):
|
|
||||||
v = config.get("DEFAULT", "syslog_log_level")
|
|
||||||
self.syslog_log_level = logging.getLevelName(v.upper())
|
|
||||||
if (self.syslog_log_level == "Level %s" % v.upper()):
|
|
||||||
raise ValueError('Unexepected syslog_log_level: %s' % v)
|
|
||||||
else:
|
|
||||||
self.syslog_log_level = logging.ERROR
|
|
||||||
|
|
||||||
# use_syslog
|
|
||||||
if config.has_option("DEFAULT", "use_syslog"):
|
|
||||||
self.use_syslog = config.getboolean("DEFAULT", "use_syslog")
|
|
||||||
else:
|
|
||||||
self.use_syslog = False
|
|
||||||
|
|
||||||
|
|
||||||
def setup_syslog(execname, facility, level):
|
|
||||||
rootwrap_logger = logging.getLogger()
|
|
||||||
rootwrap_logger.setLevel(level)
|
|
||||||
handler = logging.handlers.SysLogHandler(address='/dev/log',
|
|
||||||
facility=facility)
|
|
||||||
handler.setFormatter(logging.Formatter(
|
|
||||||
os.path.basename(execname) + ': %(message)s'))
|
|
||||||
rootwrap_logger.addHandler(handler)
|
|
||||||
|
|
||||||
|
|
||||||
def build_filter(class_name, *args):
|
|
||||||
"""Returns a filter object of class class_name"""
|
|
||||||
if not hasattr(filters, class_name):
|
|
||||||
logging.warning("Skipping unknown filter class (%s) specified "
|
|
||||||
"in filter definitions" % class_name)
|
|
||||||
return None
|
|
||||||
filterclass = getattr(filters, class_name)
|
|
||||||
return filterclass(*args)
|
|
||||||
|
|
||||||
|
|
||||||
def load_filters(filters_path):
|
|
||||||
"""Load filters from a list of directories"""
|
|
||||||
filterlist = []
|
|
||||||
for filterdir in filters_path:
|
|
||||||
if not os.path.isdir(filterdir):
|
|
||||||
continue
|
|
||||||
for filterfile in os.listdir(filterdir):
|
|
||||||
filterconfig = ConfigParser.RawConfigParser()
|
|
||||||
filterconfig.read(os.path.join(filterdir, filterfile))
|
|
||||||
for (name, value) in filterconfig.items("Filters"):
|
|
||||||
filterdefinition = [string.strip(s) for s in value.split(',')]
|
|
||||||
newfilter = build_filter(*filterdefinition)
|
|
||||||
if newfilter is None:
|
|
||||||
continue
|
|
||||||
newfilter.name = name
|
|
||||||
filterlist.append(newfilter)
|
|
||||||
return filterlist
|
|
||||||
|
|
||||||
|
|
||||||
def match_filter(filters, userargs, exec_dirs=[]):
|
|
||||||
"""
|
|
||||||
Checks user command and arguments through command filters and
|
|
||||||
returns the first matching filter.
|
|
||||||
Raises NoFilterMatched if no filter matched.
|
|
||||||
Raises FilterMatchNotExecutable if no executable was found for the
|
|
||||||
best filter match.
|
|
||||||
"""
|
|
||||||
first_not_executable_filter = None
|
|
||||||
|
|
||||||
for f in filters:
|
|
||||||
if f.match(userargs):
|
|
||||||
# Try other filters if executable is absent
|
|
||||||
if not f.get_exec(exec_dirs=exec_dirs):
|
|
||||||
if not first_not_executable_filter:
|
|
||||||
first_not_executable_filter = f
|
|
||||||
continue
|
|
||||||
# Otherwise return matching filter for execution
|
|
||||||
return f
|
|
||||||
|
|
||||||
if first_not_executable_filter:
|
|
||||||
# A filter matched, but no executable was found for it
|
|
||||||
raise FilterMatchNotExecutable(match=first_not_executable_filter)
|
|
||||||
|
|
||||||
# No filter matched
|
|
||||||
raise NoFilterMatched()
|
|
@ -15,7 +15,6 @@ module=log
|
|||||||
module=network_utils
|
module=network_utils
|
||||||
module=policy
|
module=policy
|
||||||
module=processutils
|
module=processutils
|
||||||
module=rootwrap
|
|
||||||
module=scheduler
|
module=scheduler
|
||||||
module=scheduler.filters
|
module=scheduler.filters
|
||||||
module=scheduler.weights
|
module=scheduler.weights
|
||||||
|
@ -11,6 +11,7 @@ lxml>=2.3
|
|||||||
oslo.config>=1.2.1
|
oslo.config>=1.2.1
|
||||||
oslo.db>=0.2.0
|
oslo.db>=0.2.0
|
||||||
oslo.messaging>=1.3.0
|
oslo.messaging>=1.3.0
|
||||||
|
oslo.rootwrap
|
||||||
paramiko>=1.13.0
|
paramiko>=1.13.0
|
||||||
Paste
|
Paste
|
||||||
PasteDeploy>=1.5.0
|
PasteDeploy>=1.5.0
|
||||||
|
@ -30,12 +30,13 @@ scripts =
|
|||||||
bin/manila-api
|
bin/manila-api
|
||||||
bin/manila-clear-rabbit-queues
|
bin/manila-clear-rabbit-queues
|
||||||
bin/manila-manage
|
bin/manila-manage
|
||||||
bin/manila-rootwrap
|
|
||||||
bin/manila-rpc-zmq-receiver
|
bin/manila-rpc-zmq-receiver
|
||||||
bin/manila-scheduler
|
bin/manila-scheduler
|
||||||
bin/manila-share
|
bin/manila-share
|
||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
|
console_scripts =
|
||||||
|
manila-rootwrap = oslo.rootwrap.cmd:main
|
||||||
manila.scheduler.filters =
|
manila.scheduler.filters =
|
||||||
AvailabilityZoneFilter = manila.openstack.common.scheduler.filters.availability_zone_filter:AvailabilityZoneFilter
|
AvailabilityZoneFilter = manila.openstack.common.scheduler.filters.availability_zone_filter:AvailabilityZoneFilter
|
||||||
CapabilitiesFilter = manila.openstack.common.scheduler.filters.capabilities_filter:CapabilitiesFilter
|
CapabilitiesFilter = manila.openstack.common.scheduler.filters.capabilities_filter:CapabilitiesFilter
|
||||||
|
Loading…
Reference in New Issue
Block a user