def test_flows_fail_from_session(): """Confirm flows-fail assert passes and fails as expected when called from a session.""" startLocation = "node1" headers = HeaderConstraints(srcIps="1.1.1.1") bf = Session(load_questions=False) with patch.object(bf.q, "reachability", create=True) as reachability: # Test success reachability.return_value = MockQuestion() bf.asserts.assert_flows_fail(startLocation, headers) reachability.assert_called_with( pathConstraints=PathConstraints(startLocation=startLocation), headers=headers, actions="success", ) # Test failure mock_df = DataFrame.from_records([{"Flow": "found", "More": "data"}]) reachability.return_value = MockQuestion(MockTableAnswer(mock_df)) with pytest.raises(BatfishAssertException) as excinfo: bf.asserts.assert_flows_fail(startLocation, headers) # Ensure found answer is printed assert mock_df.to_string() in str(excinfo.value) reachability.assert_called_with( pathConstraints=PathConstraints(startLocation=startLocation), headers=headers, actions="success", )
def test_filter_permits_no_session(): """ Confirm filter-permits assert passes and fails as expected when not specifying a session. For reverse compatibility. """ headers = HeaderConstraints(srcIps="1.1.1.1") # Confirm assert works when not specifying a session # for reverse compatibility with patch.object(bfq, "searchFilters", create=True) as mock_search_filters: # Test success mock_search_filters.return_value = MockQuestion() assert_filter_permits("filter", headers) mock_search_filters.assert_called_with(filters="filter", headers=headers, action="deny") # Test failure; also test that startLocation is passed through mock_df = DataFrame.from_records([{"Flow": "found", "More": "data"}]) mock_search_filters.return_value = MockQuestion( MockTableAnswer(mock_df)) with pytest.raises(BatfishAssertException) as excinfo: assert_filter_permits("filter", headers, startLocation="Ethernet1") # Ensure found answer is printed assert mock_df.to_string() in str(excinfo.value) mock_search_filters.assert_called_with(filters="filter", headers=headers, startLocation="Ethernet1", action="deny")
def test_filter_denies_from_session(): """Confirm filter-denies assert passes and fails as expected when called from a session.""" headers = HeaderConstraints(srcIps="1.1.1.1") bf = Session(load_questions=False) with patch.object(bf.q, "searchFilters", create=True) as mock_search_filters: # Test success mock_search_filters.return_value = MockQuestion() bf.asserts.assert_filter_denies("filter", headers) mock_search_filters.assert_called_with(filters="filter", headers=headers, action="permit") # Test failure; also test that startLocation is passed through mock_df = DataFrame.from_records([{"Flow": "found", "More": "data"}]) mock_search_filters.return_value = MockQuestion( MockTableAnswer(mock_df)) with pytest.raises(BatfishAssertException) as excinfo: bf.asserts.assert_filter_denies("filter", headers, startLocation="Ethernet1") # Ensure found answer is printed assert mock_df.to_string() in str(excinfo.value) mock_search_filters.assert_called_with( filters="filter", headers=headers, startLocation="Ethernet1", action="permit", )
def test_customer_link_input_filter_anti_spoofing(bf, customer_list, bgpPeer): """Check that all customer input filters only allowed specified source IP addresses""" bf.asserts.current_assertion = 'Assert all customer input filters only allow specified source IP addresses' missing = [] for customer in customer_list: df = bf.q.resolveInterfaceSpecifier( interfaces=f"@connectedTo({customer['Remote_IP']})", nodes=customer['Node']).answer().frame() # this only works for numbered interfaces and BGP peers if not df.empty: local_intf = df.iloc[0].Interface else: missing.append( f"Bad specifier: @connectedTo({customer['Remote_IP']}, Cannot find local interface for {customer}" ) continue df2 = bf.q.interfaceProperties( interfaces=str(local_intf)).answer().frame() if df2.empty: missing.append( f"No Interface Properties for {local_intf}, Cannot find local interface for {customer}" ) continue input_acl = df2.iloc[0]['Incoming_Filter_Name'] if df2.iloc[0]['Incoming_Filter_Name'] is None: missing.append( f"No ACL applied to {local_intf} connecting to {customer}") continue in_acl = df2.iloc[0]['Incoming_Filter_Name'] filter_spec = f"@in({df2.iloc[0]['Interface']})" srcIps = ",".join(customer.get('Origin_Prefixes', ["0.0.0.0/0"])) headers = HeaderConstraints(srcIps=srcIps) df3 = bf.q.searchFilters(action='permit', headers=headers, filters=filter_spec).answer().frame() if df3.empty: continue else: missing.append( f"ACL {in_acl} on {local_intf} connecting to {customer} does not prevent spoofing\n\n{df3}\n" ) # need to check anti-spoofing here test = (len(missing) == 0) print_fail = "\n".join(missing) pass_message = "All customer input filters prevent spoofing\n" fail_message = f"List of customer connections with filters that do not prevent spoofing\n{print_fail}" record_results(bf, test, pass_message, fail_message)
def get_traces(nodes: List[str], snapshot: str): traces = {} for n1 in nodes: for n2 in nodes: results = bfq.traceroute( startLocation=n1, headers=HeaderConstraints(dstIps=n2)).answer( snapshot=snapshot).frame() for idx, result in results.iterrows(): for trace in result.Traces: if trace.disposition not in traces: traces[trace.disposition] = set() hops_str = json.dumps(convert_list_to_tuple(trace.hops)) traces[trace.disposition].add(hops_str) return traces
def test_vtep_reachability(bf, l2_vnis): """Check that all VTEPs can reach each other.""" bf.asserts.current_assertion = 'Assert all VTEP reachability' # Collect the list of VTEP_IP from vxlanVniProperties and then loop # through to do traceroute vtep_ip_list = set(l2_vnis['Local_VTEP_IP']) vni_dict = defaultdict(dict) for index, row in l2_vnis.iterrows(): node = row['Node'] t_ip = row['Local_VTEP_IP'] vni_dict[node]['Local_IP'] = t_ip for vtep_ip in vtep_ip_list: if vtep_ip != t_ip: try: vni_dict[node]['Remote_IPs'].add(vtep_ip) except KeyError: vni_dict[node]['Remote_IPs'] = {vtep_ip} tr_dict = defaultdict(list) for src_location in vni_dict.keys(): for remote_vtep in vni_dict[src_location]['Remote_IPs']: src_ip = vni_dict[src_location]['Local_IP'] headers = HeaderConstraints(srcIps=src_ip, dstIps=remote_vtep, ipProtocols='udp', dstPorts='4789') tr = bf.q.traceroute(startLocation=src_location + "[@vrf(default)]", headers=headers).answer().frame() for trace in tr.Traces[0]: if trace.disposition != 'ACCEPTED': tr_dict[f"{src_location}:{src_ip}"].append(remote_vtep) test = not len(tr_dict) pass_message = 'Full VTEP to VTEP reachability\n' fail_message = f"Some VTEPs unable to reach others:\n{dict(tr_dict)}" record_results(bf, test, pass_message, fail_message)
def test_filter_denies(): headers = HeaderConstraints(srcIps='1.1.1.1') with patch.object(bfq, 'searchFilters', create=True) as mock_search_filters: # Test success mock_search_filters.return_value = MockQuestion() assert_filter_denies('filter', headers) mock_search_filters.assert_called_with(filters='filter', headers=headers, action='permit') # Test failure; also test that startLocation is passed through mock_df = DataFrame.from_records([{'Flow': 'found', 'More': 'data'}]) mock_search_filters.return_value = MockQuestion( MockTableAnswer(mock_df)) with pytest.raises(BatfishAssertException) as excinfo: assert_filter_denies('filter', headers, startLocation='Ethernet1') # Ensure found answer is printed assert mock_df.to_string() in str(excinfo.value) mock_search_filters.assert_called_with(filters='filter', headers=headers, startLocation='Ethernet1', action='permit')
def test_flows_succeed(): """Confirm flows-succeed assert passes and fails as expected when specifying a session.""" startLocation = "node1" headers = HeaderConstraints(srcIps='1.1.1.1') bf = Session(load_questions=False) with patch.object(bf.q, 'reachability', create=True) as reachability: # Test success reachability.return_value = MockQuestion() assert_flows_succeed(startLocation, headers, session=bf) reachability.assert_called_with( pathConstraints=PathConstraints(startLocation=startLocation), headers=headers, actions='failure') # Test failure mock_df = DataFrame.from_records([{'Flow': 'found', 'More': 'data'}]) reachability.return_value = MockQuestion(MockTableAnswer(mock_df)) with pytest.raises(BatfishAssertException) as excinfo: assert_flows_succeed(startLocation, headers, session=bf) # Ensure found answer is printed assert mock_df.to_string() in str(excinfo.value) reachability.assert_called_with( pathConstraints=PathConstraints(startLocation=startLocation), headers=headers, actions='failure')
@pytest.fixture(scope="module") def session_fail(): s = Session() name = s.init_snapshot(join(_this_dir, "snapshots", "asserts_fail")) yield s s.delete_snapshot(name) @pytest.mark.parametrize( "assert_func, params", [ ( "assert_filter_denies", { "filters": "/101/", "headers": HeaderConstraints(srcIps="12.34.56.78"), "startLocation": "@enter(node[GigabitEthernet1/0])", }, ), ("assert_filter_has_no_unreachable_lines", { "filters": "/101/" }), ( "assert_filter_permits", { "filters": "/101/", "headers": HeaderConstraints(srcIps="1.0.1.0", dstIps="8.8.8.8"), "startLocation": "@enter(node[GigabitEthernet1/0])", }, ),
s.delete_snapshot(name) # Just use a single session and snapshot for all failing assertion tests @pytest.fixture(scope='module') def session_fail(): s = Session() name = s.init_snapshot(join(_this_dir, 'snapshots', 'asserts_fail')) yield s s.delete_snapshot(name) @pytest.mark.parametrize('assert_func, params', [ ('assert_filter_denies', { 'filters': '/101/', 'headers': HeaderConstraints(srcIps='12.34.56.78'), 'startLocation': '@enter(node[GigabitEthernet1/0])', }), ('assert_filter_has_no_unreachable_lines', { 'filters': '/101/', }), ('assert_filter_permits', { 'filters': '/101/', 'headers': HeaderConstraints(srcIps='1.0.1.0', dstIps='8.8.8.8'), 'startLocation': '@enter(node[GigabitEthernet1/0])', }), ('assert_flows_fail', { 'startLocation': '@enter(node[GigabitEthernet1/0])', 'headers': HeaderConstraints(
_this_dir = abspath(dirname(realpath(__file__))) _root_dir = abspath(join(_this_dir, pardir)) sys.path.insert(0, _root_dir) _questions_to_ignore_for_columns = [ "filterTable", # takes a question as input; no fixed schema "testRoutePolicies", # likely to evolve; need work to support its types "viModel" # not a table answer ] _example_snapshot_dir = join(_root_dir, 'jupyter_notebooks/networks/example') # Example values to use for parameters of a given type _values_by_type = { 'filterSpec': '/.*/', 'headerConstraint': HeaderConstraints(dstIps="0.0.0.0/0"), 'interfacesSpec': '/.*/', 'ip': "1.1.1.1", 'ipSpaceSpec': '0.0.0.0/0', 'locationSpec': "/.*/", 'nodeSpec': "/.*/", } # mapping from question category tags to category name and description # the master list of categories being checked and enforced is here: https://github.com/batfish/batfish/blob/master/tests/questions/test_questions.py _question_categories = OrderedDict([ ("configuration", [ "Configuration data questions", "Questions that return the contents of configuration in a structured format." ]), ("hygiene", [