From f62ab10d1cb380a10d48b8d2042b776ef9fd41b8 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 29 Sep 2022 18:33:02 +0200 Subject: [PATCH 01/10] Common: Add NetworkPort type --- monkey/common/types.py | 16 ++++++++++++++-- vulture_allowlist.py | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/monkey/common/types.py b/monkey/common/types.py index 439f07c63..b4503afc2 100644 --- a/monkey/common/types.py +++ b/monkey/common/types.py @@ -6,7 +6,7 @@ from ipaddress import IPv4Address from typing import Optional from uuid import UUID -from pydantic import PositiveInt, conint +from pydantic import ConstrainedInt, PositiveInt from typing_extensions import TypeAlias from common import OperatingSystem @@ -18,6 +18,18 @@ HardwareID: TypeAlias = PositiveInt MachineID: TypeAlias = PositiveInt +class NetworkPort(ConstrainedInt): + """ + Define network port as constrainer integer. + + To define a default value with this type: + port: NetworkPort = typing.cast(NetworkPort, 1000) + """ + + ge = 1 + le = 65535 + + @dataclass class PingScanData: response_received: bool @@ -38,7 +50,7 @@ class PortStatus(Enum): class SocketAddress(InfectionMonkeyBaseModel): ip: IPv4Address - port: conint(ge=1, le=65535) # type: ignore[valid-type] + port: NetworkPort @classmethod def from_string(cls, address_str: str) -> SocketAddress: diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 699951b10..fd4e3fbf3 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -29,6 +29,8 @@ from monkey_island.cc.repository.zero_trust.IEventRepository import IEventReposi from monkey_island.cc.repository.zero_trust.IFindingRepository import IFindingRepository from monkey_island.cc.services import AgentSignalsService +ge # unused vairable (monkey/common/types.py:28) +le # unused variable (monkey/common/types.py:29) fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37) set_os_linux # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37) fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:57) From 28ca462ce574d2f389d133c6e225f939a02b7200 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 29 Sep 2022 18:40:56 +0200 Subject: [PATCH 02/10] Common: Add TCPScanEvent to agent events --- monkey/common/agent_events/__init__.py | 1 + monkey/common/agent_events/tcp_scan_event.py | 23 ++++++++++++++++++++ vulture_allowlist.py | 6 ++++- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 monkey/common/agent_events/tcp_scan_event.py diff --git a/monkey/common/agent_events/__init__.py b/monkey/common/agent_events/__init__.py index ec9d21448..3ac5a343d 100644 --- a/monkey/common/agent_events/__init__.py +++ b/monkey/common/agent_events/__init__.py @@ -1,3 +1,4 @@ from .abstract_agent_event import AbstractAgentEvent from .credentials_stolen_events import CredentialsStolenEvent from .ping_scan_event import PingScanEvent +from .tcp_scan_event import TCPScanEvent diff --git a/monkey/common/agent_events/tcp_scan_event.py b/monkey/common/agent_events/tcp_scan_event.py new file mode 100644 index 000000000..dffba5f96 --- /dev/null +++ b/monkey/common/agent_events/tcp_scan_event.py @@ -0,0 +1,23 @@ +from ipaddress import IPv4Address + +from common.types import NetworkPort, PortStatus + +from . import AbstractAgentEvent + + +class TCPScanEvent(AbstractAgentEvent): + """ + An event that occurs when the Agent performs a TCP scan on its network + + Attributes: + :param port: Port on which the scan was performed + :param port_status: Status of the port (closed/open) + :param banner: Information from the tcp response + :param service: Name of the service which runs on the port + """ + + target: IPv4Address + port: NetworkPort + port_status: PortStatus + banner: str + service: str diff --git a/vulture_allowlist.py b/vulture_allowlist.py index fd4e3fbf3..e19c2c79b 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -7,7 +7,7 @@ from common.agent_configuration.agent_sub_configurations import ( CustomPBAConfiguration, ScanTargetConfiguration, ) -from common.agent_events import PingScanEvent +from common.agent_events import PingScanEvent, TCPScanEvent from common.credentials import Credentials, LMHash, NTHash from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue @@ -313,6 +313,10 @@ IAgentLogRepository.get_agent_log # TODO: Remove once #2268 is closed PingScanEvent +# TODO: Remove once #2267 is closed +TCPScanEvent +TCPScanEvent.port_status + # pydantic base models underscore_attrs_are_private extra From edaa7ec34d44d5a64501a298d20ccec1262355b0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Sep 2022 13:29:21 -0400 Subject: [PATCH 03/10] Common: Reword TCPScanEvent summary --- monkey/common/agent_events/tcp_scan_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/common/agent_events/tcp_scan_event.py b/monkey/common/agent_events/tcp_scan_event.py index dffba5f96..4cc30aba2 100644 --- a/monkey/common/agent_events/tcp_scan_event.py +++ b/monkey/common/agent_events/tcp_scan_event.py @@ -7,7 +7,7 @@ from . import AbstractAgentEvent class TCPScanEvent(AbstractAgentEvent): """ - An event that occurs when the Agent performs a TCP scan on its network + An event that occurs when the Agent performs a TCP scan on a host Attributes: :param port: Port on which the scan was performed From ba7e44038cc80ee0f0cbb013f70cd5401283be32 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Sep 2022 13:29:43 -0400 Subject: [PATCH 04/10] Common: Remove "service" from TCPScanEvent It's not the responsibility of the agent to format this information for display. --- monkey/common/agent_events/tcp_scan_event.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/common/agent_events/tcp_scan_event.py b/monkey/common/agent_events/tcp_scan_event.py index 4cc30aba2..84f85572f 100644 --- a/monkey/common/agent_events/tcp_scan_event.py +++ b/monkey/common/agent_events/tcp_scan_event.py @@ -13,11 +13,9 @@ class TCPScanEvent(AbstractAgentEvent): :param port: Port on which the scan was performed :param port_status: Status of the port (closed/open) :param banner: Information from the tcp response - :param service: Name of the service which runs on the port """ target: IPv4Address port: NetworkPort port_status: PortStatus banner: str - service: str From 34ca127c6c9aab51ecc28a9c39cf3f6573fc5aae Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Sep 2022 13:31:06 -0400 Subject: [PATCH 05/10] Common: Capitalize TCP --- monkey/common/agent_events/tcp_scan_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/common/agent_events/tcp_scan_event.py b/monkey/common/agent_events/tcp_scan_event.py index 84f85572f..3341a878d 100644 --- a/monkey/common/agent_events/tcp_scan_event.py +++ b/monkey/common/agent_events/tcp_scan_event.py @@ -12,7 +12,7 @@ class TCPScanEvent(AbstractAgentEvent): Attributes: :param port: Port on which the scan was performed :param port_status: Status of the port (closed/open) - :param banner: Information from the tcp response + :param banner: Information from the TCP response """ target: IPv4Address From ccaf0b63c68385018f24e5c2159b9e5a2a6a7920 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Sep 2022 13:38:33 -0400 Subject: [PATCH 06/10] Common: Remove banner from TCPScanEvent --- monkey/common/agent_events/tcp_scan_event.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/common/agent_events/tcp_scan_event.py b/monkey/common/agent_events/tcp_scan_event.py index 3341a878d..1f629214e 100644 --- a/monkey/common/agent_events/tcp_scan_event.py +++ b/monkey/common/agent_events/tcp_scan_event.py @@ -12,10 +12,8 @@ class TCPScanEvent(AbstractAgentEvent): Attributes: :param port: Port on which the scan was performed :param port_status: Status of the port (closed/open) - :param banner: Information from the TCP response """ target: IPv4Address port: NetworkPort port_status: PortStatus - banner: str From 349b183e5d62e7f956714e8b521702dbd95d3df5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Sep 2022 13:39:59 -0400 Subject: [PATCH 07/10] Common: Represent multiple ports in TCPScanEvent --- monkey/common/agent_events/tcp_scan_event.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/common/agent_events/tcp_scan_event.py b/monkey/common/agent_events/tcp_scan_event.py index 1f629214e..f7950916a 100644 --- a/monkey/common/agent_events/tcp_scan_event.py +++ b/monkey/common/agent_events/tcp_scan_event.py @@ -1,4 +1,5 @@ from ipaddress import IPv4Address +from typing import Dict from common.types import NetworkPort, PortStatus @@ -10,10 +11,8 @@ class TCPScanEvent(AbstractAgentEvent): An event that occurs when the Agent performs a TCP scan on a host Attributes: - :param port: Port on which the scan was performed - :param port_status: Status of the port (closed/open) + :param ports: The scanned ports and their status (open/closed) """ target: IPv4Address - port: NetworkPort - port_status: PortStatus + ports: Dict[NetworkPort, PortStatus] From 28026716dbab325f403f86285d9a72949a354828 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Sep 2022 13:41:19 -0400 Subject: [PATCH 08/10] Project: Import NetworkPort in vulture_allowlist.py --- vulture_allowlist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index e19c2c79b..48a50e8cc 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -9,6 +9,7 @@ from common.agent_configuration.agent_sub_configurations import ( ) from common.agent_events import PingScanEvent, TCPScanEvent from common.credentials import Credentials, LMHash, NTHash +from common.types import NetworkPort from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue from monkey_island.cc.models import Report @@ -29,8 +30,8 @@ from monkey_island.cc.repository.zero_trust.IEventRepository import IEventReposi from monkey_island.cc.repository.zero_trust.IFindingRepository import IFindingRepository from monkey_island.cc.services import AgentSignalsService -ge # unused vairable (monkey/common/types.py:28) -le # unused variable (monkey/common/types.py:29) +NetworkPort.ge # unused vairable (monkey/common/types.py:28) +NetworkPort.le # unused variable (monkey/common/types.py:29) fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37) set_os_linux # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37) fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:57) From 208ba1c2ab0b6606e39afd6bba756960a544b59d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Sep 2022 13:49:50 -0400 Subject: [PATCH 09/10] Common: Use lower-case member name for PortStatus values --- monkey/common/types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/common/types.py b/monkey/common/types.py index b4503afc2..89794b942 100644 --- a/monkey/common/types.py +++ b/monkey/common/types.py @@ -41,11 +41,11 @@ class PortStatus(Enum): An Enum representing the status of the port. This Enum represents the status of a network pork. The value of each - member is distincive and unique number. + member is the member's name in all lower-case characters. """ - OPEN = 1 - CLOSED = 2 + OPEN = "open" + CLOSED = "closed" class SocketAddress(InfectionMonkeyBaseModel): From b95baaba87eda899a7778f5b7c0b2b3e7127fb66 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Sep 2022 13:59:55 -0400 Subject: [PATCH 10/10] UT: Add tests for TCPScanEvent --- .../agent_events/test_tcp_scan_event.py | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 monkey/tests/unit_tests/common/agent_events/test_tcp_scan_event.py diff --git a/monkey/tests/unit_tests/common/agent_events/test_tcp_scan_event.py b/monkey/tests/unit_tests/common/agent_events/test_tcp_scan_event.py new file mode 100644 index 000000000..5d64eb62e --- /dev/null +++ b/monkey/tests/unit_tests/common/agent_events/test_tcp_scan_event.py @@ -0,0 +1,95 @@ +from ipaddress import IPv4Address +from uuid import UUID + +import pytest + +from common.agent_events import TCPScanEvent +from common.types import PortStatus + +TARGET_IP_STR = "192.168.1.10" +AGENT_ID = UUID("012e7238-7b81-4108-8c7f-0787bc3f3c10") + +TCP_SCAN_EVENT = TCPScanEvent( + source=AGENT_ID, + timestamp=1664371327.4067292, + target=IPv4Address(TARGET_IP_STR), + ports={ + 22: PortStatus.OPEN, + 80: PortStatus.CLOSED, + 443: PortStatus.OPEN, + 8080: PortStatus.CLOSED, + }, +) + +TCP_OBJECT_DICT = { + "source": AGENT_ID, + "timestamp": 1664371327.4067292, + "target": IPv4Address(TARGET_IP_STR), + "ports": { + 22: PortStatus.OPEN, + 80: PortStatus.CLOSED, + 443: PortStatus.OPEN, + 8080: PortStatus.CLOSED, + }, +} + +TCP_SIMPLE_DICT = { + "source": str(AGENT_ID), + "timestamp": 1664371327.4067292, + "target": TARGET_IP_STR, + "ports": { + 22: "open", + 80: "closed", + 443: "open", + 8080: "closed", + }, +} + + +@pytest.mark.parametrize("tcp_event_dict", [TCP_OBJECT_DICT, TCP_SIMPLE_DICT]) +def test_constructor(tcp_event_dict): + assert TCPScanEvent(**tcp_event_dict) == TCP_SCAN_EVENT + + +def test_to_dict(): + TCP_SCAN_EVENT.dict(simplify=True) == TCP_SIMPLE_DICT + + +@pytest.mark.parametrize( + "key, value", + [ + ("target", None), + ("ports", "not-a-dict"), + ("ports", {"not-a-number": "open"}), + ("ports", {22: "bogus"}), + ], +) +def test_construct_invalid_field__type_error(key, value): + invalid_type_dict = TCP_SIMPLE_DICT.copy() + invalid_type_dict[key] = value + + with pytest.raises(TypeError): + TCPScanEvent(**invalid_type_dict) + + +@pytest.mark.parametrize( + "key, value", + [ + ("target", "not-an-ip"), + ("ports", {99999: "closed"}), + ], +) +def test_construct_invalid_field__value_error(key, value): + invalid_type_dict = TCP_SIMPLE_DICT.copy() + invalid_type_dict[key] = value + + with pytest.raises(ValueError): + TCPScanEvent(**invalid_type_dict) + + +def test_construct__extra_fields_forbidden(): + extra_field_dict = TCP_SIMPLE_DICT.copy() + extra_field_dict["extra_field"] = 99 # red balloons + + with pytest.raises(ValueError): + TCPScanEvent(**extra_field_dict)