import demistomock as demisto # noqa: F401 import pytest import json from CommonServerPython import DemistoException import ipaddress def util_load_json(path): with open(path) as f: return json.loads(f.read()) def test_get_nsg_rules(mocker): """ Tests that the get_nsg_rules function correctly identifies valid command responses and returns a correctly formatted and ordered list of NSG rules. """ from AzureIdentifyNSGExposureRule import get_nsg_rules command_response = util_load_json("./test_data/command_response.json") mocker.patch.object(demisto, "executeCommand", return_value=command_response) sorted_rules_result, instance_to_use_result = get_nsg_rules("fake-subscription-id", "fake-resource-group", "test-nsg", "") # Validate that the response contains 5 rules assert len(sorted_rules_result) == 5 # Validate that the correct integration instance's response was identified assert instance_to_use_result == "azure-correct-instance" # Validate that nsg rule entries are sorted in ascending order by priority priorities = [rule["properties"]["priority"] for rule in sorted_rules_result] assert priorities == sorted(priorities), f"Rules are not sorted by priority. Found order: {priorities}" def test_find_available_priorities_success(): """ Test find_available_priorities function with valid inputs that should return available priorities. """ from AzureIdentifyNSGExposureRule import find_available_priorities nsg_rules = util_load_json("./test_data/sorted_nsg_rules.json") # Test finding 2 available priorities before priority 105 # Available should be 104 and 101 (counting down from 104) result = find_available_priorities(105, nsg_rules, 2) assert len(result) == 2 assert 104 in result assert 101 in result # Ensure none of the existing priorities are returned existing_priorities = [102, 103, 105, 107, 117] for priority in result: assert priority not in existing_priorities def test_find_available_priorities_single(): """ Test find_available_priorities function when requesting a single priority. """ from AzureIdentifyNSGExposureRule import find_available_priorities nsg_rules = util_load_json("./test_data/sorted_nsg_rules.json") # Test finding 1 available priority before priority 107 # Available should be 106 result = find_available_priorities(107, nsg_rules, 1) assert len(result) == 1 assert result[0] == 106 def test_find_available_priorities_insufficient(): """ Test find_available_priorities function when there aren't enough available priorities. """ from AzureIdentifyNSGExposureRule import find_available_priorities # Create rules that occupy most priorities near 100 nsg_rules = util_load_json("./test_data/sorted_nsg_rules.json") # Try to find 5 available priorities before 105 when there aren't enough with pytest.raises(DemistoException) as exc_info: find_available_priorities(105, nsg_rules, 5) assert "Requested 5 available priority values, but only found" in str(exc_info.value) def test_find_available_priorities_edge_case_near_limit(): """ Test find_available_priorities function near the lower limit of 100. """ from AzureIdentifyNSGExposureRule import find_available_priorities nsg_rules = util_load_json("./test_data/sorted_nsg_rules.json") # Find available priorities before 103 (should find 101 and 100) result = find_available_priorities(103, nsg_rules, 2) assert len(result) == 2 assert 101 in result assert 100 in result def test_process_nsg_info_success(mocker): """ Test process_nsg_info function with valid arguments. """ from AzureIdentifyNSGExposureRule import process_nsg_info nsg_rules = util_load_json("./test_data/sorted_nsg_rules.json") # Mock the get_nsg_rules function mocker.patch("AzureIdentifyNSGExposureRule.get_nsg_rules", return_value=(nsg_rules, "azure-correct-instance")) # Mock find_matching_rule function (assuming it exists) mocker.patch("AzureIdentifyNSGExposureRule.find_matching_rule", return_value=("AllowSshToAll", 102)) args = { "subscription_id": "fake-subscription-id", "resource_group_name": "fake-resource-group", "network_security_group_name": "test-nsg", "private_ip_addresses": "1.2.3.4", "port": "22", "protocol": "TCP", "priority_count": "2", "integration_instance": "", } result = process_nsg_info(args) expected_result = { "MatchingRuleName": "AllowSshToAll", "MatchingRulePriority": 102, "NextAvailablePriorityValues": [101, 100], "IntegrationInstance": "azure-correct-instance", } # Validate CommandResults structure assert result.outputs == expected_result def test_process_nsg_info_multiple_ips(mocker): """ Test process_nsg_info function with multiple IP addresses. """ from AzureIdentifyNSGExposureRule import process_nsg_info nsg_rules = util_load_json("./test_data/sorted_nsg_rules.json") # Mock the get_nsg_rules function mocker.patch("AzureIdentifyNSGExposureRule.get_nsg_rules", return_value=(nsg_rules, "azure-correct-instance")) # Mock find_matching_rule function mocker.patch("AzureIdentifyNSGExposureRule.find_matching_rule", return_value=("AllowRDPtoJumpBoxes", 103)) args = { "subscription_id": "fake-subscription-id", "resource_group_name": "fake-resource-group", "network_security_group_name": "test-nsg", "private_ip_addresses": ["1.2.3.4", "1.2.3.5"], "port": "3389", "protocol": "TCP", "priority_count": "1", "integration_instance": "", } result = process_nsg_info(args) # Should succeed with multiple IPs expected_results = { "MatchingRuleName": "AllowRDPtoJumpBoxes", "MatchingRulePriority": 103, "NextAvailablePriorityValues": [101], "IntegrationInstance": "azure-correct-instance", } assert result.outputs == expected_results def test_process_nsg_info_comma_separated_ips(mocker): """ Test process_nsg_info function with comma-separated IP addresses in a string. """ from AzureIdentifyNSGExposureRule import process_nsg_info nsg_rules = util_load_json("./test_data/sorted_nsg_rules.json") # Mock the get_nsg_rules function mocker.patch("AzureIdentifyNSGExposureRule.get_nsg_rules", return_value=(nsg_rules, "azure-correct-instance")) # Mock find_matching_rule function mocker.patch("AzureIdentifyNSGExposureRule.find_matching_rule", return_value=("AllowHTTPServices", 105)) args = { "subscription_id": "fake-subscription-id", "resource_group_name": "fake-resource-group", "network_security_group_name": "test-nsg", "private_ip_addresses": "1.2.3.4, 1.2.3.5, 1.2.3.6", "port": "80", "protocol": "TCP", "priority_count": "1", "integration_instance": "", } result = process_nsg_info(args) expected_result = { "MatchingRuleName": "AllowHTTPServices", "MatchingRulePriority": 105, "NextAvailablePriorityValues": [104], "IntegrationInstance": "azure-correct-instance", } # Should succeed with comma-separated IPs assert result.outputs == expected_result def test_process_nsg_info_invalid_ips(): """ Test process_nsg_info function with invalid IP addresses. """ from AzureIdentifyNSGExposureRule import process_nsg_info args = { "subscription_id": "fake-subscription-id", "resource_group_name": "fake-resource-group", "network_security_group_name": "test-nsg", "private_ip_addresses": "invalid-ip", "port": "22", "protocol": "TCP", "priority_count": "2", "integration_instance": "", } with pytest.raises(ValueError) as exc_info: process_nsg_info(args) assert str(exc_info.value) == "Invalid IP address provided: invalid-ip" def test_process_nsg_info_empty_ips(): """ Test process_nsg_info function with empty IP addresses. """ from AzureIdentifyNSGExposureRule import process_nsg_info args = { "subscription_id": "fake-subscription-id", "resource_group_name": "fake-resource-group", "network_security_group_name": "test-nsg", "private_ip_addresses": "", "port": "22", "protocol": "TCP", "priority_count": "2", "integration_instance": "", } with pytest.raises(ValueError) as exc_info: process_nsg_info(args) assert "At least one valid IP address must be provided" in str(exc_info.value) def test_port_matches_range_single_port(): """ Test _port_matches_range function with a single port. """ from AzureIdentifyNSGExposureRule import _port_matches_range result = _port_matches_range(80, "80") assert result is True def test_port_matches_range_multiple_ports(): """ Test _port_matches_range function with a multiple individual ports. """ from AzureIdentifyNSGExposureRule import _port_matches_range result = _port_matches_range(80, "80,443") assert result is True def test_port_matches_range_multiple_ports_with_range(): """ Test _port_matches_range function with multiple ports and ranges """ from AzureIdentifyNSGExposureRule import _port_matches_range result = _port_matches_range(80, "22, 79-81") assert result is True def test_ip_matches_prefix_in_cidr(): """ Test _ip_matches_prefix function when the target IP address is within a CIDR prefix """ from AzureIdentifyNSGExposureRule import _ip_matches_prefix target_ip_obj = ipaddress.ip_address("10.0.0.5") result = _ip_matches_prefix(target_ip_obj, "10.0.0.0/24") assert result is True def test_ip_matches_prefix_not_in_cidr(): """ Test _ip_matches_prefix function when the target IP address is not within a CIDR prefix """ from AzureIdentifyNSGExposureRule import _ip_matches_prefix target_ip_obj = ipaddress.ip_address("10.0.0.5") result = _ip_matches_prefix(target_ip_obj, "10.0.1.0/24") assert result is False def test_ip_matches_prefix_invalid_prefix(): """ Test _ip_matches_prefix function when the provided prefix string is invalid """ from AzureIdentifyNSGExposureRule import _ip_matches_prefix target_ip_obj = ipaddress.ip_address("10.0.0.5") result = _ip_matches_prefix(target_ip_obj, "10.0.0.0/42") assert result is False def test_get_nsg_rules_error(mocker): """Tests that get_nsg_rules raises DemistoException when the command returns an error. Given: - A mocked error response from azure-vn-security-rules-list When: - Calling get_nsg_rules Then: - A DemistoException is raised """ from AzureIdentifyNSGExposureRule import get_nsg_rules error_result = [{"Type": 4, "Contents": "Some error occurred", "ContentsFormat": "text"}] mocker.patch.object(demisto, "executeCommand", return_value=error_result) with pytest.raises(DemistoException, match="Error retrieving security group details"): get_nsg_rules("fake-subscription-id", "fake-resource-group", "test-nsg", "") def test_get_nsg_rules_no_inbound_rules(mocker): """Tests that get_nsg_rules raises DemistoException when no inbound rules are found. Given: - A valid response from azure-vn-security-rules-list with only outbound rules When: - Calling get_nsg_rules Then: - A DemistoException is raised indicating no inbound rules were found """ from AzureIdentifyNSGExposureRule import get_nsg_rules outbound_only_result = [ { "Type": 1, "Metadata": {"instance": "azure-instance"}, "Contents": [ { "properties": { "priority": 100, "direction": "Outbound", "protocol": "TCP", "access": "Allow", "destinationPortRange": "443", "sourceAddressPrefix": "*", "destinationAddressPrefix": "*", }, "name": "AllowOutboundHTTPS", } ], } ] mocker.patch.object(demisto, "executeCommand", return_value=outbound_only_result) with pytest.raises(DemistoException, match="No inbound NSG rules found"): get_nsg_rules("fake-subscription-id", "fake-resource-group", "test-nsg", "") def test_get_nsg_rules_empty_result(mocker): """Tests that get_nsg_rules raises DemistoException when the command returns an empty list. Given: - An empty list response from azure-vn-security-rules-list When: - Calling get_nsg_rules Then: - A DemistoException is raised indicating no results returned """ from AzureIdentifyNSGExposureRule import get_nsg_rules mocker.patch.object(demisto, "executeCommand", return_value=[]) with pytest.raises(DemistoException, match="No results returned"): get_nsg_rules("fake-subscription-id", "fake-resource-group", "test-nsg", "")