diff --git a/ironic_inspector/conf/iptables.py b/ironic_inspector/conf/iptables.py index 6b0c55340..7ecf61388 100644 --- a/ironic_inspector/conf/iptables.py +++ b/ironic_inspector/conf/iptables.py @@ -34,6 +34,12 @@ _OPTS = [ 'which are not in desired state are going to be ' 'blacklisted based on the list of neighbor MACs ' 'on these interfaces.')), + cfg.StrOpt('ip_version', + default='4', + choices=[('4', _('IPv4')), + ('6', _('IPv6'))], + help=_('The IP version that will be used for iptables filter. ' + 'Defaults to 4.')), ] diff --git a/ironic_inspector/pxe_filter/iptables.py b/ironic_inspector/pxe_filter/iptables.py index c4f1ad63e..62480c654 100644 --- a/ironic_inspector/pxe_filter/iptables.py +++ b/ironic_inspector/pxe_filter/iptables.py @@ -50,8 +50,18 @@ class IptablesFilter(pxe_filter.BaseFilter): self.interface = CONF.iptables.dnsmasq_interface self.chain = CONF.iptables.firewall_chain self.new_chain = self.chain + '_temp' + + # Determine arguments used for pxe filtering, we only support 4 and 6 + # at this time. + if CONF.iptables.ip_version == '4': + self._cmd_iptables = 'iptables' + self._dhcp_port = '67' + else: + self._cmd_iptables = 'ip6tables' + self._dhcp_port = '547' + self.base_command = ('sudo', 'ironic-inspector-rootwrap', - CONF.rootwrap_config, 'iptables') + CONF.rootwrap_config, self._cmd_iptables) def reset(self): self.enabled = True @@ -137,9 +147,9 @@ class IptablesFilter(pxe_filter.BaseFilter): # Swap chains self._iptables('-I', 'INPUT', '-i', self.interface, '-p', 'udp', - '--dport', '67', '-j', chain) + '--dport', self._dhcp_port, '-j', chain) self._iptables('-D', 'INPUT', '-i', self.interface, '-p', 'udp', - '--dport', '67', '-j', main_chain, + '--dport', self._dhcp_port, '-j', main_chain, ignore=True) self._iptables('-F', main_chain, ignore=True) self._iptables('-X', main_chain, ignore=True) @@ -163,7 +173,7 @@ class IptablesFilter(pxe_filter.BaseFilter): def _clean_up(self, chain): self._iptables('-D', 'INPUT', '-i', self.interface, '-p', 'udp', - '--dport', '67', '-j', chain, + '--dport', self._dhcp_port, '-j', chain, ignore=True) self._iptables('-F', chain, ignore=True) self._iptables('-X', chain, ignore=True) diff --git a/ironic_inspector/test/unit/test_iptables.py b/ironic_inspector/test/unit/test_iptables.py index 3053ead58..1e86190a5 100644 --- a/ironic_inspector/test/unit/test_iptables.py +++ b/ironic_inspector/test/unit/test_iptables.py @@ -114,20 +114,23 @@ class TestIptablesDriver(test_base.NodeTest): self.assertRaisesRegex(MyError, 'Oops!', self.driver.init_filter) self.check_fsm([pxe_filter.Events.initialize, pxe_filter.Events.reset]) - def test__iptables_args(self): + def _test__iptables_args(self, expected_port): + self.driver = iptables.IptablesFilter() + self.mock_iptables = self.useFixture( + fixtures.MockPatchObject(self.driver, '_iptables')).mock self.mock_should_enable_dhcp.return_value = True _iptables_expected_args = [ ('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.new_chain), + expected_port, '-j', self.driver.new_chain), ('-F', self.driver.new_chain), ('-X', self.driver.new_chain), ('-N', self.driver.new_chain), ('-A', self.driver.new_chain, '-j', 'ACCEPT'), ('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.new_chain), + expected_port, '-j', self.driver.new_chain), ('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.chain), + expected_port, '-j', self.driver.chain), ('-F', self.driver.chain), ('-X', self.driver.chain), ('-E', self.driver.new_chain, self.driver.chain) @@ -142,6 +145,14 @@ class TestIptablesDriver(test_base.NodeTest): self.mock__get_blacklist.assert_called_once_with(self.mock_ironic) self.check_fsm([pxe_filter.Events.sync]) + def test__iptables_args_ipv4(self): + CONF.set_override('ip_version', '4', 'iptables') + self._test__iptables_args('67') + + def test__iptables_args_ipv6(self): + CONF.set_override('ip_version', '6', 'iptables') + self._test__iptables_args('547') + def test__iptables_kwargs(self): _iptables_expected_kwargs = [ {'ignore': True}, @@ -163,13 +174,16 @@ class TestIptablesDriver(test_base.NodeTest): self.assertEqual(kwargs, call[1]) self.check_fsm([pxe_filter.Events.sync]) - def test_sync_with_blacklist(self): + def _test_sync_with_blacklist(self, expected_port): + self.driver = iptables.IptablesFilter() + self.mock_iptables = self.useFixture( + fixtures.MockPatchObject(self.driver, '_iptables')).mock self.mock__get_blacklist.return_value = ['AA:BB:CC:DD:EE:FF'] self.mock_should_enable_dhcp.return_value = True _iptables_expected_args = [ ('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.new_chain), + expected_port, '-j', self.driver.new_chain), ('-F', self.driver.new_chain), ('-X', self.driver.new_chain), ('-N', self.driver.new_chain), @@ -178,9 +192,9 @@ class TestIptablesDriver(test_base.NodeTest): self.mock__get_blacklist.return_value[0], '-j', 'DROP'), ('-A', self.driver.new_chain, '-j', 'ACCEPT'), ('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.new_chain), + expected_port, '-j', self.driver.new_chain), ('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.chain), + expected_port, '-j', self.driver.chain), ('-F', self.driver.chain), ('-X', self.driver.chain), ('-E', self.driver.new_chain, self.driver.chain) @@ -203,7 +217,18 @@ class TestIptablesDriver(test_base.NodeTest): self.mock__get_blacklist.assert_called_once_with(self.mock_ironic) self.assertFalse(self.mock_iptables.called) - def test__iptables_clean_cache_on_error(self): + def test_sync_with_blacklist_ipv4(self): + CONF.set_override('ip_version', '4', 'iptables') + self._test_sync_with_blacklist('67') + + def test_sync_with_blacklist_ipv6(self): + CONF.set_override('ip_version', '6', 'iptables') + self._test_sync_with_blacklist('547') + + def _test__iptables_clean_cache_on_error(self, expected_port): + self.driver = iptables.IptablesFilter() + self.mock_iptables = self.useFixture( + fixtures.MockPatchObject(self.driver, '_iptables')).mock self.mock__get_blacklist.return_value = ['AA:BB:CC:DD:EE:FF'] self.mock_should_enable_dhcp.return_value = True @@ -217,7 +242,7 @@ class TestIptablesDriver(test_base.NodeTest): syncs_expected_args = [ # driver reset ('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.new_chain), + expected_port, '-j', self.driver.new_chain), ('-F', self.driver.new_chain), ('-X', self.driver.new_chain), ('-N', self.driver.new_chain), @@ -226,9 +251,9 @@ class TestIptablesDriver(test_base.NodeTest): self.mock__get_blacklist.return_value[0], '-j', 'DROP'), ('-A', self.driver.new_chain, '-j', 'ACCEPT'), ('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.new_chain), + expected_port, '-j', self.driver.new_chain), ('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', - '67', '-j', self.driver.chain), + expected_port, '-j', self.driver.chain), ('-F', self.driver.chain), ('-X', self.driver.chain), ('-E', self.driver.new_chain, self.driver.chain) @@ -247,6 +272,24 @@ class TestIptablesDriver(test_base.NodeTest): self.assertEqual(args, call[0], 'idx: %s' % idx) self.mock__get_blacklist.assert_called_once_with(self.mock_ironic) + def test__iptables_clean_cache_on_error_ipv4(self): + CONF.set_override('ip_version', '4', 'iptables') + self._test__iptables_clean_cache_on_error('67') + + def test__iptables_clean_cache_on_error_ipv6(self): + CONF.set_override('ip_version', '6', 'iptables') + self._test__iptables_clean_cache_on_error('547') + + def test_iptables_command_ipv4(self): + CONF.set_override('ip_version', '4', 'iptables') + driver = iptables.IptablesFilter() + self.assertEqual(driver._cmd_iptables, 'iptables') + + def test_iptables_command_ipv6(self): + CONF.set_override('ip_version', '6', 'iptables') + driver = iptables.IptablesFilter() + self.assertEqual(driver._cmd_iptables, 'ip6tables') + class Test_ShouldEnableDhcp(test_base.BaseTest): def setUp(self): diff --git a/releasenotes/notes/support-ip6tables-ce30f614de502adb.yaml b/releasenotes/notes/support-ip6tables-ce30f614de502adb.yaml new file mode 100644 index 000000000..33235b678 --- /dev/null +++ b/releasenotes/notes/support-ip6tables-ce30f614de502adb.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Adds a configuration option ``[iptables]ip_version`` to specify the + desired ip version for the iptables pxe filter, possible values are ``4`` + and ``6``, the default value is ``4``. When set to ``6``, the iptables + pxe filter will use ``ip6tables`` command to manage rules for the DHCPv6 + port ``547``. diff --git a/rootwrap.d/ironic-inspector.filters b/rootwrap.d/ironic-inspector.filters index 352dd843a..4a38b515f 100644 --- a/rootwrap.d/ironic-inspector.filters +++ b/rootwrap.d/ironic-inspector.filters @@ -2,8 +2,9 @@ [Filters] # ironic-inspector-rootwrap command filters for firewall manipulation -# ironic_inspector/firewall.py +# ironic_inspector/pxe_filter/iptables.py iptables: CommandFilter, iptables, root +ip6tables: CommandFilter, ip6tables, root # ironic-inspector-rootwrap command filters for systemctl manipulation of the dnsmasq service # ironic_inspector/pxe_filter/dnsmasq.py