def test_validate_order(self): policy = { "selector": "a == 'b'", "order": 10, "inbound_rules": [], "outbound_rules": [], } common.validate_policy(TieredPolicyId("a", "b"), policy) policy = { "selector": "a == 'b'", "order": "10", "inbound_rules": [], "outbound_rules": [], } with self.assertRaises(ValidationFailed): common.validate_policy(TieredPolicyId("a", "b"), policy) policy = { "selector": "a == 'b'", "inbound_rules": [], "outbound_rules": [], } with self.assertRaises(ValidationFailed): common.validate_policy(TieredPolicyId("a", "b"), policy) policy = { "order": 10, "inbound_rules": [], "outbound_rules": [], } with self.assertRaises(ValidationFailed): common.validate_policy(TieredPolicyId("a", "b"), policy)
def test_replace_selector_with_object(self): """ Checks that the validate_profile() method replaces selectors (with their object representations). """ policy = { "selector": "a == 'b'", "inbound_rules": [{ "src_selector": "b == 'c'", "dst_selector": "e == 'f'" }], "outbound_rules": [{ "src_selector": "h == 'c'", "dst_selector": "i == 'f'" }], "order": 10 } common.validate_policy(TieredPolicyId("a", "b"), policy) self.assertEqual(policy["selector"], parse_selector("a == 'b'")) self.assertEqual(policy["inbound_rules"][0]["src_selector"], parse_selector("b == 'c'")) self.assertEqual(policy["inbound_rules"][0]["dst_selector"], parse_selector("e == 'f'")) self.assertEqual(policy["outbound_rules"][0]["src_selector"], parse_selector("h == 'c'")) self.assertEqual(policy["outbound_rules"][0]["dst_selector"], parse_selector("i == 'f'"))
def test_label_inheritance(self): # Make sure we have an endpoint so that we can check that it gets # put in the dirty set. These have no labels at all so we test # that no labels gets translated to an empty dict. self.mgr.on_endpoint_update(ENDPOINT_ID, {"name": "tap12345", "profile_ids": ["prof1"]}, async=True) self.mgr.on_endpoint_update(ENDPOINT_ID_2, {"name": "tap23456", "profile_ids": ["prof2"]}, async=True) # And we need a selector to pick out one of the endpoints by the labels # attached to its parent. self.mgr.on_policy_selector_update(TieredPolicyId("a", "b"), parse_selector('a == "b"'), 10, async=True) self.step_actor(self.mgr) with mock.patch.object(self.mgr, "_update_dirty_policy") as m_update: self.mgr.on_prof_labels_set("prof1", {"a": "b"}, async=True) self.step_actor(self.mgr) # Only the first endpoint should end up matching the selector. self.assertEqual(self.mgr.endpoints_with_dirty_policy, set([ENDPOINT_ID])) # And an update should be triggered. self.assertEqual(m_update.mock_calls, [mock.call()])
def on_tiered_policy_delete(self, response, tier, policy_id): """Handler for tiered rules deletes, passes update to the splitter.""" _log.debug("Rules for %s/%s deleted", tier, policy_id) _stats.increment("tiered rules deleted") policy_id = TieredPolicyId(tier, policy_id) self.splitter.on_rules_update(policy_id, None) self.splitter.on_policy_selector_update(policy_id, None, None)
def test_tiered_policy_chain_names(self): chain_names = self.iptables_generator.profile_chain_names( TieredPolicyId("tier", "pol") ) self.assertEqual(chain_names, set(['felix-p-tier/pol-o', 'felix-p-tier/pol-i']))
def on_tiered_policy_update(self, msg): _log.debug("Rules for %s/%s set", msg.id.tier, msg.id.name) _stats.increment("Tiered rules created/updated") policy_id = TieredPolicyId(msg.id.tier, msg.id.name) rules = { "inbound_rules": convert_pb_rules(msg.policy.inbound_rules), "outbound_rules": convert_pb_rules(msg.policy.outbound_rules), } self.splitter.on_rules_update(policy_id, rules)
def test_validate_order(self): policy = { "selector": "a == 'b'", "order": 10, "inbound_rules": [], "outbound_rules": [], } common.validate_policy(TieredPolicyId("a", "b"), policy) policy = { "selector": "a == 'b'", "order": "10", "inbound_rules": [], "outbound_rules": [], } with self.assertRaises(ValidationFailed): common.validate_policy(TieredPolicyId("a", "b"), policy) policy = { "selector": "a == 'b'", "inbound_rules": [], "outbound_rules": [], } common.validate_policy(TieredPolicyId("a", "b"), policy) self.assertEqual(policy["order"], common.INFINITY) self.assertGreater(policy["order"], 9999999999999999999999999999999999) policy = { "selector": "a == 'b'", "inbound_rules": [], "outbound_rules": [], "order": "default", } common.validate_policy(TieredPolicyId("a", "b"), policy) self.assertEqual(policy["order"], common.INFINITY) self.assertGreater(policy["order"], 9999999999999999999999999999999999) policy = { "order": 10, "inbound_rules": [], "outbound_rules": [], } with self.assertRaises(ValidationFailed): common.validate_policy(TieredPolicyId("a", "b"), policy)
def on_tiered_policy_set(self, response, tier, policy_id): _log.debug("Rules for %s/%s set", tier, policy_id) _stats.increment("Tiered rules created/updated") policy_id = TieredPolicyId(tier, policy_id) rules = parse_policy(policy_id, response.value) if rules is not None: selector = rules.pop("selector") order = rules.pop("order") self.splitter.on_rules_update(policy_id, rules) self.splitter.on_policy_selector_update(policy_id, selector, order) else: self.splitter.on_rules_update(policy_id, None) self.splitter.on_policy_selector_update(policy_id, None, None)
def test_validate_policy(self): policy_id = TieredPolicyId("a", "b") with self.assertRaisesRegexp(ValidationFailed, "Expected policy 'a/b' to " "be a dict"): common.validate_policy(policy_id, []) rules = {'selector': "+abcd", # Bad selector 'inbound_rules': [], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Failed to parse selector"): common.validate_policy(policy_id, rules)
def test_tiered_policy_mainline(self, m_devices): self.config.plugins["iptables_generator"] = self.m_ipt_gen ep = self.get_local_endpoint(ENDPOINT_ID, futils.IPV4) mac = stub_utils.get_mac() ep.on_endpoint_update( { 'state': "active", 'endpoint': "endpoint_id", 'mac': mac, 'name': "tap1234", 'ipv4_nets': ["10.0.0.1"], 'profile_ids': ["prof1"] }, async=True) self.step_actor(ep) self.assertEqual( self.m_ipt_gen.endpoint_updates.mock_calls, [ mock.call(4, 'd', '1234', mac, ['prof1'], {}), ] ) self.m_ipt_gen.endpoint_updates.reset_mock() tiers = OrderedDict() t1_1 = TieredPolicyId("t1", "t1_1") t1_2 = TieredPolicyId("t1", "t1_2") tiers["t1"] = [t1_1, t1_2] t2_1 = TieredPolicyId("t2", "t2_1") tiers["t2"] = [t2_1] ep.on_tiered_policy_update(tiers, async=True) self.step_actor(ep) self.assertEqual( self.m_ipt_gen.endpoint_updates.mock_calls, [ mock.call(4, 'd', '1234', mac, ['prof1'], OrderedDict([('t1', [TieredPolicyId('t1','t1_1'), TieredPolicyId('t1','t1_2')]), ('t2', [TieredPolicyId('t2','t2_1')])])) ])
def test_validate_rules(self): profile_id = "valid_name-ok." rules = {'inbound_rules': [], 'outbound_rules': []} common.validate_profile(profile_id, rules.copy()) with self.assertRaisesRegexp( ValidationFailed, "Expected profile 'valid_name-ok.' to " "be a dict"): common.validate_profile(profile_id, []) with self.assertRaisesRegexp(ValidationFailed, "Invalid profile ID"): common.validate_profile("a&b", rules.copy()) with self.assertRaisesRegexp(ValidationFailed, "Invalid profile ID"): common.validate_policy(TieredPolicyId("+123", "abc"), rules.copy()) with self.assertRaisesRegexp(ValidationFailed, "Invalid profile ID"): common.validate_policy(TieredPolicyId("abc", "+"), rules.copy()) # No rules. with self.assertRaisesRegexp(ValidationFailed, "No outbound_rules"): common.validate_profile(profile_id, {'inbound_rules': []}) with self.assertRaisesRegexp(ValidationFailed, "No inbound_rules"): common.validate_profile(profile_id, {'outbound_rules': []}) rules = {'inbound_rules': 3, 'outbound_rules': []} with self.assertRaisesRegexp( ValidationFailed, "Expected rules\[inbound_rules\] to be a list"): common.validate_profile(profile_id, rules.copy()) rule = "not a dict" rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Rule should be a dict"): common.validate_profile(profile_id, rules.copy()) rule = {'bad_key': ""} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Rule contains unknown keys"): common.validate_profile(profile_id, rules) rule = {'protocol': "bloop"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp( ValidationFailed, "Invalid protocol bloop in rule " "{'protocol': 'bloop'}"): common.validate_profile(profile_id, rules) rule = {'ip_version': 5} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid ip_version in rule"): common.validate_profile(profile_id, rules) rule = {'ip_version': 4, 'protocol': "icmpv6"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Using icmpv6 with IPv4"): common.validate_profile(profile_id, rules) rule = {'ip_version': 6, 'protocol': "icmp"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Using icmp with IPv6"): common.validate_profile(profile_id, rules) rule = {'src_tag': "abc", 'protocol': "icmp"} rules = {'inbound_rules': [rule], 'outbound_rules': []} common.validate_profile(profile_id, rules) rule = {'src_tag': "abc", 'protocol': "123"} rules = {'inbound_rules': [rule], 'outbound_rules': []} common.validate_profile(profile_id, rules) rule = {'protocol': "256"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid protocol 256 in rule"): common.validate_profile(profile_id, rules) rule = {'protocol': "0"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid protocol 0 in rule"): common.validate_profile(profile_id, rules) rule = {'src_tag': "a!b", 'protocol': "icmp"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid src_tag"): common.validate_profile(profile_id, rules) rule = {'dst_tag': "x,y", 'protocol': "icmp"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid dst_tag"): common.validate_profile(profile_id, rules) rule = {'src_selector': "a!b"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid src_selector"): common.validate_profile(profile_id, rules) rule = {'dst_selector': "+b"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid dst_selector"): common.validate_profile(profile_id, rules) rule = {'src_net': "nonsense"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid CIDR"): common.validate_profile(profile_id, rules) rule = {'dst_net': "1.2.3.4/16", 'ip_version': 6} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid CIDR"): common.validate_profile(profile_id, rules) rule = {'src_ports': "nonsense"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Expected ports to be a list"): common.validate_profile(profile_id, rules) rule = {'dst_ports': [32, "nonsense"]} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid port"): common.validate_profile(profile_id, rules) rule = {'action': "nonsense"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "Invalid action"): common.validate_profile(profile_id, rules) rule = {'icmp_type': "nonsense"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "ICMP type is not an integer"): common.validate_profile(profile_id, rules) rule = {'icmp_type': -1} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "ICMP type is out of range"): common.validate_profile(profile_id, rules) rule = {'icmp_type': 256} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "ICMP type is out of range"): common.validate_profile(profile_id, rules) rule = {'icmp_type': 22, 'icmp_code': "2"} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "ICMP code is not an integer"): common.validate_profile(profile_id, rules) rule = {'icmp_type': 0, 'icmp_code': -1} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "ICMP code is out of range"): common.validate_profile(profile_id, rules) rule = {'icmp_type': 0, 'icmp_code': 256} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "ICMP code is out of range"): common.validate_profile(profile_id, rules) rule = {'icmp_code': 2} rules = {'inbound_rules': [rule], 'outbound_rules': []} with self.assertRaisesRegexp(ValidationFailed, "ICMP code specified without ICMP type"): common.validate_profile(profile_id, rules)
"ipv6_nets": ["dead::beef/128"] } ENDPOINT_STR = json.dumps(VALID_ENDPOINT) RULES = { "inbound_rules": [], "outbound_rules": [], } RULES_STR = json.dumps(RULES) TAGS = ["a", "b"] TAGS_STR = json.dumps(TAGS) ETCD_ADDRESS = 'localhost:4001' POLICY_ID = TieredPolicyId("tiername", "polname") POLICY = { "selector": "a == 'b'", "inbound_rules": [], "outbound_rules": [], "order": 10, } SELECTOR = parse_selector(POLICY["selector"]) POLICY_PARSED = { "inbound_rules": [], "outbound_rules": [], } POLICY_STR = json.dumps(POLICY) class TestEtcdAPI(BaseTestCase):
def _apply_endpoint_update(self): pending_endpoint = self._pending_endpoint if pending_endpoint == self.endpoint: _log.debug("Endpoint hasn't changed, nothing to do") return # Calculate the set of IPs that we had before this update. Needed on # the update and delete code paths below. if self.endpoint: old_ips = set( futils.net_to_ip(n) for n in self.endpoint.get(self.nets_key, [])) old_nat_mappings = self.endpoint.get(self.nat_key, []) else: old_ips = set() old_nat_mappings = [] all_old_ips = old_ips | set([n["ext_ip"] for n in old_nat_mappings]) if pending_endpoint: # Update/create. if pending_endpoint.get('mac') != self._mac: # Either we have not seen this MAC before, or it has changed. _log.debug("Endpoint MAC changed to %s", pending_endpoint.get("mac")) self._mac = pending_endpoint.get('mac') self._mac_changed = True # MAC change requires refresh of iptables rules and ARP table. self._iptables_in_sync = False self._device_in_sync = False new_iface_name = pending_endpoint["name"] # Interface renames are handled in the EndpointManager by # simulating a delete then an add. We shouldn't see one here. assert (self.endpoint is None or self._iface_name == new_iface_name), ( "Unexpected change of interface name.") if self.endpoint is None: # This is the first time we have seen the endpoint, so extract # the interface name and endpoint ID. self._iface_name = new_iface_name self._suffix = interface_to_chain_suffix( self.config, self._iface_name) _log.debug("Learned interface name/suffix: %s/%s", self._iface_name, self._suffix) # First time through, need to program everything. self._iptables_in_sync = False self._device_in_sync = False if self._device_is_up is None: _log.debug("Learned interface name, checking if device " "is up.") self._device_is_up = ( devices.interface_exists(self._iface_name) and devices.interface_up(self._iface_name)) # Check if the profile ID or IP addresses have changed, requiring # a refresh of the dataplane. profile_ids = set(pending_endpoint.get("profile_ids", [])) if profile_ids != self._explicit_profile_ids: # Profile ID update requires iptables update but not device # update. _log.debug( "Profile IDs changed from %s to %s, need to update " "iptables", self._rules_ref_helper.required_refs, profile_ids) self._explicit_profile_ids = profile_ids self._iptables_in_sync = False self._profile_ids_dirty = True # Check for changes to values that require a device update. if self.endpoint: if self.endpoint.get("state") != pending_endpoint.get("state"): _log.debug("Desired interface state updated.") self._device_in_sync = False self._iptables_in_sync = False new_ips = set( futils.net_to_ip(n) for n in pending_endpoint.get(self.nets_key, [])) if old_ips != new_ips: # IP addresses have changed, need to update the routing # table. _log.debug("IP addresses changed, need to update routing") self._device_in_sync = False new_nat_mappings = pending_endpoint.get(self.nat_key, []) if old_nat_mappings != new_nat_mappings: _log.debug("NAT mappings have changed, refreshing.") self._device_in_sync = False self._iptables_in_sync = False all_new_ips = new_ips | set( [n["ext_ip"] for n in new_nat_mappings]) if all_old_ips != all_new_ips: # Ensure we clean up any conntrack entries for IPs that # have been removed. _log.debug("Set of all IPs changed from %s to %s", all_old_ips, all_new_ips) self._removed_ips |= all_old_ips self._removed_ips -= all_new_ips tiers = pending_endpoint.get("tiers", []) tier_dict = OrderedDict() for tier in tiers or []: pols = [] for pol in tier["policies"] or []: pol_id = TieredPolicyId(tier["name"], pol) pols.append(pol_id) tier_dict[tier["name"]] = pols if self._pol_ids_by_tier.items() != tier_dict.items(): self._pol_ids_by_tier = tier_dict self._iptables_in_sync = False self._profile_ids_dirty = True else: # Delete of the endpoint. Need to resync everything. self._profile_ids_dirty = True self._iptables_in_sync = False self._device_in_sync = False self._removed_ips |= all_old_ips self.endpoint = pending_endpoint self._endpoint_update_pending = False self._pending_endpoint = None
def on_tiered_policy_remove(self, msg): _log.debug("Rules for %s/%s set", msg.id.tier, msg.id.name) _stats.increment("Tiered rules created/updated") policy_id = TieredPolicyId(msg.id.tier, msg.id.name) self.splitter.on_rules_update(policy_id, None)
def test_tiered_policy_ordering_and_updates(self): """ Check that the tier_sequence ordering is updated correctly as we add and remove tiers and policies. """ # Make sure we have an endpoint so that we can check that it gets # put in the dirty set. self.mgr.on_datamodel_in_sync(async=True) self.mgr.on_endpoint_update(ENDPOINT_ID, {"name": "tap12345"}, async=True) self.step_actor(self.mgr) # Pretend that the endpoint is alive so that we'll send updates to id. m_endpoint = Mock(spec=LocalEndpoint) self.mgr.objects_by_id[ENDPOINT_ID] = m_endpoint self.mgr._is_starting_or_live = Mock(return_value=True) # Add a profile into the tier so it'll apply to the endpoint. pol_id_a = TieredPolicyId("a", "a1") self.mgr.on_policy_selector_update(pol_id_a, parse_selector("all()"), 10, async=True) pol_id_b = TieredPolicyId("b", "b1") self.mgr.on_policy_selector_update(pol_id_b, parse_selector("all()"), 10, async=True) pol_id_c1 = TieredPolicyId("c1", "c1") self.mgr.on_policy_selector_update(pol_id_c1, parse_selector("all()"), 10, async=True) pol_id_c2 = TieredPolicyId("c2", "c2") self.mgr.on_policy_selector_update(pol_id_c2, parse_selector("all()"), 10, async=True) pol_id_c3 = TieredPolicyId("c3", "c3") self.mgr.on_policy_selector_update(pol_id_c3, parse_selector("all()"), 10, async=True) self.step_actor(self.mgr) # Since we haven't set the tier ID yet, the policy won't get applied... self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls, [mock.call(OrderedDict(), async=True)] * 5) m_endpoint.on_tiered_policy_update.reset_mock() # Adding a tier should trigger an update, adding the tier and policy. self.mgr.on_tier_data_update("a", {"order": 1}, async=True) self.step_actor(self.mgr) self.assertEqual(self.mgr.endpoints_with_dirty_policy, set()) tiers = OrderedDict() tiers["a"] = [pol_id_a] self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls, [mock.call(tiers, async=True)]) m_endpoint.on_tiered_policy_update.reset_mock() # Idempotent update should get squashed. self.mgr.on_tier_data_update("a", {"order": 2}, async=True) self.mgr.on_tier_data_update("a", {"order": 2}, async=True) self.step_actor(self.mgr) self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls, []) # Adding another tier should trigger an update. self.mgr.on_tier_data_update("b", {"order": 3}, async=True) self.step_actor(self.mgr) tiers = OrderedDict() tiers["a"] = [pol_id_a] tiers["b"] = [pol_id_b] self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls, [mock.call(tiers, async=True)]) m_endpoint.on_tiered_policy_update.reset_mock() # Swapping the order should trigger an update. self.mgr.on_tier_data_update("b", {"order": 1}, async=True) self.step_actor(self.mgr) tiers = OrderedDict() tiers["b"] = [pol_id_b] tiers["a"] = [pol_id_a] self.assertEqual(m_endpoint.on_tiered_policy_update.mock_calls, [mock.call(tiers, async=True)]) m_endpoint.on_tiered_policy_update.reset_mock() # Check deletion and that it's idempotent. self.mgr.on_tier_data_update("b", None, async=True) self.step_actor(self.mgr) self.mgr.on_policy_selector_update(pol_id_b, None, None, async=True) self.mgr.on_policy_selector_update(pol_id_b, None, None, async=True) self.step_actor(self.mgr) self.mgr.on_tier_data_update("b", None, async=True) self.step_actor(self.mgr) self.mgr.on_policy_selector_update(pol_id_b, None, None, async=True) self.mgr.on_policy_selector_update(pol_id_b, None, None, async=True) self.step_actor(self.mgr) tiers = OrderedDict() tiers["a"] = [pol_id_a] self.assertEqual( m_endpoint.on_tiered_policy_update.mock_calls, [mock.call(tiers, async=True)] * 2 # One for policy, one for tier. ) m_endpoint.on_tiered_policy_update.reset_mock() # Check lexicographic tie-breaker. self.mgr.on_tier_data_update("c1", {"order": 0}, async=True) self.mgr.on_tier_data_update("c2", {"order": 0}, async=True) self.mgr.on_tier_data_update("c3", {"order": 0}, async=True) self.step_actor(self.mgr) tiers = OrderedDict() # All 'c's should sort before 'a' due to explicit ordering but 'c's # should sort in lexicographic order. tiers["c1"] = [pol_id_c1] tiers["c2"] = [pol_id_c2] tiers["c3"] = [pol_id_c3] tiers["a"] = [pol_id_a] actual_call = m_endpoint.on_tiered_policy_update.mock_calls[-1] expected_call = mock.call(tiers, async=True) self.assertEqual(actual_call, expected_call, msg="\nExpected: %s\n Got: %s" % (expected_call, actual_call)) m_endpoint.on_tiered_policy_update.reset_mock()