From f37eb0fc587a24b3a29a695c2d2b708d2f7c32f2 Mon Sep 17 00:00:00 2001 From: Kaifeng Wang Date: Tue, 6 Nov 2018 13:42:29 +0800 Subject: [PATCH] Support ip6tables for iptables pxe filter Adds a configuration option [iptables]ip_version to specify the desired ip version for the iptables pxe filter, which can be set to 4 or 6. When set to 6, the iptables pxe filter will use ip6tables command to manage rules for the port 547 which is the port of DHCPv6 server side. The string type is used to make room for the future, when there is need to automatically determine ip version from the binding interface. Change-Id: I7de2be5950a23def3ec6490f2e6dfa3d5c42798a Story: 1756012 Task: 11411 --- ironic_inspector/conf/iptables.py | 6 ++ ironic_inspector/pxe_filter/iptables.py | 18 +++-- ironic_inspector/test/unit/test_iptables.py | 67 +++++++++++++++---- .../support-ip6tables-ce30f614de502adb.yaml | 8 +++ rootwrap.d/ironic-inspector.filters | 3 +- 5 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/support-ip6tables-ce30f614de502adb.yaml 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