From 0c5969e5cf61939fd9ad614094d655cc4a36f4ad Mon Sep 17 00:00:00 2001 From: Shaun Crampton Date: Thu, 4 Aug 2016 17:56:55 +0100 Subject: [PATCH] Add config parameter to disable loose RPF check. --- calico/felix/config.py | 9 +++++++++ calico/felix/devices.py | 21 +++++++++++++++------ calico/felix/felix.py | 2 +- calico/felix/test/test_devices.py | 23 ++++++++++++++++++++--- calico/felix/test/test_felix.py | 4 ++-- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/calico/felix/config.py b/calico/felix/config.py index ace8125cbbd..cfeb5e9076a 100644 --- a/calico/felix/config.py +++ b/calico/felix/config.py @@ -240,6 +240,14 @@ def __init__(self, config_path): "One of 'DROP', 'ACCEPT', 'LOG-and-DROP', " "'LOG-and-ACCEPT'.", "DROP") + self.add_parameter("IgnoreLooseRPF", + "If set to true, Felix will ignore the kernel's " + "RPF check setting. If set to false, Felix will " + "abort if the RPF setting is not 'strict'. Should " + "only be set to true if workloads are incapable of " + "spoofing their source IP. (For example, " + "unprivileged containers.)", + False, value_is_bool=True) self.add_parameter("LogFilePath", "Path to log file", "/var/log/calico/felix.log") self.add_parameter("EtcdDriverLogFilePath", @@ -419,6 +427,7 @@ def _finish_update(self, final=False): self.FAILSAFE_OUTBOUND_PORTS = \ self.parameters["FailsafeOutboundHostPorts"].value self.ACTION_ON_DROP = self.parameters["DropActionOverride"].value + self.IGNORE_LOOSE_RPF = self.parameters["IgnoreLooseRPF"].value self._validate_cfg(final=final) diff --git a/calico/felix/devices.py b/calico/felix/devices.py index b24478b63f5..112516d60d0 100644 --- a/calico/felix/devices.py +++ b/calico/felix/devices.py @@ -36,7 +36,7 @@ _log = logging.getLogger(__name__) -def configure_global_kernel_config(): +def configure_global_kernel_config(config): """ Configures the global kernel config. In particular, sets the flags that we rely on to ensure security, such as the kernel's RPF check. @@ -67,11 +67,20 @@ def configure_global_kernel_config(): ps_name = "/proc/sys/net/ipv4/conf/all/rp_filter" rp_filter = int(_read_proc_sys(ps_name)) if rp_filter > 1: - _log.critical("Kernel's RPF check is set to 'loose'. This would " - "allow endpoints to spoof their IP address. Calico " - "requires net.ipv4.conf.all.rp_filter to be set to " - "0 or 1.") - raise BadKernelConfig("net.ipv4.conf.all.rp_filter set to 'loose'") + if config.IGNORE_LOOSE_RPF: + _log.warning( + "Kernel's RPF check is set to 'loose' and IgnoreLooseRPF " + "set to true. Calico will not be able to prevent workloads " + "from spoofing their source IP. Please ensure that some " + "other anti-spoofing mechanism is in place (such as running " + "only non-privileged containers)." + ) + else: + _log.critical("Kernel's RPF check is set to 'loose'. This would " + "allow endpoints to spoof their IP address. Calico " + "requires net.ipv4.conf.all.rp_filter to be set to " + "0 or 1.") + raise BadKernelConfig("net.ipv4.conf.all.rp_filter set to 'loose'") # Make sure the default for new interfaces is set to strict checking so # that there's no race when a new interface is added and felix hasn't diff --git a/calico/felix/felix.py b/calico/felix/felix.py index 6c1f2476544..8b132dba99f 100755 --- a/calico/felix/felix.py +++ b/calico/felix/felix.py @@ -76,7 +76,7 @@ def _main_greenlet(config): # Ensure the Kernel's global options are correctly configured for # Calico. - devices.configure_global_kernel_config() + devices.configure_global_kernel_config(config) # Check the commands we require are present. futils.check_command_deps() diff --git a/calico/felix/test/test_devices.py b/calico/felix/test/test_devices.py index 480969f2ead..a221a57d8a1 100644 --- a/calico/felix/test/test_devices.py +++ b/calico/felix/test/test_devices.py @@ -59,7 +59,9 @@ def test_configure_global_kernel_config(self, m_read_proc_sys, m_write_proc_sys, m_exists): - devices.configure_global_kernel_config() + m_config = mock.Mock() + m_config.IGNORE_LOOSE_RPF = True + devices.configure_global_kernel_config(m_config) m_write_proc_sys.assert_called_once_with( "/proc/sys/net/ipv4/conf/default/rp_filter", "1" ) @@ -73,14 +75,29 @@ def test_configure_global_kernel_config_no_sysfs(self, m_read_proc_sys, m_write_proc_sys, m_exists): + m_config = mock.Mock() + m_config.IGNORE_LOOSE_RPF = False self.assertRaises(devices.BadKernelConfig, - devices.configure_global_kernel_config) + devices.configure_global_kernel_config, m_config) def test_configure_global_kernel_config_bad_rp_filter(self): + m_config = mock.Mock() + m_config.IGNORE_LOOSE_RPF = False with mock.patch("calico.felix.devices._read_proc_sys", autospec=True, return_value="2") as m_read_proc_sys: self.assertRaises(devices.BadKernelConfig, - devices.configure_global_kernel_config) + devices.configure_global_kernel_config, + m_config) + + @mock.patch("calico.felix.devices._write_proc_sys", + autospec=True) + @mock.patch("calico.felix.devices._read_proc_sys", + autospec=True) + def test_configure_global_kernel_config_bad_rp_filter_ignored(self, m_rps, m_wps): + m_config = mock.Mock() + m_config.IGNORE_LOOSE_RPF = True + m_rps.return_value = "2" + devices.configure_global_kernel_config(m_config) def test_read_proc_sys(self): m_open = mock.mock_open(read_data="1\n") diff --git a/calico/felix/test/test_felix.py b/calico/felix/test/test_felix.py index 065b49fa805..6bc5f010134 100644 --- a/calico/felix/test/test_felix.py +++ b/calico/felix/test/test_felix.py @@ -111,7 +111,7 @@ def test_main_greenlet(self, m_iwait, m_MasqueradeManager, m_load.assert_called_once_with(async=False) m_iface_exists.assert_called_once_with("tunl0") m_iface_up.assert_called_once_with("tunl0") - m_configure_global_kernel_config.assert_called_once_with() + m_configure_global_kernel_config.assert_called_once_with(config) m_conntrack.assert_called_once_with() m_http_server.assert_called_once_with(("0.0.0.0", 9091), felix.MetricsHandler) @@ -170,7 +170,7 @@ def test_main_greenlet_no_ipv6(self, m_iwait, m_MasqueradeManager, self.assertRaises(TestException, felix._main_greenlet, config) m_load.assert_called_once_with(async=False) - m_configure_global_kernel_config.assert_called_once_with() + m_configure_global_kernel_config.assert_called_once_with(config) m_install_globals.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY, ip_version=4) m_conntrack.assert_called_once_with()