def test_negative_matches(self): self.index.on_labels_update("l0", {}) self.index.on_labels_update("l1", {"a": "a", "b": "b1"}) self.index.on_labels_update("l2", {"a": "a2", "b": "b2"}) self.index.on_labels_update("l3", {"a": "a3", "c": "c3"}) self.index.on_expression_update("not_a", parse_selector('a!="a"')) self.index.on_expression_update("not_d", parse_selector('d!="d"')) self.assert_add("not_d", "l0") self.assert_add("not_d", "l1") self.assert_add("not_d", "l2") self.assert_add("not_d", "l3") self.assert_add("not_a", "l0") self.assert_add("not_a", "l2") self.assert_add("not_a", "l3") self.assert_no_updates() self.index.on_labels_update("l2", {"a": "a", "b": "b2"}) self.assert_remove("not_a", "l2") self.assert_no_updates() self.index.on_labels_update("l1", {"a": "a", "d": "d1"}) self.index.on_labels_update("l2", {"a": "a", "d": "d"}) self.assert_remove("not_d", "l2") self.assert_no_updates() self.index.on_expression_update("not_a", None) self.index.on_expression_update("not_d", None) self.assert_remove("not_a", "l0") self.assert_remove("not_a", "l3") self.assert_remove("not_d", "l1") self.assert_remove("not_d", "l0") self.assert_remove("not_d", "l3") self.index.on_labels_update("l0", None) self.index.on_labels_update("l1", None) self.index.on_labels_update("l2", None) self.index.on_labels_update("l3", None) self.assert_indexes_empty()
def test_mainline(self): # Add some labels. self.index.on_labels_update("l1", {"a": "a1", "b": "b1", "c": "c1"}) self.assert_no_updates() # Add a non-matching expression self.index.on_expression_update("e1", parse_selector('d=="d1"')) self.assert_no_updates() # Add a matching expression self.index.on_expression_update("e2", parse_selector('a=="a1"')) self.assert_add("e2", "l1") self.assert_no_updates() # Update matching expression, still matches self.index.on_expression_update("e2", parse_selector('b=="b1"')) self.assert_no_updates() # Update matching expression, no-longer matches self.index.on_expression_update("e2", parse_selector('b=="b2"')) self.assert_remove("e2", "l1") self.assert_no_updates() # Update labels to match. self.index.on_labels_update("l1", {"b": "b2", "d": "d1"}) self.assert_add("e1", "l1") self.assert_add("e2", "l1") self.assert_no_updates() self.index.on_labels_update("l1", None) self.assert_remove("e1", "l1") self.assert_remove("e2", "l1") self.index.on_expression_update("e1", None) self.index.on_expression_update("e2", None) self.assert_indexes_empty()
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_mainline(self): # Add some labels. self.index.on_labels_update("l1", {"a": "a1", "b": "b1", "c": "c1"}) self.assert_no_updates() # Add a non-matching expression self.index.on_expression_update("e1", parse_selector('d=="d1"')) self.assert_no_updates() # Add a matching expression self.index.on_expression_update("e2", parse_selector('a=="a1"')) self.assert_add("e2", "l1") self.assert_no_updates() # Update matching expression, still matches self.index.on_expression_update("e2", parse_selector('b=="b1"')) self.assert_no_updates() # Update matching expression, no-longer matches self.index.on_expression_update("e2", parse_selector('b=="b2"')) self.assert_remove("e2", "l1") self.assert_no_updates() # Update labels to match. self.index.on_labels_update("l1", {"b": "b2", "d": "d1"}) self.assert_add("e1", "l1") self.assert_add("e2", "l1") self.assert_no_updates() self.index.on_labels_update("l1", None) self.assert_remove("e1", "l1") self.assert_remove("e2", "l1") self.index.on_expression_update("e1", None) self.index.on_expression_update("e2", None) self.assert_indexes_empty()
def test_multiple_matches(self): # Insert some labels. self.index.on_labels_update("l1", {"a": "a", "b": "b1"}) self.index.on_labels_update("l2", {"a": "a", "b": "b2"}) self.index.on_labels_update("l3", {"a": "a", "b": "b3"}) self.index.on_labels_update("l4", {"c": "c1"}) # And some expressions. self.index.on_expression_update("e1", parse_selector('a=="a"')) # e2 starts off matching l4, which only has index entries for c==c1. # This ensures that we hit the cleanup code when we change e2 because # the the indexes that apply to the new value of e2 don't apply to # l4. self.index.on_expression_update("e2", parse_selector('c=="c1"')) self.assert_add("e2", "l4") self.index.on_expression_update("e2", parse_selector('a=="a" && b=="b1"')) self.assert_remove("e2", "l4") self.assert_add("e1", "l1") self.assert_add("e1", "l2") self.assert_add("e1", "l3") self.assert_add("e2", "l1") self.assert_no_updates() self.index.on_expression_update("e1", parse_selector('a=="z"')) self.assert_remove("e1", "l1") self.assert_remove("e1", "l2") self.assert_remove("e1", "l3") self.assert_no_updates() self.index.on_expression_update("e1", None) self.assert_no_updates() self.index.on_expression_update("e2", None) self.assert_remove("e2", "l1") self.index.on_expression_update("e4", None) self.assert_no_updates()
def test_multiple_matches(self): # Insert some labels. self.index.on_labels_update("l1", {"a": "a", "b": "b1"}) self.index.on_labels_update("l2", {"a": "a", "b": "b2"}) self.index.on_labels_update("l3", {"a": "a", "b": "b3"}) self.index.on_labels_update("l4", {"c": "c1"}) # And some expressions. self.index.on_expression_update("e1", parse_selector('a=="a"')) # e2 starts off matching l4, which only has index entries for c==c1. # This ensures that we hit the cleanup code when we change e2 because # the the indexes that apply to the new value of e2 don't apply to # l4. self.index.on_expression_update("e2", parse_selector('c=="c1"')) self.assert_add("e2", "l4") self.index.on_expression_update("e2", parse_selector('a=="a" && b=="b1"')) self.assert_remove("e2", "l4") self.assert_add("e1", "l1") self.assert_add("e1", "l2") self.assert_add("e1", "l3") self.assert_add("e2", "l1") self.assert_no_updates() self.index.on_expression_update("e1", parse_selector('a=="z"')) self.assert_remove("e1", "l1") self.assert_remove("e1", "l2") self.assert_remove("e1", "l3") self.assert_no_updates() self.index.on_expression_update("e1", None) self.assert_no_updates() self.index.on_expression_update("e2", None) self.assert_remove("e2", "l1") self.index.on_expression_update("e4", None) self.assert_no_updates()
def test_negative_matches(self): self.index.on_labels_update("l0", {}) self.index.on_labels_update("l1", {"a": "a", "b": "b1"}) self.index.on_labels_update("l2", {"a": "a2", "b": "b2"}) self.index.on_labels_update("l3", {"a": "a3", "c": "c3"}) self.index.on_expression_update("not_a", parse_selector('a!="a"')) self.index.on_expression_update("not_d", parse_selector('d!="d"')) self.assert_add("not_d", "l0") self.assert_add("not_d", "l1") self.assert_add("not_d", "l2") self.assert_add("not_d", "l3") self.assert_add("not_a", "l0") self.assert_add("not_a", "l2") self.assert_add("not_a", "l3") self.assert_no_updates() self.index.on_labels_update("l2", {"a": "a", "b": "b2"}) self.assert_remove("not_a", "l2") self.assert_no_updates() self.index.on_labels_update("l1", {"a": "a", "d": "d1"}) self.index.on_labels_update("l2", {"a": "a", "d": "d"}) self.assert_remove("not_d", "l2") self.assert_no_updates() self.index.on_expression_update("not_a", None) self.index.on_expression_update("not_d", None) self.assert_remove("not_a", "l0") self.assert_remove("not_a", "l3") self.assert_remove("not_d", "l1") self.assert_remove("not_d", "l0") self.assert_remove("not_d", "l3") self.index.on_labels_update("l0", None) self.index.on_labels_update("l1", None) self.index.on_labels_update("l2", None) self.index.on_labels_update("l3", None) self.assert_indexes_empty()
def test_set_indexing(self): self.index.on_labels_update("l1", {"a": "a", "b": "b1"}) self.index.on_labels_update("l2", {"a": "a", "b": "b2"}) self.index.on_labels_update("l3", {"a": "a2", "b": "b3"}) # Add an expression with existing labels. self.index.on_expression_update("e1", parse_selector("a in {'a', 'z'}")) self.assert_add("e1", "l1") self.assert_add("e1", "l2") # And one that uses a real set. self.index.on_expression_update("e2", parse_selector("b in {'b1', 'b2'}")) self.assert_add("e2", "l1") self.assert_add("e2", "l2") self.assert_no_updates() # Then update them. self.index.on_expression_update("e2", parse_selector("b in {'b2', 'b3'}")) self.assert_add("e2", "l3") self.assert_remove("e2", "l1") self.assert_no_updates() self.index.on_expression_update("e1", parse_selector("a in {'a2'}")) self.assert_add("e1", "l3") self.assert_remove("e1", "l1") self.assert_remove("e1", "l2") self.assert_no_updates() # Then update the labels: self.index.on_labels_update("l1", {"a": "a2", "b": "b1"}) self.index.on_labels_update("l2", {"b": "b2"}) self.assert_add("e1", "l1")
def test_set_indexing(self): self.index.on_labels_update("l1", {"a": "a", "b": "b1"}) self.index.on_labels_update("l2", {"a": "a", "b": "b2"}) self.index.on_labels_update("l3", {"a": "a2", "b": "b3"}) # Add an expression with existing labels. self.index.on_expression_update("e1", parse_selector("a in {'a', 'z'}")) self.assert_add("e1", "l1") self.assert_add("e1", "l2") # And one that uses a real set. self.index.on_expression_update("e2", parse_selector("b in {'b1', 'b2'}")) self.assert_add("e2", "l1") self.assert_add("e2", "l2") self.assert_no_updates() # Then update them. self.index.on_expression_update("e2", parse_selector("b in {'b2', 'b3'}")) self.assert_add("e2", "l3") self.assert_remove("e2", "l1") self.assert_no_updates() self.index.on_expression_update("e1", parse_selector("a in {'a2'}")) self.assert_add("e1", "l3") self.assert_remove("e1", "l1") self.assert_remove("e1", "l2") self.assert_no_updates() # Then update the labels: self.index.on_labels_update("l1", {"a": "a2", "b": "b1"}) self.index.on_labels_update("l2", {"b": "b2"}) self.assert_add("e1", "l1")
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_selector(self): self.assert_rule_valid( {"src_selector": "foo == 'bar'"}, {"src_selector": parse_selector("foo == 'bar'")}) self.assert_rule_valid( {"dst_selector": "foo == 'bar'"}, {"dst_selector": parse_selector("foo == 'bar'")}) self.assert_rule_issue({"src_selector": "+"}, "Invalid !?src_selector") self.assert_rule_issue({"dst_selector": "+"}, "Invalid !?dst_selector")
def test_non_equality_stale_match(self): self.index.on_labels_update("l1", {"a": "a", "b": "b1"}) self.index.on_labels_update("l2", {"a": "a", "b": "b2"}) self.index.on_labels_update("l3", {"a": "a", "b": "b3"}) self.index.on_expression_update("e1", parse_selector('b == "b1" && ' 'a == "a"')) self.assert_add("e1", "l1") self.assert_no_updates() self.index.on_expression_update("e1", parse_selector('b == "b2" && ' 'a == "a"')) self.assert_remove("e1", "l1") self.assert_add("e1", "l2") self.assert_no_updates()
def check_no_match(selector, labels): expr = parse_selector(selector) assert_false(expr.evaluate(labels), "%r unexpectedly matched %s" % (selector, labels)) if selector.strip(): # Check that wrapping the selector in a negation reverses its effect. negated_expr = parse_selector("!(%s)" % selector) assert_true(negated_expr.evaluate(labels), "Negated version of %r unexpectedly matched %s" % (selector, labels)) assert_general_expression_properties(expr)
def check_ids_equal(selector_1, selector_2): e = parse_selector(selector_1) uid_1 = e.unique_id e = parse_selector(selector_2) uid_2 = e.unique_id assert_equal(uid_1, uid_2) assert_true(re.match(r'^[a-zA-Z0-9_-]{38}$', uid_1)) assert_not_in(uid_1, seen_ids, msg="Selector %r unexpectedly had same unique ID as %r" % (selector_1, seen_ids.get(uid_1))) seen_ids[uid_1] = selector_1
def check_no_match(selector, labels): expr = parse_selector(selector) assert_false(expr.evaluate(labels), "%r unexpectedly matched %s" % (selector, labels)) if selector.strip(): # Check that wrapping the selector in a negation reverses its effect. negated_expr = parse_selector("!(%s)" % selector) assert_true( negated_expr.evaluate(labels), "Negated version of %r unexpectedly matched %s" % (selector, labels)) assert_general_expression_properties(expr)
def check_ids_equal(selector_1, selector_2): e = parse_selector(selector_1) uid_1 = e.unique_id e = parse_selector(selector_2) uid_2 = e.unique_id assert_equal(uid_1, uid_2) assert_true(re.match(r'^[a-zA-Z0-9_-]{38}$', uid_1)) assert_not_in(uid_1, seen_ids, msg="Selector %r unexpectedly had same unique ID as %r" % (selector_1, seen_ids.get(uid_1))) seen_ids[uid_1] = selector_1
def assert_general_expression_properties(expr): assert_equal(expr, expr, "%r wasn't equal to self" % expr) expr2 = parse_selector(str(expr)) expr2a = parse_selector(str(expr)) assert_true(expr2 is expr2a) expr3 = parse_selector(str(expr) + " ") assert_true(expr3 is not expr, "Expected to defeat cache") assert_equal(expr3, expr, "Selector parsed from equivalent input should " "be equal") assert_false(expr3 != expr, "!= operator should be inverse of ==") assert_equal(hash(expr3), hash(expr), "Hashes of equal objects should be equal") assert_equal(expr.unique_id, expr2.unique_id, "Unique ids should be equal") assert_equal(expr.unique_id, expr3.unique_id, "Unique ids should be equal")
def test_selector(self): self.assert_rule_valid( {"src_selector": "foo == 'bar'"}, {"src_selector": parse_selector("foo == 'bar'")} ) self.assert_rule_valid( {"dst_selector": "foo == 'bar'"}, {"dst_selector": parse_selector("foo == 'bar'")} ) self.assert_rule_issue({"src_selector": "+"}, "Invalid !?src_selector") self.assert_rule_issue({"dst_selector": "+"}, "Invalid !?dst_selector")
def assert_general_expression_properties(expr): assert_equal(expr, expr, "%r wasn't equal to self" % expr) expr2 = parse_selector(str(expr)) expr2a = parse_selector(str(expr)) assert_true(expr2 is expr2a) expr3 = parse_selector(str(expr) + " ") assert_true(expr3 is not expr, "Expected to defeat cache") assert_equal(expr3, expr, "Selector parsed from equivalent input should " "be equal") assert_false(expr3 != expr, "!= operator should be inverse of ==") assert_equal(hash(expr3), hash(expr), "Hashes of equal objects should be equal") assert_equal(expr.unique_id, expr2.unique_id, "Unique ids should be equal") assert_equal(expr.unique_id, expr3.unique_id, "Unique ids should be equal")
def test_non_equality_stale_match(self): self.index.on_labels_update("l1", {"a": "a", "b": "b1"}) self.index.on_labels_update("l2", {"a": "a", "b": "b2"}) self.index.on_labels_update("l3", {"a": "a", "b": "b3"}) self.index.on_expression_update( "e1", parse_selector('b == "b1" && ' 'a == "a"')) self.assert_add("e1", "l1") self.assert_no_updates() self.index.on_expression_update( "e1", parse_selector('b == "b2" && ' 'a == "a"')) self.assert_remove("e1", "l1") self.assert_add("e1", "l2") self.assert_no_updates()
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 test_endpoint_ip_update_with_selector_match(self): """ Test a selector that relies on both directly-set labels and inherited ones. """ # Send in the messages. this selector should match even though there # are no labels in the endpoint. selector = parse_selector("a == 'a1'") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) self.mgr.on_endpoint_update(EP_ID_1_1, EP_1_1_LABELS, async=True) self.step_mgr() # Should now have a match. self.assert_one_selector_one_ep(selector) # Now update the IPs, should update the index. self.mgr.on_endpoint_update(EP_ID_1_1, EP_1_1_LABELS_NEW_IP, async=True) self.step_mgr() self.assertEqual(self.mgr.tag_membership_index.ip_owners_by_tag, {selector: { "10.0.0.2": ("dummy", EP_ID_1_1), }}) # Undo our messages to check that the index is correctly updated. self.mgr.decref(selector, async=True) self.mgr.on_endpoint_update(EP_ID_1_1, None, async=True) self.step_mgr() self.assert_index_empty()
def test_host_endpoint_expected_ips_indexed(self): """Check host endpoint expected IPs are added to index.""" # Send in the messages. this selector should match even though there # are no labels in the endpoint. selector = parse_selector("all()") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) self.mgr.on_host_ep_update(HOST_EP_ID_1_1, HOST_EP_1_1, async=True) # Let the actor process them. self.step_mgr() # Check the indexes now contain the IP. self.assertEqual(self.mgr.endpoint_data_by_ep_id, { HOST_EP_ID_1_1: HOST_EP_DATA_1_1, }) self.assertEqual(self.mgr.tag_membership_index.ip_owners_by_tag, { selector: { "10.0.0.1": ("dummy", HOST_EP_ID_1_1), } }) # Undo our messages to check that the index is correctly updated. self.mgr.decref(selector, async=True) self.mgr.on_host_ep_update(HOST_EP_ID_1_1, None, async=True) self.step_mgr() self.assert_index_empty()
def test_non_trivial_selector_parent_match(self): """ Test a selector that relies on both directly-set labels and inherited ones. """ # Send in the messages. this selector should match even though there # are no labels in the endpoint. selector = parse_selector("a == 'a1' && p == 'p1'") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) self.mgr.on_endpoint_update(EP_ID_1_1, EP_1_1_LABELS, async=True) # Let the actor process them. self.step_mgr() # Should be no match yet. self.assertEqual(self.mgr.tag_membership_index.ip_owners_by_tag, {}) # Now fire in a parent label. self.mgr.on_prof_labels_set("prof1", {"p": "p1"}, async=True) self.step_mgr() # Should now have a match. self.assert_one_selector_one_ep(selector) # Undo our messages to check that the index is correctly updated. self.mgr.on_prof_labels_set("prof1", None, async=True) self.step_mgr() self.assertEqual(self.mgr.tag_membership_index.ip_owners_by_tag, {}) self.mgr.decref(selector, async=True) self.mgr.on_endpoint_update(EP_ID_1_1, None, async=True) self.step_mgr() self.assert_index_empty()
def test_label_regex(self, label): """ Test that the label validation logic matches the selector parsing logic in what it allows. """ # Whitespace is ignored in selectors. assume(not re.search(r'\s', label)) try: common.validate_labels("foo", {label: "foo"}) except ValidationFailed: # Validation failed, should fail to parse as a selector too. _log.exception("Validation failed for label %r", label) assert_raises(BadSelector, parse_selector, "%s == 'a'" % label) else: # Validation passed, should be allowed in expression too. parse_selector("%s == 'a'" % label)
def test_host_endpoint_expected_ips_indexed(self): """Check host endpoint expected IPs are added to index.""" # Send in the messages. this selector should match even though there # are no labels in the endpoint. selector = parse_selector("all()") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) self.mgr.on_host_ep_update(HOST_EP_ID_1_1, HOST_EP_1_1, async=True) # Let the actor process them. self.step_mgr() # Check the indexes now contain the IP. self.assertEqual(self.mgr.endpoint_data_by_ep_id, { HOST_EP_ID_1_1: HOST_EP_DATA_1_1, }) self.assertEqual(self.mgr.tag_membership_index.ip_owners_by_tag, {selector: { "10.0.0.1": ("dummy", HOST_EP_ID_1_1), }}) # Undo our messages to check that the index is correctly updated. self.mgr.decref(selector, async=True) self.mgr.on_host_ep_update(HOST_EP_ID_1_1, None, async=True) self.step_mgr() self.assert_index_empty()
def test_non_trivial_selector_parent_match(self): """ Test a selector that relies on both directly-set labels and inherited ones. """ # Send in the messages. this selector should match even though there # are no labels in the endpoint. selector = parse_selector("a == 'a1' && p == 'p1'") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) self.mgr.on_endpoint_update(EP_ID_1_1, EP_1_1_LABELS, async=True) # Let the actor process them. self.step_mgr() # Should be no match yet. self.assertEqual(self.mgr.tag_membership_index.ip_owners_by_tag, {}) # Now fire in a parent label. self.mgr.on_prof_labels_set("prof1", {"p": "p1"}, async=True) self.step_mgr() # Should now have a match. self.assert_one_selector_one_ep(selector) # Undo our messages to check that the index is correctly updated. self.mgr.on_prof_labels_set("prof1", None, async=True) self.step_mgr() self.assertEqual(self.mgr.tag_membership_index.ip_owners_by_tag, {}) self.mgr.decref(selector, async=True) self.mgr.on_endpoint_update(EP_ID_1_1, None, async=True) self.step_mgr() self.assert_index_empty()
def test_endpoint_ip_update_with_selector_match(self): """ Test a selector that relies on both directly-set labels and inherited ones. """ # Send in the messages. this selector should match even though there # are no labels in the endpoint. selector = parse_selector("a == 'a1'") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) self.mgr.on_endpoint_update(EP_ID_1_1, EP_1_1_LABELS, async=True) self.step_mgr() # Should now have a match. self.assert_one_selector_one_ep(selector) # Now update the IPs, should update the index. self.mgr.on_endpoint_update(EP_ID_1_1, EP_1_1_LABELS_NEW_IP, async=True) self.step_mgr() self.assertEqual(self.mgr.tag_membership_index.ip_owners_by_tag, { selector: { "10.0.0.2": ("dummy", EP_ID_1_1), } }) # Undo our messages to check that the index is correctly updated. self.mgr.decref(selector, async=True) self.mgr.on_endpoint_update(EP_ID_1_1, None, async=True) self.step_mgr() self.assert_index_empty()
def test_label_regex(self, label): """ Test that the label validation logic matches the selector parsing logic in what it allows. """ # Whitespace is ignored in selectors. assume(not re.search(r'\s', label)) try: common.validate_labels("foo", {label: "foo"}) except ValidationFailed: # Validation failed, should fail to parse as a selector too. _log.exception("Validation failed for label %r", label) assert_raises(BadSelector, parse_selector, "%s == 'a'" % label) else: # Validation passed, should be allowed in expression too. parse_selector("%s == 'a'" % label)
def test_inheritance_index_mainline(self): ii = LabelInheritanceIndex(self.index) ii.on_item_update("item_1", {}, []) ii.on_item_update("item_2", {"a": "a1"}, []) ii.on_item_update("item_3", {}, ["parent_1"]) ii.on_item_update("item_4", {"a": "a1"}, ["parent_2"]) self.index.on_expression_update("e1", parse_selector("a == 'a1'")) self.index.on_expression_update("e2", parse_selector("a != 'a1'")) self.index.on_expression_update("e3", parse_selector("a == 'p1'")) self.assert_add("e1", "item_2") self.assert_add("e1", "item_4") self.assert_add("e2", "item_1") self.assert_add("e2", "item_3") self.assert_no_updates() # Now make a parent change, should cause a match. ii.on_parent_labels_update("parent_1", {"a": "p1"}) self.assert_add("e3", "item_3") # Then, remove the parent label, should remove the match. ii.on_parent_labels_update("parent_1", {}) self.assert_remove("e3", "item_3") # Now make a parent change, should cause a match. ii.on_parent_labels_update("parent_1", {"a": "p1"}) self.assert_add("e3", "item_3") # Then, remove the parent labels entirely, should remove the match. ii.on_parent_labels_update("parent_1", None) self.assert_remove("e3", "item_3") # Now make a parent change for parent_2; the per-item labels should # override. ii.on_parent_labels_update("parent_2", {"a": "p1"}) ii.on_parent_labels_update("parent_2", None) self.assert_no_updates() # Now make a parent change, should cause a match. ii.on_parent_labels_update("parent_1", {"a": "p1"}) self.assert_add("e3", "item_3") # But then remove the item. ii.on_item_update("item_3", None, None) self.assert_remove("e3", "item_3") self.assert_remove("e2", "item_3")
def test_inheritance_index_mainline(self): ii = LabelInheritanceIndex(self.index) ii.on_item_update("item_1", {}, []) ii.on_item_update("item_2", {"a": "a1"}, []) ii.on_item_update("item_3", {}, ["parent_1"]) ii.on_item_update("item_4", {"a": "a1"}, ["parent_2"]) self.index.on_expression_update("e1", parse_selector("a == 'a1'")) self.index.on_expression_update("e2", parse_selector("a != 'a1'")) self.index.on_expression_update("e3", parse_selector("a == 'p1'")) self.assert_add("e1", "item_2") self.assert_add("e1", "item_4") self.assert_add("e2", "item_1") self.assert_add("e2", "item_3") self.assert_no_updates() # Now make a parent change, should cause a match. ii.on_parent_labels_update("parent_1", {"a": "p1"}) self.assert_add("e3", "item_3") # Then, remove the parent label, should remove the match. ii.on_parent_labels_update("parent_1", {}) self.assert_remove("e3", "item_3") # Now make a parent change, should cause a match. ii.on_parent_labels_update("parent_1", {"a": "p1"}) self.assert_add("e3", "item_3") # Then, remove the parent labels entirely, should remove the match. ii.on_parent_labels_update("parent_1", None) self.assert_remove("e3", "item_3") # Now make a parent change for parent_2; the per-item labels should # override. ii.on_parent_labels_update("parent_2", {"a": "p1"}) ii.on_parent_labels_update("parent_2", None) self.assert_no_updates() # Now make a parent change, should cause a match. ii.on_parent_labels_update("parent_1", {"a": "p1"}) self.assert_add("e3", "item_3") # But then remove the item. ii.on_item_update("item_3", None, None) self.assert_remove("e3", "item_3") self.assert_remove("e2", "item_3")
def validate_policy(policy_id, policy): """ Validates and normalises the given policy dictionary. As side effects to the input dict: * Fields that are set to None in rules are removed completely. * Selectors are replaced with SelectorExpression objects. Parsing now ensures that the selectors are valid and ensures that equal selectors compare and hash equally. For example: "a == 'b'" and "a=='b'" are different strings that parse to the same selector. Once this routine has returned successfully, we know that all required fields are present and have valid values. :param TieredPolicyId policy_id: The ID of the profile, which is also validated. :param policy: The profile dict. For example, {"inbound_rules": [...], "outbound_rules": [...], "selector": "foo == 'bar'", "order": 123} :raises ValidationFailed """ # The code below relies on the top-level object to be a dict. Check # that first. if not isinstance(policy, dict): raise ValidationFailed("Expected policy '%s' to be a dict, not %r." % (policy_id, policy)) issues = [] for part in policy_id.tier, policy_id.policy_id: if not VALID_ID_RE.match(part): issues.append("Invalid profile ID '%r'." % policy_id) _validate_rules(policy, issues) if "selector" in policy: try: selector = parse_selector(policy["selector"]) except BadSelector: issues.append("Failed to parse selector %s" % policy["selector"]) else: policy["selector"] = selector else: issues.append("Profile missing required selector field") if "order" in policy: if not isinstance(policy["order"], numbers.Number): issues.append("Order should be a number, not %s" % policy["order"]) else: issues.append("Profile missing required order field") if issues: raise ValidationFailed(" ".join(issues))
def test_repr(): sel = "a== 'a' && c != 'd' || e in {'f'} || d not in {'g' }" e = parse_selector(sel) assert_equal(repr(e), "SelectorExpression<((a == 'a' && c != 'd') || " "e in {'f'} || d not in {'g'})>") assert_equal(repr(e.expr_op), "OrNode<((a == 'a' && c != 'd') || " "e in {'f'} || d not in {'g'})>")
def test_plausible_garbage(l): try: sel = "".join(l) expr = parse_selector(sel) except BadSelector: pass else: assert isinstance(expr, SelectorExpression) expr.evaluate({})
def test_repr(): sel = "a== 'a' && c != 'd' || e in {'f'} || d not in {'g' }" e = parse_selector(sel) assert_equal( repr(e), "SelectorExpression<((a == 'a' && c != 'd') || " "e in {'f'} || d not in {'g'})>") assert_equal( repr(e.expr_op), "OrNode<((a == 'a' && c != 'd') || " "e in {'f'} || d not in {'g'})>")
def test_plausible_garbage(l): try: sel = "".join(l) expr = parse_selector(sel) except BadSelector: pass else: assert isinstance(expr, SelectorExpression) expr.evaluate({})
def test_parse_gives_correct_exc_or_value(s): try: expr = parse_selector(s) except BadSelector: pass except KeyboardInterrupt: print "Interrupted while processing %r" % s raise else: assert isinstance(expr, SelectorExpression) expr.evaluate({})
def test_parse_gives_correct_exc_or_value(s): try: expr = parse_selector(s) except BadSelector: pass except KeyboardInterrupt: print "Interrupted while processing %r" % s raise else: assert isinstance(expr, SelectorExpression) expr.evaluate({})
def test_host_endpoint_no_ips(self): """Check host endpoint expected IPs are added to index.""" # Send in the messages. this selector should match even though there # are no labels in the endpoint. selector = parse_selector("all()") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) self.mgr.on_host_ep_update(HOST_EP_ID_1_1, HOST_EP_1_1_NO_IPS, async=True) # Let the actor process them. self.step_mgr() # Index should be empty because there's no contribution. self.assert_index_empty()
def test_host_endpoint_no_ips(self): """Check host endpoint expected IPs are added to index.""" # Send in the messages. this selector should match even though there # are no labels in the endpoint. selector = parse_selector("all()") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) self.mgr.on_host_ep_update(HOST_EP_ID_1_1, HOST_EP_1_1_NO_IPS, async=True) # Let the actor process them. self.step_mgr() # Index should be empty because there's no contribution. self.assert_index_empty()
def test_endpoint_then_selector(self): # Send in the messages. self.mgr.on_endpoint_update(EP_ID_1_1, EP_1_1, async=True) selector = parse_selector("all()") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) # Let the actor process them. self.step_mgr() self.assert_one_selector_one_ep(selector) # Undo our messages to check that the index is correctly updated. self.mgr.on_endpoint_update(EP_ID_1_1, None, async=True) self.mgr.decref(selector, async=True) self.step_mgr() self.assert_index_empty()
def test_endpoint_then_selector(self): # Send in the messages. self.mgr.on_endpoint_update(EP_ID_1_1, EP_1_1, async=True) selector = parse_selector("all()") self.mgr.get_and_incref(selector, callback=self.on_ref_acquired, async=True) # Let the actor process them. self.step_mgr() self.assert_one_selector_one_ep(selector) # Undo our messages to check that the index is correctly updated. self.mgr.on_endpoint_update(EP_ID_1_1, None, async=True) self.mgr.decref(selector, async=True) self.step_mgr() self.assert_index_empty()
def check_match(selector, labels): expr = parse_selector(selector) assert_true(expr.evaluate(labels), "%r did not match %s" % (selector, labels)) assert_general_expression_properties(expr)
def check_no_match(selector, labels): expr = parse_selector(selector) assert_false(expr.evaluate(labels), "%r unexpectedly matched %s" % (selector, labels)) assert_general_expression_properties(expr)
def check_match(selector, labels): expr = parse_selector(selector) assert_true(expr.evaluate(labels), "%r did not match %s" % (selector, labels)) assert_general_expression_properties(expr)
'--append felix-INPUT --match conntrack --ctstate INVALID --jump DROP', '--append felix-INPUT --match conntrack --ctstate RELATED,ESTABLISHED --jump ACCEPT', '--append felix-INPUT --goto felix-FROM-HOST-IF ! --in-interface tap+', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 130', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 131', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 132', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 133', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 135', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 136', '--append felix-INPUT --protocol udp --sport 546 --dport 547 --jump ACCEPT', '--append felix-INPUT --protocol udp --dport 53 --jump ACCEPT', '--append felix-INPUT --jump felix-FROM-ENDPOINT', ] } SELECTOR_A_EQ_B = parse_selector("a == 'b'") RULES_TESTS = [ { "ip_version": 4, "tag_to_ipset": {}, "sel_to_ipset": { SELECTOR_A_EQ_B: "a-eq-b" }, "profile": { "id": "prof1", "inbound_rules": [{ "src_selector": SELECTOR_A_EQ_B, "log_prefix": "foo", "action": "next-tier"
def check_no_match(selector, labels): expr = parse_selector(selector) assert_false(expr.evaluate(labels), "%r unexpectedly matched %s" % (selector, labels)) assert_general_expression_properties(expr)
def check_prereqs(selector, expected): expr = parse_selector(selector) assert_equal(expr.required_kvs, set(expected)) assert_general_expression_properties(expr)
def _validate_rule_match_criteria(rule, issues, neg_pfx): """Validates and canonicalises a rule's match criteria. Each call validates either the negated or non-negated match criteria. I.e. the ones with "!" prefixed or not. :param rule: The dict for the individual rule. If this contains selectors, they are replaced with parsed versions. Protocols and IPs are also normalised. :param list[str] issues: List of issues found. This method appends any issues it finds to the list. :param str neg_pfx: The negation prefix, "" for positive matches or "!" for negative. """ # Absolutely all fields are optional, but some have valid and # invalid values. assert neg_pfx in ("", "!") # Explicitly get the non-negated protocol; even negated matches on port # or ICMP values require the protocol to be specified. protocol = rule.get("protocol") # Check the (possibly negated) profile. protocol_key = neg_pfx + 'protocol' maybe_neg_proto = rule.get(protocol_key) if maybe_neg_proto is not None and maybe_neg_proto not in KERNEL_PROTOCOLS: issues.append("Invalid %s %s in rule %s" % (protocol_key, maybe_neg_proto, rule)) elif maybe_neg_proto is not None: maybe_neg_proto = intern(str(maybe_neg_proto)) rule[protocol_key] = str(maybe_neg_proto) ip_version = rule.get('ip_version') if ip_version == 4 and protocol == "icmpv6": issues.append("Using icmpv6 with IPv4 in rule %s." % rule) if ip_version == 6 and protocol == "icmp": issues.append("Using icmp with IPv6 in rule %s." % rule) for tag_type in (neg_pfx + 'src_tag', neg_pfx + 'dst_tag'): tag = rule.get(tag_type) if tag is None: continue if not VALID_ID_RE.match(tag): issues.append("Invalid %s: %r." % (tag_type, tag)) # For selectors, we replace the value with the parsed selector. # This avoids having to re-parse it later and it ensures that # equivalent selectors compare equal. for sel_type in (neg_pfx + 'src_selector', neg_pfx + 'dst_selector'): sel_str = rule.get(sel_type) if sel_str is None: # sel_type wasn't present. continue try: sel = parse_selector(sel_str) except BadSelector: issues.append("Invalid %s: %r" % (sel_type, sel_str)) else: rule[sel_type] = sel if "log_prefix" in rule: log_pfx = rule["log_prefix"] if not isinstance(log_pfx, basestring): issues.append("Log prefix should be a string") else: # Sanitize the log prefix. iptables length limit is 29 chars but # we add ": " to the end in the iptables generator. rule["log_prefix"] = INVALID_LOG_KEY_CHARS.sub("_", log_pfx)[:27] for key in (neg_pfx + "src_net", neg_pfx + "dst_net"): network = rule.get(key) if (network is not None and not validate_cidr(rule[key], ip_version)): issues.append("Invalid CIDR (version %s) in rule %s." % (ip_version, rule)) elif network is not None: rule[key] = canonicalise_cidr(network, ip_version) for key in (neg_pfx + "src_ports", neg_pfx + "dst_ports"): ports = rule.get(key) if (ports is not None and not isinstance(ports, list)): issues.append("Expected ports to be a list in rule %s." % rule) continue if ports is not None: if protocol not in KERNEL_PORT_PROTOCOLS: issues.append("%s is not allowed for protocol %s in " "rule %s" % (key, protocol, rule)) for port in ports: error = validate_rule_port(port) if error: issues.append("Invalid port %s (%s) in rule %s." % (port, error, rule)) action = rule.get(neg_pfx + 'action') if (action is not None and action not in KNOWN_ACTIONS): issues.append("Invalid action in rule %s." % rule) icmp_type = rule.get(neg_pfx + 'icmp_type') if icmp_type is not None: if not isinstance(icmp_type, int): issues.append("ICMP type is not an integer in rule %s." % rule) elif not 0 <= icmp_type <= 255: issues.append("ICMP type is out of range in rule %s." % rule) icmp_code = rule.get(neg_pfx + "icmp_code") if icmp_code is not None: if not isinstance(icmp_code, int): issues.append("ICMP code is not an integer in rule %s." % rule) elif not 0 <= icmp_code <= 255: issues.append("ICMP code is out of range.") if icmp_type is None: # ICMP code without ICMP type not supported by iptables; # firewall against that. issues.append("ICMP code specified without ICMP type.")
def _validate_rule_match_criteria(rule, issues, neg_pfx): """Validates and canonicalises a rule's match criteria. Each call validates either the negated or non-negated match criteria. I.e. the ones with "!" prefixed or not. :param rule: The dict for the individual rule. If this contains selectors, they are replaced with parsed versions. Protocols and IPs are also normalised. :param list[str] issues: List of issues found. This method appends any issues it finds to the list. :param str neg_pfx: The negation prefix, "" for positive matches or "!" for negative. """ # Absolutely all fields are optional, but some have valid and # invalid values. assert neg_pfx in ("", "!") # Explicitly get the non-negated protocol; even negated matches on port # or ICMP values require the protocol to be specified. protocol = rule.get("protocol") # Check the (possibly negated) profile. protocol_key = neg_pfx + 'protocol' maybe_neg_proto = rule.get(protocol_key) if maybe_neg_proto is not None and maybe_neg_proto not in KERNEL_PROTOCOLS: issues.append("Invalid %s %s in rule %s" % (protocol_key, maybe_neg_proto, rule)) elif maybe_neg_proto is not None: maybe_neg_proto = intern(str(maybe_neg_proto)) rule[protocol_key] = str(maybe_neg_proto) ip_version = rule.get('ip_version') if ip_version == 4 and protocol == "icmpv6": issues.append("Using icmpv6 with IPv4 in rule %s." % rule) if ip_version == 6 and protocol == "icmp": issues.append("Using icmp with IPv6 in rule %s." % rule) for tag_type in (neg_pfx + 'src_tag', neg_pfx + 'dst_tag'): tag = rule.get(tag_type) if tag is None: continue if not VALID_ID_RE.match(tag): issues.append("Invalid %s: %r." % (tag_type, tag)) # For selectors, we replace the value with the parsed selector. # This avoids having to re-parse it later and it ensures that # equivalent selectors compare equal. for sel_type in (neg_pfx + 'src_selector', neg_pfx + 'dst_selector'): sel_str = rule.get(sel_type) if sel_str is None: # sel_type wasn't present. continue try: sel = parse_selector(sel_str) except BadSelector: issues.append("Invalid %s: %r" % (sel_type, sel_str)) else: rule[sel_type] = sel for key in (neg_pfx + "src_net", neg_pfx + "dst_net"): network = rule.get(key) if (network is not None and not validate_cidr(rule[key], ip_version)): issues.append("Invalid CIDR (version %s) in rule %s." % (ip_version, rule)) elif network is not None: rule[key] = canonicalise_cidr(network, ip_version) for key in (neg_pfx + "src_ports", neg_pfx + "dst_ports"): ports = rule.get(key) if (ports is not None and not isinstance(ports, list)): issues.append("Expected ports to be a list in rule %s." % rule) continue if ports is not None: if protocol not in KERNEL_PORT_PROTOCOLS: issues.append("%s is not allowed for protocol %s in " "rule %s" % (key, protocol, rule)) for port in ports: error = validate_rule_port(port) if error: issues.append("Invalid port %s (%s) in rule %s." % (port, error, rule)) action = rule.get(neg_pfx + 'action') if (action is not None and action not in KNOWN_ACTIONS): issues.append("Invalid action in rule %s." % rule) icmp_type = rule.get(neg_pfx + 'icmp_type') if icmp_type is not None: if not isinstance(icmp_type, int): issues.append("ICMP type is not an integer in rule %s." % rule) elif not 0 <= icmp_type <= 255: issues.append("ICMP type is out of range in rule %s." % rule) icmp_code = rule.get(neg_pfx + "icmp_code") if icmp_code is not None: if not isinstance(icmp_code, int): issues.append("ICMP code is not an integer in rule %s." % rule) elif not 0 <= icmp_code <= 255: issues.append("ICMP code is out of range.") if icmp_type is None: # ICMP code without ICMP type not supported by iptables; # firewall against that. issues.append("ICMP code specified without ICMP type.")
def check_prereqs(selector, expected): expr = parse_selector(selector) assert_equal(expr.required_kvs, set(expected)) assert_general_expression_properties(expr)
} 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 setUp(self): super(TestEtcdAPI, self).setUp() self.m_config = Mock(spec=Config) self.m_config.ETCD_ADDRS = [ETCD_ADDRESS] self.m_config.ETCD_SCHEME = "http" self.m_config.ETCD_KEY_FILE = None self.m_config.ETCD_CERT_FILE = None
'--append felix-p-prof1-i --match set ' '--match-set src-tag-name src ' '--jump MARK --set-mark 0x1000000/0x1000000', '--append felix-p-prof1-i --match mark ' '--mark 0x1000000/0x1000000 --jump RETURN', ], 'felix-p-prof1-o': [ '--append felix-p-prof1-o --match set ' '--match-set dst-tag-name dst ' '--jump MARK --set-mark 0x1000000/0x1000000', '--append felix-p-prof1-o --match mark ' '--mark 0x1000000/0x1000000 --jump RETURN', ] } SELECTOR_1 = parse_selector("a == 'a1'") RULES_2 = { "id": "prof1", "inbound_rules": [ # Use negated matches to ensure we extract dependencies for negated # matches. {"!src_tag": "src-tag-added", "!src_selector": SELECTOR_1} ], "outbound_rules": [ {"dst_tag": "dst-tag"} ] } RULES_2_CHAINS = { 'felix-p-prof1-i': [
} 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 setUp(self): super(TestEtcdAPI, self).setUp() self.m_config = Mock(spec=Config) self.m_config.ETCD_ADDRS = [ETCD_ADDRESS] self.m_config.ETCD_SCHEME = "http" self.m_config.ETCD_KEY_FILE = None self.m_config.ETCD_CERT_FILE = None self.m_config.ETCD_CA_FILE = None
def _validate_rules(rules_dict, issues): """ Validates and normalises the given rules dictionary. As side effects to the input dict: * Fields that are set to None in rules are removed completely. * Selectors are replaced with SelectorExpression objects. Parsing now ensures that the selectors are valid and ensures that equal selectors compare and hash equally. For example: "a == 'b'" and "a=='b'" are different strings that parse to the same selector. Once this routine has returned successfully, we know that all required fields are present and have valid values. :param dict rules_dict: profile dict as read from etcd :param list issues: Updated with any issues discovered. """ for dirn in ("inbound_rules", "outbound_rules"): if dirn not in rules_dict: issues.append("No %s in rules." % dirn) continue if not isinstance(rules_dict[dirn], list): issues.append("Expected rules[%s] to be a list." % dirn) continue for rule in rules_dict[dirn]: if not isinstance(rule, dict): issues.append("Rule should be a dict: %r" % rule) break for key, value in rule.items(): if value is None: del rule[key] # Absolutely all fields are optional, but some have valid and # invalid values. protocol = rule.get('protocol') if protocol is not None and protocol not in KERNEL_PROTOCOLS: issues.append("Invalid protocol %s in rule %s" % (protocol, rule)) elif protocol is not None: protocol = intern(str(protocol)) rule['protocol'] = str(protocol) ip_version = rule.get('ip_version') if ip_version is not None and ip_version not in (4, 6): # Bad IP version prevents further validation issues.append("Invalid ip_version in rule %s." % rule) continue if ip_version == 4 and protocol == "icmpv6": issues.append("Using icmpv6 with IPv4 in rule %s." % rule) if ip_version == 6 and protocol == "icmp": issues.append("Using icmp with IPv6 in rule %s." % rule) for tag_type in ('src_tag', 'dst_tag'): tag = rule.get(tag_type) if tag is None: continue if not VALID_ID_RE.match(tag): issues.append("Invalid %s: %r." % (tag_type, tag)) # For selectors, we replace the value with the parsed selector. # This avoids having to re-parse it later and it ensures that # equivalent selectors compare equal. for sel_type in ('src_selector', 'dst_selector'): sel_str = rule.get(sel_type) if sel_str is None: # sel_type wasn't present. continue try: sel = parse_selector(sel_str) except BadSelector: issues.append("Invalid %s: %r" % (sel_type, sel_str)) else: rule[sel_type] = sel for key in ("src_net", "dst_net"): network = rule.get(key) if (network is not None and not validate_cidr(rule[key], ip_version)): issues.append("Invalid CIDR (version %s) in rule %s." % (ip_version, rule)) elif network is not None: rule[key] = canonicalise_cidr(network, ip_version) for key in ("src_ports", "dst_ports"): ports = rule.get(key) if (ports is not None and not isinstance(ports, list)): issues.append("Expected ports to be a list in rule %s." % rule) continue if ports is not None: if protocol not in KERNEL_PORT_PROTOCOLS: issues.append("%s is not allowed for protocol %s in " "rule %s" % (key, protocol, rule)) for port in ports: error = validate_rule_port(port) if error: issues.append("Invalid port %s (%s) in rule %s." % (port, error, rule)) action = rule.get('action') if (action is not None and action not in KNOWN_ACTIONS): issues.append("Invalid action in rule %s." % rule) icmp_type = rule.get('icmp_type') if icmp_type is not None: if not isinstance(icmp_type, int): issues.append("ICMP type is not an integer in rule %s." % rule) elif not 0 <= icmp_type <= 255: issues.append("ICMP type is out of range in rule %s." % rule) icmp_code = rule.get("icmp_code") if icmp_code is not None: if not isinstance(icmp_code, int): issues.append("ICMP code is not an integer in rule %s." % rule) elif not 0 <= icmp_code <= 255: issues.append("ICMP code is out of range.") if icmp_type is None: # ICMP code without ICMP type not supported by iptables; # firewall against that. issues.append("ICMP code specified without ICMP type.") unknown_keys = set(rule.keys()) - KNOWN_RULE_KEYS if unknown_keys: issues.append("Rule contains unknown keys: %s." % unknown_keys)
'--append felix-INPUT --match conntrack --ctstate INVALID --jump DROP', '--append felix-INPUT --match conntrack --ctstate RELATED,ESTABLISHED --jump ACCEPT', '--append felix-INPUT --goto felix-FROM-HOST-IF ! --in-interface tap+', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 130', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 131', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 132', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 133', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 135', '--append felix-INPUT --jump ACCEPT --protocol ipv6-icmp --icmpv6-type 136', '--append felix-INPUT --protocol udp --sport 546 --dport 547 --jump ACCEPT', '--append felix-INPUT --protocol udp --dport 53 --jump ACCEPT', '--append felix-INPUT --jump felix-FROM-ENDPOINT', ] } SELECTOR_A_EQ_B = parse_selector("a == 'b'") RULES_TESTS = [ { "ip_version": 4, "tag_to_ipset": {}, "sel_to_ipset": {SELECTOR_A_EQ_B: "a-eq-b"}, "profile": { "id": "prof1", "inbound_rules": [ {"src_selector": SELECTOR_A_EQ_B, "log_prefix": "foo", "action": "next-tier"} ], "outbound_rules": [ {"dst_selector": SELECTOR_A_EQ_B,