a5b12b675c
Add syslogging capabilities to nova-rootwrap, if you set parameter use_syslog to True. You can specify a facility (syslog_log_facility) and level (syslog_log_level) to use. Finalizes bp nova-rootwrap-options. In doing so, it moves rootwrap config parsing to a nova.rootwrap.wrapper object (and adds unit testing for it). It also improves log messages content (including the name of the matching filter and the escalation path used). Incidentally fixes bug 1084766. Change-Id: Idb8cd9c9febd6263dafab4bc2bff817f00c53dc0
128 lines
4.5 KiB
Python
Executable File
128 lines
4.5 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright (c) 2011 OpenStack, LLC.
|
|
# 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 Nova
|
|
|
|
Filters which commands nova is allowed to run as another user.
|
|
|
|
To use this, you should set the following in nova.conf:
|
|
rootwrap_config=/etc/nova/rootwrap.conf
|
|
|
|
You also need to let the nova user run nova-rootwrap as root in sudoers:
|
|
nova ALL = (root) NOPASSWD: /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf *
|
|
|
|
To make allowed commands node-specific, your packaging should only
|
|
install {compute,network,volume}.filters respectively on compute, network
|
|
and volume nodes (i.e. nova-api nodes should not have any of those files
|
|
installed).
|
|
"""
|
|
|
|
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, "nova", "__init__.py")):
|
|
sys.path.insert(0, possible_topdir)
|
|
|
|
from nova.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)
|