|
| 1 | +#!/usr/bin/env python |
| 2 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
| 3 | + |
| 4 | +# Copyright (c) 2012 OpenStack Foundation. |
| 5 | +# All Rights Reserved. |
| 6 | +# |
| 7 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 8 | +# not use this file except in compliance with the License. You may obtain |
| 9 | +# a copy of the License at |
| 10 | +# |
| 11 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | +# |
| 13 | +# Unless required by applicable law or agreed to in writing, software |
| 14 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 15 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 16 | +# License for the specific language governing permissions and limitations |
| 17 | +# under the License. |
| 18 | + |
| 19 | +"""Root wrapper for Neutron |
| 20 | +
|
| 21 | + Filters which commands neutron is allowed to run as another user. |
| 22 | +
|
| 23 | + To use this, you should set the following in neutron.conf and the |
| 24 | + various .ini files for the agent plugins: |
| 25 | + root_helper=sudo neutron-rootwrap /etc/neutron/rootwrap.conf |
| 26 | +
|
| 27 | + You also need to let the neutron user run neutron-rootwrap as root in |
| 28 | + /etc/sudoers: |
| 29 | + neutron ALL = (root) NOPASSWD: /usr/bin/neutron-rootwrap |
| 30 | + /etc/neutron/rootwrap.conf * |
| 31 | +
|
| 32 | + Filter specs live in /etc/neutron/rootwrap.d/*.filters, or |
| 33 | + other locations pointed to by /etc/neutron/rootwrap.conf. |
| 34 | + To make allowed commands node-specific, your packaging should only |
| 35 | + install apropriate .filters for commands which are needed on each |
| 36 | + node. |
| 37 | +""" |
| 38 | + |
| 39 | +from __future__ import print_function |
| 40 | + |
| 41 | +import ConfigParser |
| 42 | +import logging |
| 43 | +import os |
| 44 | +import pwd |
| 45 | +import signal |
| 46 | +import subprocess |
| 47 | +import sys |
| 48 | + |
| 49 | + |
| 50 | +RC_UNAUTHORIZED = 99 |
| 51 | +RC_NOCOMMAND = 98 |
| 52 | +RC_BADCONFIG = 97 |
| 53 | +RC_NOEXECFOUND = 96 |
| 54 | + |
| 55 | + |
| 56 | +def _subprocess_setup(): |
| 57 | + # Python installs a SIGPIPE handler by default. This is usually not what |
| 58 | + # non-Python subprocesses expect. |
| 59 | + signal.signal(signal.SIGPIPE, signal.SIG_DFL) |
| 60 | + |
| 61 | + |
| 62 | +def _exit_error(execname, message, errorcode, log=True): |
| 63 | + print("%s: %s" % (execname, message)) |
| 64 | + if log: |
| 65 | + logging.error(message) |
| 66 | + sys.exit(errorcode) |
| 67 | + |
| 68 | + |
| 69 | +if __name__ == '__main__': |
| 70 | + # Split arguments, require at least a command |
| 71 | + execname = sys.argv.pop(0) |
| 72 | + if len(sys.argv) < 2: |
| 73 | + _exit_error(execname, "No command specified", RC_NOCOMMAND, log=False) |
| 74 | + |
| 75 | + configfile = sys.argv.pop(0) |
| 76 | + userargs = sys.argv[:] |
| 77 | + |
| 78 | + # Add ../ to sys.path to allow running from branch |
| 79 | + possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname), |
| 80 | + os.pardir, os.pardir)) |
| 81 | + if os.path.exists(os.path.join(possible_topdir, "neutron", "__init__.py")): |
| 82 | + sys.path.insert(0, possible_topdir) |
| 83 | + |
| 84 | + from neutron.rootwrap import wrapper |
| 85 | + |
| 86 | + # Load configuration |
| 87 | + try: |
| 88 | + rawconfig = ConfigParser.RawConfigParser() |
| 89 | + rawconfig.read(configfile) |
| 90 | + config = wrapper.RootwrapConfig(rawconfig) |
| 91 | + except ValueError as exc: |
| 92 | + msg = "Incorrect value in %s: %s" % (configfile, exc.message) |
| 93 | + _exit_error(execname, msg, RC_BADCONFIG, log=False) |
| 94 | + except ConfigParser.Error: |
| 95 | + _exit_error(execname, "Incorrect configuration file: %s" % configfile, |
| 96 | + RC_BADCONFIG, log=False) |
| 97 | + |
| 98 | + if config.use_syslog: |
| 99 | + wrapper.setup_syslog(execname, |
| 100 | + config.syslog_log_facility, |
| 101 | + config.syslog_log_level) |
| 102 | + |
| 103 | + # Execute command if it matches any of the loaded filters |
| 104 | + filters = wrapper.load_filters(config.filters_path) |
| 105 | + try: |
| 106 | + filtermatch = wrapper.match_filter(filters, userargs, |
| 107 | + exec_dirs=config.exec_dirs) |
| 108 | + if filtermatch: |
| 109 | + command = filtermatch.get_command(userargs, |
| 110 | + exec_dirs=config.exec_dirs) |
| 111 | + if config.use_syslog: |
| 112 | + logging.info("(%s > %s) Executing %s (filter match = %s)" % ( |
| 113 | + os.getlogin(), pwd.getpwuid(os.getuid())[0], |
| 114 | + command, filtermatch.name)) |
| 115 | + |
| 116 | + obj = subprocess.Popen(command, |
| 117 | + stdin=sys.stdin, |
| 118 | + stdout=sys.stdout, |
| 119 | + stderr=sys.stderr, |
| 120 | + preexec_fn=_subprocess_setup, |
| 121 | + env=filtermatch.get_environment(userargs)) |
| 122 | + obj.wait() |
| 123 | + sys.exit(obj.returncode) |
| 124 | + |
| 125 | + except wrapper.FilterMatchNotExecutable as exc: |
| 126 | + msg = ("Executable not found: %s (filter match = %s)" |
| 127 | + % (exc.match.exec_path, exc.match.name)) |
| 128 | + _exit_error(execname, msg, RC_NOEXECFOUND, log=config.use_syslog) |
| 129 | + |
| 130 | + except wrapper.NoFilterMatched: |
| 131 | + msg = ("Unauthorized command: %s (no filter matched)" |
| 132 | + % ' '.join(userargs)) |
| 133 | + _exit_error(execname, msg, RC_UNAUTHORIZED, log=config.use_syslog) |
0 commit comments