diff --git a/monkey/infection_monkey/island_api_client/__init__.py b/monkey/infection_monkey/island_api_client/__init__.py new file mode 100644 index 000000000..ec513e774 --- /dev/null +++ b/monkey/infection_monkey/island_api_client/__init__.py @@ -0,0 +1,9 @@ +from .island_api_client_errors import ( + IslandAPIConnectionError, + IslandAPIError, + IslandAPIRequestError, + IslandAPIRequestFailedError, + IslandAPITimeoutError, +) +from .i_island_api_client import IIslandAPIClient +from .http_island_api_client import HTTPIslandAPIClient diff --git a/monkey/infection_monkey/island_api_client/http_island_api_client.py b/monkey/infection_monkey/island_api_client/http_island_api_client.py new file mode 100644 index 000000000..d9f9b1d9e --- /dev/null +++ b/monkey/infection_monkey/island_api_client/http_island_api_client.py @@ -0,0 +1,29 @@ +import logging + +import requests + +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT + +from . import IIslandAPIClient, IslandAPIConnectionError, IslandAPIError, IslandAPITimeoutError + +logger = logging.getLogger(__name__) + + +class HTTPIslandAPIClient(IIslandAPIClient): + """ + A client for the Island's HTTP API + """ + + def __init__(self, island_server: str): + try: + requests.get( # noqa: DUO123 + f"https://{island_server}/api?action=is-up", + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) + except requests.exceptions.ConnectionError as err: + raise IslandAPIConnectionError(err) + except TimeoutError as err: + raise IslandAPITimeoutError(err) + except Exception as err: + raise IslandAPIError(err) diff --git a/monkey/infection_monkey/island_api_client/i_island_api_client.py b/monkey/infection_monkey/island_api_client/i_island_api_client.py new file mode 100644 index 000000000..4a168b3d5 --- /dev/null +++ b/monkey/infection_monkey/island_api_client/i_island_api_client.py @@ -0,0 +1,19 @@ +from abc import ABC, abstractmethod + + +class IIslandAPIClient(ABC): + """ + A client for the Island's API + """ + + @abstractmethod + def __init__(self, island_server: str): + """ + Construct and island API client and connect it to the island + + :param island_server: The socket address of the API + :raises IslandAPIConnectionError: If the client cannot successfully connect to the island + :raises IslandAPITimeoutError: If a timeout occurs while attempting to connect to the island + :raises IslandAPIError: If an unexpected error occurs while attempting to connect to the + island + """ diff --git a/monkey/infection_monkey/island_api_client/island_api_client_errors.py b/monkey/infection_monkey/island_api_client/island_api_client_errors.py new file mode 100644 index 000000000..cdb23539a --- /dev/null +++ b/monkey/infection_monkey/island_api_client/island_api_client_errors.py @@ -0,0 +1,38 @@ +class IslandAPIError(Exception): + """ + Raised when something goes wrong when calling the Island API + """ + + pass + + +class IslandAPITimeoutError(IslandAPIError): + """ + Raised when the API request hits a timeout + """ + + pass + + +class IslandAPIConnectionError(IslandAPIError): + """ + Raised when the API request can't find/connect to the Island + """ + + pass + + +class IslandAPIRequestError(IslandAPIError): + """ + Raised when the API request fails due to an error in the request sent from the client + """ + + pass + + +class IslandAPIRequestFailedError(IslandAPIError): + """ + Raised when the API request fails due to an error on the server + """ + + pass diff --git a/monkey/tests/unit_tests/infection_monkey/island_api_client/__init__.py b/monkey/tests/unit_tests/infection_monkey/island_api_client/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/tests/unit_tests/infection_monkey/island_api_client/test_http_island_api_client.py b/monkey/tests/unit_tests/infection_monkey/island_api_client/test_http_island_api_client.py new file mode 100644 index 000000000..f213b0569 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/island_api_client/test_http_island_api_client.py @@ -0,0 +1,30 @@ +import pytest +import requests +import requests_mock + +from infection_monkey.island_api_client import ( + HTTPIslandAPIClient, + IslandAPIConnectionError, + IslandAPIError, + IslandAPITimeoutError, +) + +SERVER = "1.1.1.1:9999" + +ISLAND_URI = f"https://{SERVER}/api?action=is-up" + + +@pytest.mark.parametrize( + "actual_error, expected_error", + [ + (requests.exceptions.ConnectionError, IslandAPIConnectionError), + (TimeoutError, IslandAPITimeoutError), + (Exception, IslandAPIError), + ], +) +def test_island_api_client(actual_error, expected_error): + with requests_mock.Mocker() as m: + m.get(ISLAND_URI, exc=actual_error) + + with pytest.raises(expected_error): + HTTPIslandAPIClient(SERVER) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 53001be59..83fb82005 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -9,6 +9,12 @@ from common.agent_configuration.agent_sub_configurations import ( ) from common.credentials import Credentials, LMHash, NTHash from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory +from infection_monkey.island_api_client import ( + HTTPIslandAPIClient, + IIslandAPIClient, + IslandAPIRequestError, + IslandAPIRequestFailedError, +) from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue from monkey_island.cc.models import Report from monkey_island.cc.models.networkmap import Arc, NetworkMap @@ -328,3 +334,9 @@ CC_TUNNEL IslandEventTopic.AGENT_CONNECTED IslandEventTopic.CLEAR_SIMULATION_DATA IslandEventTopic.RESET_AGENT_CONFIGURATION + +# TODO: Remove after #2292 is closed +IIslandAPIClient +HTTPIslandAPIClient +IslandAPIRequestFailedError +IslandAPIRequestError