Use rootwrap to execute iptables instead of requiring root

This patch set adds support for rootwrap in order to execute iptables.

Co-Authored-By: Dmitry Tantsur <dtantsur@redhat.com>
Change-Id: I7c424c17222f119730b8c5ac0daafd9906282e4d
Closes-bug: #1495844
This commit is contained in:
Yuiko Takada 2015-09-15 19:39:21 +09:00 committed by Dmitry Tantsur
parent 7e10d5c2fa
commit 52ef561c9f
10 changed files with 96 additions and 13 deletions

View File

@ -161,6 +161,23 @@ for the other possible configuration options.
Configuration file contains a password and thus should be owned by ``root``
and should have access rights like ``0600``.
**ironic-inspector** requires root rights for managing iptables. It gets them
by running ``ironic-inspector-rootwrap`` utility with ``sudo``. To allow it,
copy ``rootwrap.conf`` to the configuration directory (e.g. as
``/etc/ironic-inspector/rootwrap.conf`` and create file
``/etc/sudoers.d/ironic-inspector-rootwrap`` with the following content::
stack ALL=(root) NOPASSWD: /usr/bin/ironic-inspector-rootwrap /etc/ironic-inspector/rootwrap.conf *
.. note::
``rootwrap.conf`` must be writeable only by root.
Replace ``stack`` with whatever user you'll be using to run
**ironic-inspector**.
Configuring PXE
^^^^^^^^^^^^^^^
As for PXE boot environment, you'll need:
* TFTP server running and accessible (see below for using *dnsmasq*).
@ -280,15 +297,10 @@ will be accessed by ramdisk on a booting machine).
Running
~~~~~~~
Run as ``root``::
::
ironic-inspector --config-file /etc/ironic-inspector/inspector.conf
.. note::
Running as ``root`` is not required if **ironic-inspector** does not
manage the firewall (i.e. ``manage_firewall`` is set to ``false`` in the
configuration file).
A good starting point for writing your own *systemd* unit should be `one used
in Fedora <http://pkgs.fedoraproject.org/cgit/openstack-ironic-discoverd.git/plain/openstack-ironic-discoverd.service>`_
(note usage of old name).

View File

@ -4,8 +4,9 @@ IRONIC_INSPECTOR_BIN_DIR=$(get_python_exec_prefix)
IRONIC_INSPECTOR_BIN_FILE=$IRONIC_INSPECTOR_BIN_DIR/ironic-inspector
IRONIC_INSPECTOR_CONF_DIR=${IRONIC_INSPECTOR_CONF_DIR:-/etc/ironic-inspector}
IRONIC_INSPECTOR_CONF_FILE=$IRONIC_INSPECTOR_CONF_DIR/inspector.conf
IRONIC_INSPECTOR_CMD="sudo $IRONIC_INSPECTOR_BIN_FILE --config-file $IRONIC_INSPECTOR_CONF_FILE"
IRONIC_INSPECTOR_CMD="$IRONIC_INSPECTOR_BIN_FILE --config-file $IRONIC_INSPECTOR_CONF_FILE"
IRONIC_INSPECTOR_DHCP_CONF_FILE=$IRONIC_INSPECTOR_CONF_DIR/dnsmasq.conf
IRONIC_INSPECTOR_ROOTWRAP_CONF_FILE=$IRONIC_INSPECTOR_CONF_DIR/rootwrap.conf
IRONIC_INSPECTOR_DATA_DIR=$DATA_DIR/ironic-inspector
IRONIC_INSPECTOR_ADMIN_USER=${IRONIC_INSPECTOR_ADMIN_USER:-ironic-inspector}
IRONIC_INSPECTOR_MANAGE_FIREWALL=$(trueorfalse True $IRONIC_INSPECTOR_MANAGE_FIREWALL)
@ -145,6 +146,20 @@ function configure_inspector {
if [ "$LOG_COLOR" == "True" ] && [ "$SYSLOG" == "False" ]; then
setup_colorized_logging $IRONIC_INSPECTOR_CONF_FILE DEFAULT
fi
cp "$IRONIC_INSPECTOR_DIR/rootwrap.conf" "$IRONIC_INSPECTOR_ROOTWRAP_CONF_FILE"
cp -r "$IRONIC_INSPECTOR_DIR/rootwrap.d" "$IRONIC_INSPECTOR_CONF_DIR"
local ironic_inspector_rootwrap=$(get_rootwrap_location ironic-inspector)
local rootwrap_sudoer_cmd="$ironic_inspector_rootwrap $IRONIC_INSPECTOR_CONF_DIR/rootwrap.conf *"
# Set up the rootwrap sudoers for ironic-inspector
local tempfile=`mktemp`
echo "$STACK_USER ALL=(root) NOPASSWD: $rootwrap_sudoer_cmd" >$tempfile
chmod 0640 $tempfile
sudo chown root:root $tempfile
sudo mv $tempfile /etc/sudoers.d/ironic-inspector-rootwrap
inspector_iniset DEFAULT rootwrap_config $IRONIC_INSPECTOR_ROOTWRAP_CONF_FILE
}
function configure_inspector_swift {
@ -188,6 +203,7 @@ function cleanup_inspector {
rm -rf $IRONIC_INSPECTOR_DATA_DIR
rm -f $IRONIC_TFTPBOOT_DIR/pxelinux.cfg/default
rm -f $IRONIC_TFTPBOOT_DIR/ironic-inspector.*
sudo rm -f /etc/sudoers.d/ironic-inspector-rootwrap
# Try to clean up firewall rules
sudo iptables -D INPUT -i $IRONIC_INSPECTOR_INTERFACE -p udp \

View File

@ -63,6 +63,10 @@
# value)
#ipmi_address_fields = ilo_address,drac_host,cimc_address
# Path to the rootwrap configuration file to use for running commands
# as root (string value)
#rootwrap_config = /etc/ironic-inspector/rootwrap.conf
#
# From oslo.log
#
@ -403,6 +407,9 @@
# Verify HTTPS connections. (boolean value)
#insecure = false
# The region in which the identity server can be found. (string value)
#region_name = <None>
# Directory used to cache files related to PKI tokens. (string value)
#signing_dir = <None>

View File

@ -249,6 +249,10 @@ SERVICE_OPTS = [
default=['ilo_address', 'drac_host', 'cimc_address'],
help='Ironic driver_info fields that are equivalent '
'to ipmi_address.'),
cfg.StrOpt('rootwrap_config',
default="/etc/ironic-inspector/rootwrap.conf",
help='Path to the rootwrap configuration file to use for '
'running commands as root'),
]

View File

@ -29,7 +29,7 @@ NEW_CHAIN = None
CHAIN = None
INTERFACE = None
LOCK = semaphore.BoundedSemaphore()
BASE_COMMAND = ('iptables',)
BASE_COMMAND = None
def _iptables(*args, **kwargs):
@ -61,19 +61,20 @@ def init():
INTERFACE = CONF.firewall.dnsmasq_interface
CHAIN = CONF.firewall.firewall_chain
NEW_CHAIN = CHAIN + '_temp'
BASE_COMMAND = ('sudo', 'ironic-inspector-rootwrap',
CONF.rootwrap_config, 'iptables',)
# -w flag makes iptables wait for xtables lock, but it's not supported
# everywhere yet
try:
with open(os.devnull, 'wb') as null:
subprocess.check_call(['iptables', '-w', '-h'],
subprocess.check_call(BASE_COMMAND + ('-w', '-h'),
stderr=null, stdout=null)
except subprocess.CalledProcessError:
LOG.warn(_LW('iptables does not support -w flag, please update '
'it to at least version 1.4.21'))
BASE_COMMAND = ('iptables',)
else:
BASE_COMMAND = ('iptables', '-w')
BASE_COMMAND += ('-w',)
_clean_up(CHAIN)
# Not really needed, but helps to validate that we have access to iptables

View File

@ -39,6 +39,8 @@ class TestFirewall(test_base.NodeTest):
self.assertEqual(0, mock_iptables.call_count)
def test_init_args(self, mock_call, mock_get_client, mock_iptables):
rootwrap_path = '/some/fake/path'
CONF.set_override('rootwrap_config', rootwrap_path)
firewall.init()
init_expected_args = [
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', '67',
@ -52,10 +54,14 @@ class TestFirewall(test_base.NodeTest):
for (args, call) in zip(init_expected_args, call_args_list):
self.assertEqual(args, call[0])
self.assertEqual(('iptables', '-w'), firewall.BASE_COMMAND)
expected = ('sudo', 'ironic-inspector-rootwrap', rootwrap_path,
'iptables', '-w')
self.assertEqual(expected, firewall.BASE_COMMAND)
def test_init_args_old_iptables(self, mock_call, mock_get_client,
mock_iptables):
rootwrap_path = '/some/fake/path'
CONF.set_override('rootwrap_config', rootwrap_path)
mock_call.side_effect = subprocess.CalledProcessError(2, '')
firewall.init()
init_expected_args = [
@ -70,7 +76,9 @@ class TestFirewall(test_base.NodeTest):
for (args, call) in zip(init_expected_args, call_args_list):
self.assertEqual(args, call[0])
self.assertEqual(('iptables',), firewall.BASE_COMMAND)
expected = ('sudo', 'ironic-inspector-rootwrap', rootwrap_path,
'iptables',)
self.assertEqual(expected, firewall.BASE_COMMAND)
def test_init_kwargs(self, mock_call, mock_get_client, mock_iptables):
firewall.init()

View File

@ -16,6 +16,7 @@ oslo.config>=2.3.0 # Apache-2.0
oslo.db>=2.4.1 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0
oslo.log>=1.8.0 # Apache-2.0
oslo.rootwrap>=2.0.0 # Apache-2.0
oslo.utils>=2.0.0 # Apache-2.0
six>=1.9.0
stevedore>=1.5.0 # Apache-2.0

27
rootwrap.conf Normal file
View File

@ -0,0 +1,27 @@
# Configuration for ironic-inspector-rootwrap
# This file should be owned by (and only-writeable by) the root user
[DEFAULT]
# List of directories to load filter definitions from (separated by ',').
# These directories MUST all be only writeable by root !
filters_path=/etc/ironic-inspector/rootwrap.d,/usr/share/ironic-inspector/rootwrap
# List of directories to search executables in, in case filters do not
# explicitely specify a full path (separated by ',')
# If not specified, defaults to system PATH environment variable.
# These directories MUST all be only writeable by root !
exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin
# Enable logging to syslog
# Default value is False
use_syslog=False
# Which syslog facility to use.
# Valid values include auth, authpriv, syslog, user0, user1...
# Default value is 'syslog'
syslog_log_facility=syslog
# Which messages to log.
# INFO means log all usage
# ERROR means only log unsuccessful attempts
syslog_log_level=ERROR

View File

@ -0,0 +1,6 @@
# ironic-inspector-rootwrap command filters for firewall manipulation
# This file should be owned by (and only-writeable by) the root user
[Filters]
# ironic_inspector/firewall.py
iptables: CommandFilter, iptables, root

View File

@ -21,6 +21,7 @@ packages =
[entry_points]
console_scripts =
ironic-inspector = ironic_inspector.main:main
ironic-inspector-rootwrap = oslo_rootwrap.cmd:main
ironic_inspector.hooks.processing =
scheduler = ironic_inspector.plugins.standard:SchedulerHook
validate_interfaces = ironic_inspector.plugins.standard:ValidateInterfacesHook