From 65eb9b171bfd03e44ac2ee395d97583b4d8e3d80 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 27 Apr 2022 18:10:23 +0300 Subject: [PATCH 01/21] Island, Common: Move singleton to code_utils.py in common Singleton is a common pattern, potentially usable in the Agent so it belongs in common --- monkey/common/utils/code_utils.py | 9 +++++++++ .../cc/services/reporting/report_exporter_manager.py | 11 ++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index bb77f9f61..f4060870b 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -10,3 +10,12 @@ class abstractstatic(staticmethod): function.__isabstractmethod__ = True __isabstractmethod__ = True + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py index 99d2ac629..a71679685 100644 --- a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py +++ b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py @@ -1,17 +1,10 @@ import logging +from common.utils.code_utils import Singleton + logger = logging.getLogger(__name__) -class Singleton(type): - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) - return cls._instances[cls] - - class ReportExporterManager(object, metaclass=Singleton): def __init__(self): self._exporters_set = set() From 0e1ffb40512ce08b318a5bb9c5b37574d4c70dc6 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 27 Apr 2022 18:15:03 +0300 Subject: [PATCH 02/21] Common: Change AwsInstance to be a Singleton --- monkey/common/cloud/aws/aws_instance.py | 66 +++++++++++++++++-------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index 76f4ab258..ba1fd36b6 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -1,10 +1,13 @@ import json import logging import re +from dataclasses import dataclass +from typing import Tuple, Optional import requests from common.cloud.instance import CloudInstance +from common.utils.code_utils import Singleton AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254" AWS_LATEST_METADATA_URI_PREFIX = "http://{0}/latest/".format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS) @@ -15,45 +18,68 @@ logger = logging.getLogger(__name__) AWS_TIMEOUT = 2 +@dataclass +class AwsInstanceInfo: + instance_id: Optional[str] = None + region: Optional[str] = None + account_id: Optional[str] = None + + class AwsInstance(CloudInstance): """ Class which gives useful information about the current instance you're on. """ - - def is_instance(self): - return self.instance_id is not None + __metaclass__ = Singleton def __init__(self): - self.instance_id = None - self.region = None - self.account_id = None + self._is_instance, instance_info = AwsInstance._fetch_instance_info() + self.instance_id = instance_info.instance_id + self.region = instance_info.region + self.account_id = instance_info.account_id + + def is_instance(self) -> bool: + return self._is_instance + + @staticmethod + def _fetch_instance_info() -> Tuple[bool, AwsInstanceInfo]: try: response = requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", - timeout=AWS_TIMEOUT, - ) - self.instance_id = response.text if response else None - self.region = self._parse_region( - requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone", + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=AWS_TIMEOUT, - ).text + ) + if not response: + return False, AwsInstanceInfo() + + info = AwsInstanceInfo() + info.instance_id = response.text if response else False + info.region = AwsInstance._parse_region( + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + + "meta-data/placement/availability-zone", + timeout=AWS_TIMEOUT, + ).text ) except (requests.RequestException, IOError) as e: logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e)) + return False, AwsInstanceInfo() try: - self.account_id = self._extract_account_id( - requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", - timeout=AWS_TIMEOUT, - ).text + info.account_id = AwsInstance._extract_account_id( + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + + "dynamic/instance-identity/document", + timeout=AWS_TIMEOUT, + ).text ) except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e: logger.debug( - "Failed init of AwsInstance while getting dynamic instance data: {}".format(e) + "Failed init of AwsInstance while getting dynamic instance data: {}".format( + e) ) + return False, AwsInstanceInfo() + + return True, info @staticmethod def _parse_region(region_url_response): From d3c1ff89e9a846cf925791e2a77b50b35515c4bb Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 27 Apr 2022 18:18:48 +0300 Subject: [PATCH 03/21] Island: Run AWS services on separate threads AWS related services call AWS metadata service which might take a long time to timeout, that's why they are ran on a separate thread --- monkey/monkey_island/cc/app.py | 5 +++-- monkey/monkey_island/cc/server_setup.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 7cb34a5a3..97f7dfca1 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -1,6 +1,7 @@ import os import uuid from datetime import timedelta +from threading import Thread from typing import Type import flask_restful @@ -104,8 +105,8 @@ def init_app_services(app): database.init() # If running on AWS, this will initialize the instance data, which is used "later" in the - # execution of the island. - RemoteRunAwsService.init() + # execution of the island. Run on a daemon thread since it's slow. + Thread(target=RemoteRunAwsService.init, name="AWS check thread", daemon=True).start() def init_app_url_rules(app): diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 399b8ebc9..c883d70db 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -3,6 +3,7 @@ import json import logging import sys from pathlib import Path +from threading import Thread import gevent.hub from gevent.pywsgi import WSGIServer @@ -131,7 +132,8 @@ def _configure_gevent_exception_handling(data_dir): def _start_island_server( should_setup_only: bool, config_options: IslandConfigOptions, container: DIContainer ): - populate_exporter_list() + # AWS exporter takes a long time to load + Thread(target=populate_exporter_list, name="Report exporter list", daemon=True).start() app = init_app(mongo_setup.MONGO_URL, container) if should_setup_only: From 8535118e4f5810777915fef2b22f07da495e8799 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 27 Apr 2022 18:20:36 +0300 Subject: [PATCH 04/21] Island: Add locks to remote_run_aws.py Locks will avoid the situation where is_running_on_aws is called before this service finished initializing --- .../monkey_island/cc/services/remote_run_aws.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 9f94a4d0b..7b2bdbf90 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -1,4 +1,5 @@ import logging +from threading import Lock from common.cloud.aws.aws_instance import AwsInstance from common.cloud.aws.aws_service import AwsService @@ -7,6 +8,7 @@ from common.cmd.cmd import Cmd from common.cmd.cmd_runner import CmdRunner logger = logging.getLogger(__name__) +aws_lock = Lock() class RemoteRunAwsService: @@ -23,15 +25,8 @@ class RemoteRunAwsService: :return: None """ if RemoteRunAwsService.aws_instance is None: - RemoteRunAwsService.try_init_aws_instance() - - @staticmethod - def try_init_aws_instance(): - # noinspection PyBroadException - try: - RemoteRunAwsService.aws_instance = AwsInstance() - except Exception: - logger.error("Failed init aws instance. Exception info: ", exc_info=True) + with aws_lock: + RemoteRunAwsService.aws_instance = AwsInstance() @staticmethod def run_aws_monkeys(instances, island_ip): @@ -53,7 +48,8 @@ class RemoteRunAwsService: @staticmethod def is_running_on_aws(): - return RemoteRunAwsService.aws_instance.is_instance() + with aws_lock: + return RemoteRunAwsService.aws_instance.is_instance() @staticmethod def update_aws_region_authless(): From 946c394c74948d80a93aade6cd8808fe82dfc364 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 28 Apr 2022 09:26:06 +0300 Subject: [PATCH 05/21] Changelog: Add entry about fixed long AWS check on island startup --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d2ac94f..374815c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - A bug where windows executable was not self deleting. #1763 - Incorrect line number in the telemetry overview window on the Map page. #1850 - Automatic jumping to the bottom in the telemetry overview windows. #1850 +- 2-second delay when the Island server starts, and it's not running on AWS. #1636 ### Security - Change SSH exploiter so that it does not set the permissions of the agent From f63bc77df0a2c304daa4e4b3141c2bcbd6484e0e Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 28 Apr 2022 11:48:06 +0300 Subject: [PATCH 06/21] Common: Fix aws_instance.py formatting --- monkey/common/cloud/aws/aws_instance.py | 30 ++++++++++++------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index ba1fd36b6..b1c2c174d 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -2,7 +2,7 @@ import json import logging import re from dataclasses import dataclass -from typing import Tuple, Optional +from typing import Optional, Tuple import requests @@ -29,6 +29,7 @@ class AwsInstance(CloudInstance): """ Class which gives useful information about the current instance you're on. """ + __metaclass__ = Singleton def __init__(self): @@ -45,20 +46,19 @@ class AwsInstance(CloudInstance): def _fetch_instance_info() -> Tuple[bool, AwsInstanceInfo]: try: response = requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", - timeout=AWS_TIMEOUT, - ) + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", + timeout=AWS_TIMEOUT, + ) if not response: return False, AwsInstanceInfo() info = AwsInstanceInfo() info.instance_id = response.text if response else False info.region = AwsInstance._parse_region( - requests.get( - AWS_LATEST_METADATA_URI_PREFIX + - "meta-data/placement/availability-zone", - timeout=AWS_TIMEOUT, - ).text + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone", + timeout=AWS_TIMEOUT, + ).text ) except (requests.RequestException, IOError) as e: logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e)) @@ -66,16 +66,14 @@ class AwsInstance(CloudInstance): try: info.account_id = AwsInstance._extract_account_id( - requests.get( - AWS_LATEST_METADATA_URI_PREFIX + - "dynamic/instance-identity/document", - timeout=AWS_TIMEOUT, - ).text + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", + timeout=AWS_TIMEOUT, + ).text ) except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e: logger.debug( - "Failed init of AwsInstance while getting dynamic instance data: {}".format( - e) + "Failed init of AwsInstance while getting dynamic instance data: {}".format(e) ) return False, AwsInstanceInfo() From fead7f602e2e5b6d40ebb3018bea06334785efd4 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 28 Apr 2022 14:47:49 +0300 Subject: [PATCH 07/21] Island, Common: Change AwsInstance properties to private, add getter --- monkey/common/cloud/aws/aws_instance.py | 13 +++++++------ .../utils/aws_environment_check.py | 2 +- monkey/monkey_island/cc/services/remote_run_aws.py | 4 ++-- monkey/monkey_island/monkey_island.spec | 4 ++-- .../common/cloud/aws/test_aws_instance.py | 14 +++++++------- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index b1c2c174d..8f00cc399 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -35,10 +35,11 @@ class AwsInstance(CloudInstance): def __init__(self): self._is_instance, instance_info = AwsInstance._fetch_instance_info() - self.instance_id = instance_info.instance_id - self.region = instance_info.region - self.account_id = instance_info.account_id + self._instance_id = instance_info.instance_id + self._region = instance_info.region + self._account_id = instance_info.account_id + @property def is_instance(self) -> bool: return self._is_instance @@ -93,10 +94,10 @@ class AwsInstance(CloudInstance): return None def get_instance_id(self): - return self.instance_id + return self._instance_id def get_region(self): - return self.region + return self._region @staticmethod def _extract_account_id(instance_identity_document_response): @@ -116,4 +117,4 @@ class AwsInstance(CloudInstance): :return: the AWS account ID which "owns" this instance. See https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html """ - return self.account_id + return self._account_id diff --git a/monkey/infection_monkey/utils/aws_environment_check.py b/monkey/infection_monkey/utils/aws_environment_check.py index aa60e0a55..af84354e8 100644 --- a/monkey/infection_monkey/utils/aws_environment_check.py +++ b/monkey/infection_monkey/utils/aws_environment_check.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) def _running_on_aws(aws_instance: AwsInstance) -> bool: - return aws_instance.is_instance() + return aws_instance.is_instance def _report_aws_environment(telemetry_messenger: LegacyTelemetryMessengerAdapter): diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 7b2bdbf90..1e1ac5b78 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -49,14 +49,14 @@ class RemoteRunAwsService: @staticmethod def is_running_on_aws(): with aws_lock: - return RemoteRunAwsService.aws_instance.is_instance() + return RemoteRunAwsService.aws_instance.is_instance @staticmethod def update_aws_region_authless(): """ Updates the AWS region without auth params (via IAM role) """ - AwsService.set_region(RemoteRunAwsService.aws_instance.region) + AwsService.set_region(RemoteRunAwsService.aws_instance._region) @staticmethod def _run_aws_monkey_cmd_async(instance_id, is_linux, island_ip): diff --git a/monkey/monkey_island/monkey_island.spec b/monkey/monkey_island/monkey_island.spec index 80335d346..4e1ff825c 100644 --- a/monkey/monkey_island/monkey_island.spec +++ b/monkey/monkey_island/monkey_island.spec @@ -4,7 +4,6 @@ import platform import sys - block_cipher = None @@ -13,7 +12,8 @@ def main(): # The format of the tuples is (src, dest_dir). See https://pythonhosted.org/PyInstaller/spec-files.html#adding-data-files added_datas = [ ("../common/BUILD", "/common"), - ("../monkey_island/cc/setup/mongo/attack_mitigations.json", "/monkey_island/cc/setup/mongo/attack_mitigations.json") + ("../monkey_island/cc/setup/mongo/attack_mitigations.json", + "/monkey_island/cc/setup/mongo/attack_mitigations.json") ] a = Analysis(['main.py'], diff --git a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py index 04421920f..2981a7bd1 100644 --- a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py +++ b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py @@ -74,7 +74,7 @@ def good_data_mock_instance(): def test_is_instance_good_data(good_data_mock_instance): - assert good_data_mock_instance.is_instance() + assert good_data_mock_instance.is_instance def test_get_instance_id_good_data(good_data_mock_instance): @@ -102,7 +102,7 @@ def bad_region_data_mock_instance(): def test_is_instance_bad_region_data(bad_region_data_mock_instance): - assert bad_region_data_mock_instance.is_instance() + assert bad_region_data_mock_instance.is_instance def test_get_instance_id_bad_region_data(bad_region_data_mock_instance): @@ -130,7 +130,7 @@ def bad_account_id_data_mock_instance(): def test_is_instance_bad_account_id_data(bad_account_id_data_mock_instance): - assert bad_account_id_data_mock_instance.is_instance() + assert bad_account_id_data_mock_instance.is_instance def test_get_instance_id_bad_account_id_data(bad_account_id_data_mock_instance): @@ -160,7 +160,7 @@ def bad_instance_id_request_mock_instance(instance_id_exception): @pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) def test_is_instance_bad_instance_id_request(bad_instance_id_request_mock_instance): - assert bad_instance_id_request_mock_instance.is_instance() is False + assert bad_instance_id_request_mock_instance.is_instance is False @pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) @@ -193,7 +193,7 @@ def bad_region_request_mock_instance(region_exception): @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_is_instance_bad_region_request(bad_region_request_mock_instance): - assert bad_region_request_mock_instance.is_instance() + assert bad_region_request_mock_instance.is_instance @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) @@ -226,7 +226,7 @@ def bad_account_id_request_mock_instance(account_id_exception): @pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) def test_is_instance_bad_account_id_request(bad_account_id_request_mock_instance): - assert bad_account_id_request_mock_instance.is_instance() + assert bad_account_id_request_mock_instance.is_instance @pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) @@ -265,7 +265,7 @@ def not_found_request_mock_instance(): def test_is_instance_not_found_request(not_found_request_mock_instance): - assert not_found_request_mock_instance.is_instance() is False + assert not_found_request_mock_instance.is_instance is False def test_get_instance_id_not_found_request(not_found_request_mock_instance): From b58d847e2262aa187a1da3e0e0b2c2fa273e188d Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 28 Apr 2022 14:58:41 +0300 Subject: [PATCH 08/21] Island: Refactor lock to event in remote_run_aws.py --- monkey/monkey_island/cc/services/remote_run_aws.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 1e1ac5b78..3c0ecb9d2 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -1,5 +1,5 @@ import logging -from threading import Lock +from threading import Event from common.cloud.aws.aws_instance import AwsInstance from common.cloud.aws.aws_service import AwsService @@ -8,7 +8,8 @@ from common.cmd.cmd import Cmd from common.cmd.cmd_runner import CmdRunner logger = logging.getLogger(__name__) -aws_lock = Lock() +AWS_INFO_FETCH_TIMEOUT = 10 # Seconds +aws_info_fetch_done = Event() class RemoteRunAwsService: @@ -25,8 +26,8 @@ class RemoteRunAwsService: :return: None """ if RemoteRunAwsService.aws_instance is None: - with aws_lock: - RemoteRunAwsService.aws_instance = AwsInstance() + RemoteRunAwsService.aws_instance = AwsInstance() + aws_info_fetch_done.set() @staticmethod def run_aws_monkeys(instances, island_ip): @@ -48,8 +49,8 @@ class RemoteRunAwsService: @staticmethod def is_running_on_aws(): - with aws_lock: - return RemoteRunAwsService.aws_instance.is_instance + aws_info_fetch_done.wait(AWS_INFO_FETCH_TIMEOUT) + return RemoteRunAwsService.aws_instance.is_instance @staticmethod def update_aws_region_authless(): From 797482a172d2c9fc89b99a19a224440fe55f35d4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 28 Apr 2022 08:43:38 -0400 Subject: [PATCH 09/21] Common: Replace protected attributes with read-only properties --- monkey/common/cloud/aws/aws_instance.py | 26 +++--- monkey/common/cloud/aws/aws_service.py | 2 +- .../utils/aws_environment_check.py | 2 +- .../cc/services/remote_run_aws.py | 2 +- .../cc/services/reporting/aws_exporter.py | 2 +- .../common/cloud/aws/test_aws_instance.py | 84 +++++++++---------- 6 files changed, 60 insertions(+), 58 deletions(-) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index 8f00cc399..ced7d6ab5 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -33,16 +33,24 @@ class AwsInstance(CloudInstance): __metaclass__ = Singleton def __init__(self): - self._is_instance, instance_info = AwsInstance._fetch_instance_info() - - self._instance_id = instance_info.instance_id - self._region = instance_info.region - self._account_id = instance_info.account_id + self._is_instance, self._instance_info = AwsInstance._fetch_instance_info() @property def is_instance(self) -> bool: return self._is_instance + @property + def instance_id(self) -> str: + return self._instance_info.instance_id + + @property + def region(self) -> str: + return self._instance_info.region + + @property + def account_id(self) -> str: + return self._instance_info.account_id + @staticmethod def _fetch_instance_info() -> Tuple[bool, AwsInstanceInfo]: try: @@ -93,12 +101,6 @@ class AwsInstance(CloudInstance): else: return None - def get_instance_id(self): - return self._instance_id - - def get_region(self): - return self._region - @staticmethod def _extract_account_id(instance_identity_document_response): """ @@ -117,4 +119,4 @@ class AwsInstance(CloudInstance): :return: the AWS account ID which "owns" this instance. See https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html """ - return self._account_id + return self.account_id diff --git a/monkey/common/cloud/aws/aws_service.py b/monkey/common/cloud/aws/aws_service.py index 4a9ded280..3cacc1a8f 100644 --- a/monkey/common/cloud/aws/aws_service.py +++ b/monkey/common/cloud/aws/aws_service.py @@ -63,7 +63,7 @@ class AwsService(object): :return: All visible instances from this instance """ current_instance = AwsInstance() - local_ssm_client = boto3.client("ssm", current_instance.get_region()) + local_ssm_client = boto3.client("ssm", current_instance.region) try: response = local_ssm_client.describe_instance_information() diff --git a/monkey/infection_monkey/utils/aws_environment_check.py b/monkey/infection_monkey/utils/aws_environment_check.py index af84354e8..f508135ca 100644 --- a/monkey/infection_monkey/utils/aws_environment_check.py +++ b/monkey/infection_monkey/utils/aws_environment_check.py @@ -21,7 +21,7 @@ def _report_aws_environment(telemetry_messenger: LegacyTelemetryMessengerAdapter if _running_on_aws(aws_instance): logger.info("Machine is an AWS instance") - telemetry_messenger.send_telemetry(AWSInstanceTelemetry(aws_instance.get_instance_id())) + telemetry_messenger.send_telemetry(AWSInstanceTelemetry(aws_instance.instance_id)) else: logger.info("Machine is NOT an AWS instance") diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 3c0ecb9d2..26ec58e5c 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -57,7 +57,7 @@ class RemoteRunAwsService: """ Updates the AWS region without auth params (via IAM role) """ - AwsService.set_region(RemoteRunAwsService.aws_instance._region) + AwsService.set_region(RemoteRunAwsService.aws_instance.region) @staticmethod def _run_aws_monkey_cmd_async(instance_id, is_linux, island_ip): diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 7c6d0903d..ce3f6a953 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -36,7 +36,7 @@ class AWSExporter(Exporter): return True # Not suppressing error here on purpose. - current_aws_region = AwsInstance().get_region() + current_aws_region = AwsInstance().region for machine in issues_list: for issue in issues_list[machine]: diff --git a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py index 2981a7bd1..54d8e942a 100644 --- a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py +++ b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py @@ -77,16 +77,16 @@ def test_is_instance_good_data(good_data_mock_instance): assert good_data_mock_instance.is_instance -def test_get_instance_id_good_data(good_data_mock_instance): - assert good_data_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID +def test_instance_id_good_data(good_data_mock_instance): + assert good_data_mock_instance.instance_id == EXPECTED_INSTANCE_ID -def test_get_region_good_data(good_data_mock_instance): - assert good_data_mock_instance.get_region() == EXPECTED_REGION +def test_region_good_data(good_data_mock_instance): + assert good_data_mock_instance.region == EXPECTED_REGION -def test_get_account_id_good_data(good_data_mock_instance): - assert good_data_mock_instance.get_account_id() == EXPECTED_ACCOUNT_ID +def test_account_id_good_data(good_data_mock_instance): + assert good_data_mock_instance.account_id == EXPECTED_ACCOUNT_ID # 'region' bad data @@ -105,16 +105,16 @@ def test_is_instance_bad_region_data(bad_region_data_mock_instance): assert bad_region_data_mock_instance.is_instance -def test_get_instance_id_bad_region_data(bad_region_data_mock_instance): - assert bad_region_data_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID +def test_instance_id_bad_region_data(bad_region_data_mock_instance): + assert bad_region_data_mock_instance.instance_id == EXPECTED_INSTANCE_ID -def test_get_region_bad_region_data(bad_region_data_mock_instance): - assert bad_region_data_mock_instance.get_region() is None +def test_region_bad_region_data(bad_region_data_mock_instance): + assert bad_region_data_mock_instance.region is None -def test_get_account_id_bad_region_data(bad_region_data_mock_instance): - assert bad_region_data_mock_instance.get_account_id() == EXPECTED_ACCOUNT_ID +def test_account_id_bad_region_data(bad_region_data_mock_instance): + assert bad_region_data_mock_instance.account_id == EXPECTED_ACCOUNT_ID # 'account_id' bad data @@ -133,16 +133,16 @@ def test_is_instance_bad_account_id_data(bad_account_id_data_mock_instance): assert bad_account_id_data_mock_instance.is_instance -def test_get_instance_id_bad_account_id_data(bad_account_id_data_mock_instance): - assert bad_account_id_data_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID +def test_instance_id_bad_account_id_data(bad_account_id_data_mock_instance): + assert bad_account_id_data_mock_instance.instance_id == EXPECTED_INSTANCE_ID -def test_get_region_bad_account_id_data(bad_account_id_data_mock_instance): - assert bad_account_id_data_mock_instance.get_region() == EXPECTED_REGION +def test_region_bad_account_id_data(bad_account_id_data_mock_instance): + assert bad_account_id_data_mock_instance.region == EXPECTED_REGION -def test_get_account_id_data_bad_account_id_data(bad_account_id_data_mock_instance): - assert bad_account_id_data_mock_instance.get_account_id() is None +def test_account_id_data_bad_account_id_data(bad_account_id_data_mock_instance): + assert bad_account_id_data_mock_instance.account_id is None # 'instance_id' bad requests @@ -164,18 +164,18 @@ def test_is_instance_bad_instance_id_request(bad_instance_id_request_mock_instan @pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) -def test_get_instance_id_bad_instance_id_request(bad_instance_id_request_mock_instance): - assert bad_instance_id_request_mock_instance.get_instance_id() is None +def test_instance_id_bad_instance_id_request(bad_instance_id_request_mock_instance): + assert bad_instance_id_request_mock_instance.instance_id is None @pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) -def test_get_region_bad_instance_id_request(bad_instance_id_request_mock_instance): - assert bad_instance_id_request_mock_instance.get_region() is None +def test_region_bad_instance_id_request(bad_instance_id_request_mock_instance): + assert bad_instance_id_request_mock_instance.region is None @pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) -def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_instance): - assert bad_instance_id_request_mock_instance.get_account_id() == EXPECTED_ACCOUNT_ID +def test_account_id_bad_instance_id_request(bad_instance_id_request_mock_instance): + assert bad_instance_id_request_mock_instance.account_id == EXPECTED_ACCOUNT_ID # 'region' bad requests @@ -197,18 +197,18 @@ def test_is_instance_bad_region_request(bad_region_request_mock_instance): @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) -def test_get_instance_id_bad_region_request(bad_region_request_mock_instance): - assert bad_region_request_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID +def test_instance_id_bad_region_request(bad_region_request_mock_instance): + assert bad_region_request_mock_instance.instance_id == EXPECTED_INSTANCE_ID @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) -def test_get_region_bad_region_request(bad_region_request_mock_instance): - assert bad_region_request_mock_instance.get_region() is None +def test_region_bad_region_request(bad_region_request_mock_instance): + assert bad_region_request_mock_instance.region is None @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) -def test_get_account_id_bad_region_request(bad_region_request_mock_instance): - assert bad_region_request_mock_instance.get_account_id() == EXPECTED_ACCOUNT_ID +def test_account_id_bad_region_request(bad_region_request_mock_instance): + assert bad_region_request_mock_instance.account_id == EXPECTED_ACCOUNT_ID # 'account_id' bad requests @@ -230,18 +230,18 @@ def test_is_instance_bad_account_id_request(bad_account_id_request_mock_instance @pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) -def test_get_instance_id_bad_account_id_request(bad_account_id_request_mock_instance): - assert bad_account_id_request_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID +def test_instance_id_bad_account_id_request(bad_account_id_request_mock_instance): + assert bad_account_id_request_mock_instance.instance_id == EXPECTED_INSTANCE_ID @pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) -def test_get_region_bad_account_id_request(bad_account_id_request_mock_instance): - assert bad_account_id_request_mock_instance.get_region() == EXPECTED_REGION +def test_region_bad_account_id_request(bad_account_id_request_mock_instance): + assert bad_account_id_request_mock_instance.region == EXPECTED_REGION @pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) -def test_get_account_id_bad_account_id_request(bad_account_id_request_mock_instance): - assert bad_account_id_request_mock_instance.get_account_id() is None +def test_account_id_bad_account_id_request(bad_account_id_request_mock_instance): + assert bad_account_id_request_mock_instance.account_id is None # not found request @@ -268,13 +268,13 @@ def test_is_instance_not_found_request(not_found_request_mock_instance): assert not_found_request_mock_instance.is_instance is False -def test_get_instance_id_not_found_request(not_found_request_mock_instance): - assert not_found_request_mock_instance.get_instance_id() is None +def test_instance_id_not_found_request(not_found_request_mock_instance): + assert not_found_request_mock_instance.instance_id is None -def test_get_region_not_found_request(not_found_request_mock_instance): - assert not_found_request_mock_instance.get_region() is None +def test_region_not_found_request(not_found_request_mock_instance): + assert not_found_request_mock_instance.region is None -def test_get_account_id_not_found_request(not_found_request_mock_instance): - assert not_found_request_mock_instance.get_account_id() is None +def test_account_id_not_found_request(not_found_request_mock_instance): + assert not_found_request_mock_instance.account_id is None From cea49b6d509936d587ad643e28ee4a335dea6585 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 28 Apr 2022 16:09:57 +0300 Subject: [PATCH 10/21] Docs: Improve running on AWS ec2 documentation Update the IAM role change screenshot and add a note about firewall rules --- .../integrations/aws-run-on-ec2-machine.md | 4 ++++ .../monkey-island-aws-screenshot-4.png | Bin 71749 -> 55221 bytes 2 files changed, 4 insertions(+) diff --git a/docs/content/usage/integrations/aws-run-on-ec2-machine.md b/docs/content/usage/integrations/aws-run-on-ec2-machine.md index 2e6492bf6..0fb6be6dd 100644 --- a/docs/content/usage/integrations/aws-run-on-ec2-machine.md +++ b/docs/content/usage/integrations/aws-run-on-ec2-machine.md @@ -50,6 +50,10 @@ If your EC2 instances don't have the _SSM agent_ installed, they will not be abl See [Amazon's documentation about working with SSM agents](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html) for more details on how to check if you have an SSM agent and how to manually install one if you don't yet have it. +### Firewall rules + +Make sure that all machines that will run the Monkey agent can access the Island(port 5000). + ## Usage ### Running the Infection Monkey diff --git a/docs/static/images/usage/integrations/monkey-island-aws-screenshot-4.png b/docs/static/images/usage/integrations/monkey-island-aws-screenshot-4.png index 58117738c0d2f75081880e0cf954e5a37d214577..6171947fac3226a3807bf80196b0fb84031ff3c8 100644 GIT binary patch literal 55221 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⪅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 literal 71749 zcmcG$cTiJZ*YFL}L?Eb0Zz777-n$S5m8Kwqfb`ybCrFW~AVrYgBGROZ^j@V(5_%`J z&>{4a5b_=Ly080r=9_u{dFP!8BPVmt-s`Nr*4k_R)|L;?)s<;3v0Ne|BBFWvYofgbcW*bbj*MjfjZh z*7*20{tKpFQ& zJ8h*;zeMLqf4yNCh4aU!Ph7v(RTz8`Ue=PgV~2d@8(dlE5RmuH`77BYd-5c%my(xX z#tDAC0DE-dBBd77=a<>h(JvJg6}3B_X>xrNddRek&uusg%qT+?`KeETuhl*v6g8+H zPFrCJGq@=?#K|x`U*B&t@EH2w^-uHe&$m?KfnQz*3PCIXy)w@?_`kViDFFq{O3S%U z(DK#qMIFgj9h}IvV`*3e1NJacXI-e~nKsl>J{kKPFGRpLKI2#)wV1|<{e9XgwKPN6 zz8xvu&)A55Pir*btG)iE3ES>+ySWz}dFDO&+b;d}Fcq;;z`^jlw7}Y3#?e6MHAeq{ zKdel^9ft_7h7XStIvCITPy}g4$c8Y% zdHV$?#okgvvOL@mIa>8!71-2E?SC*jS*j>=Jo~*vAU_G2IVmMzFpciNG)gSdva779 zkk*vR<%f_nGxo-A-lnG5`!c2Fos5RwCtsfL?tlCeEx!&;07DKY4l?kaCC$CuGlcvm zLe=WB!4Nis(O_;}|G)Ydfu%#w24knMx9H$)85Ucu4ph3-w+H$sP!YfJR(C zeM$Xp4-Avs`i@qvp6EM%<-6T=LIEp;;spA|^M4E$r$N^Pb86Z%jGjf!U@5wq4yH54 z8vMlTW#)QOZxb;1V=JyeL;&p@>2=F4{~ZE^>JqKYZr1hj?a!lS8uB>PE)pyJ_RqE% z59&DG|H>Gru`mXs4MgSgCA*+ECNU8Axp`m_X=Q&pp9mK;zYUbefr`8snO+B*JO_T{BJHD730`LTwEVt19gJY(e^hkqy) zpgf1|-HfK)z62wP&q5Ayw3Cs1tlVKz;M-&(bD_FqX?!KgW9wlDqiL#RU)(+aVLue@ z&*6JH;te|`*Ea$K7yN|Kbi$?JlG@F6?+5QH$=n}d*q86`h|2{Hj{QWJa(34nTSmFO zz~Dbg>GPNes~{&1SdZstOO2GzLr?b8FCE$7CQKq?M=d19Il@NTkQ_L|IUPt|oH%~id`QLcKzYxYT{=uZ{JE&d5?Ok>MPvIpsR+8=y8>CXU zE^3ym&Wujgc7X63vM8N*R#Q*q;~$(=#7QudeLXlk7W`oCB0fP6Qa>DXHTPwlD#opT za`tdfU3s#Ldwh*;&WN`otze?*ESbcA?n=qY72SO^2iHpH=-XTIXj^8h7BfZ7@xFJc z06EU7V&}~LlS~9%K#!;scE#%KjJ|f_!HW0kc%x{v30&rAKvnHeWD|KY=p7KB5OH7a*BwI}Vl5!zaWaPnKPlW!T;4NgMf2MK6J$j#(A{*|};M+7l; z$B(_f@kU4M`i{9`r!dhHJ`{Wu*u6XK%%EoVpEx9MP%*};Vj`J#URt7j(6c0Emz8g_8+2D6n1(%2l zU>U1%8_l;$C!`4nb_EIy|Hsk|*|EZ%1Xe1(z1?1ZBkA>}sDLF-FNE&&sJ&s48VOucvHn`Fq_F-fW>6&Nc z)(nL_mLIK_IE_4NVa12ke9Lw@o#KW!@swC}{<=o6ZwtP^RrfuOXGfkE5?epLn}{?t zLYGlX7j?pJMf#bB!YPWEE?hm3=edc&Tc^{y=#?K+Rwtg$PoymR&Jk6Gan1^V;XU8- z2Od3J7>02*Y&K{$9To}mDX=3ju zd|C5TPBRd)G22bn=kEQv$yL#$}1-Pv%j8&~HZS!54tavC9zB$@Otz=dA zMrWU_4X^yiJJ5KFCjvH4KWW;xDQ?O7A`mugXX4yP5Oz3{_o^M#aNkXAX=t)9;02{l zP6nrQg5fe;{dLaMz4?r~MRB7fA;e+C;Yqa0ve6;k6)R=sQM!!NDMsPpKdIjUsmc

keDV#1y|ZboXXN{E>_B_USHGJifsd}# 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

  • Date: Thu, 28 Apr 2022 17:56:30 +0300 Subject: [PATCH 11/21] Common: Improve singleton to use WeakValueDictionary --- monkey/common/utils/code_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index f4060870b..fce8453bf 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,5 +1,6 @@ # abstract, static method decorator # noinspection PyPep8Naming +from weakref import WeakValueDictionary class abstractstatic(staticmethod): @@ -13,7 +14,7 @@ class abstractstatic(staticmethod): class Singleton(type): - _instances = {} + _instances = WeakValueDictionary() def __call__(cls, *args, **kwargs): if cls not in cls._instances: From c5a126ff13c39a7b011bcd439e045b20f77b5b89 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 28 Apr 2022 17:58:20 +0300 Subject: [PATCH 12/21] UT: Fix AWS instance unit tests --- .../common/cloud/aws/test_aws_instance.py | 106 +++++------------- 1 file changed, 27 insertions(+), 79 deletions(-) diff --git a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py index 54d8e942a..30cc0ce08 100644 --- a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py +++ b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py @@ -37,9 +37,14 @@ EXPECTED_ACCOUNT_ID = "123456789012" def get_test_aws_instance( - text={"instance_id": None, "region": None, "account_id": None}, - exception={"instance_id": None, "region": None, "account_id": None}, + text=None, + exception=None, ): + if text is None: + text = {"instance_id": None, "region": None, "account_id": None} + if exception is None: + exception = {"instance_id": None, "region": None, "account_id": None} + with requests_mock.Mocker() as m: # request made to get instance_id url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id" @@ -64,13 +69,15 @@ def get_test_aws_instance( # all good data @pytest.fixture def good_data_mock_instance(): - return get_test_aws_instance( + instance = get_test_aws_instance( text={ "instance_id": INSTANCE_ID_RESPONSE, "region": AVAILABILITY_ZONE_RESPONSE, "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, } ) + yield instance + del instance def test_is_instance_good_data(good_data_mock_instance): @@ -92,13 +99,15 @@ def test_account_id_good_data(good_data_mock_instance): # 'region' bad data @pytest.fixture def bad_region_data_mock_instance(): - return get_test_aws_instance( + instance = get_test_aws_instance( text={ "instance_id": INSTANCE_ID_RESPONSE, "region": "in-a-different-world", "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, } ) + yield instance + del instance def test_is_instance_bad_region_data(bad_region_data_mock_instance): @@ -120,68 +129,37 @@ def test_account_id_bad_region_data(bad_region_data_mock_instance): # 'account_id' bad data @pytest.fixture def bad_account_id_data_mock_instance(): - return get_test_aws_instance( + instance = get_test_aws_instance( text={ "instance_id": INSTANCE_ID_RESPONSE, "region": AVAILABILITY_ZONE_RESPONSE, "account_id": "who-am-i", } ) + yield instance + del instance def test_is_instance_bad_account_id_data(bad_account_id_data_mock_instance): - assert bad_account_id_data_mock_instance.is_instance + assert not bad_account_id_data_mock_instance.is_instance def test_instance_id_bad_account_id_data(bad_account_id_data_mock_instance): - assert bad_account_id_data_mock_instance.instance_id == EXPECTED_INSTANCE_ID + assert bad_account_id_data_mock_instance.instance_id is None def test_region_bad_account_id_data(bad_account_id_data_mock_instance): - assert bad_account_id_data_mock_instance.region == EXPECTED_REGION + assert bad_account_id_data_mock_instance.region is None def test_account_id_data_bad_account_id_data(bad_account_id_data_mock_instance): assert bad_account_id_data_mock_instance.account_id is None -# 'instance_id' bad requests -@pytest.fixture -def bad_instance_id_request_mock_instance(instance_id_exception): - return get_test_aws_instance( - text={ - "instance_id": None, - "region": AVAILABILITY_ZONE_RESPONSE, - "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - }, - exception={"instance_id": instance_id_exception, "region": None, "account_id": None}, - ) - - -@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) -def test_is_instance_bad_instance_id_request(bad_instance_id_request_mock_instance): - assert bad_instance_id_request_mock_instance.is_instance is False - - -@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) -def test_instance_id_bad_instance_id_request(bad_instance_id_request_mock_instance): - assert bad_instance_id_request_mock_instance.instance_id is None - - -@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) -def test_region_bad_instance_id_request(bad_instance_id_request_mock_instance): - assert bad_instance_id_request_mock_instance.region is None - - -@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) -def test_account_id_bad_instance_id_request(bad_instance_id_request_mock_instance): - assert bad_instance_id_request_mock_instance.account_id == EXPECTED_ACCOUNT_ID - - # 'region' bad requests @pytest.fixture def bad_region_request_mock_instance(region_exception): - return get_test_aws_instance( + instance = get_test_aws_instance( text={ "instance_id": INSTANCE_ID_RESPONSE, "region": None, @@ -189,16 +167,18 @@ def bad_region_request_mock_instance(region_exception): }, exception={"instance_id": None, "region": region_exception, "account_id": None}, ) + yield instance + del instance @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_is_instance_bad_region_request(bad_region_request_mock_instance): - assert bad_region_request_mock_instance.is_instance + assert not bad_region_request_mock_instance.is_instance @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_instance_id_bad_region_request(bad_region_request_mock_instance): - assert bad_region_request_mock_instance.instance_id == EXPECTED_INSTANCE_ID + assert bad_region_request_mock_instance.instance_id is None @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) @@ -208,40 +188,7 @@ def test_region_bad_region_request(bad_region_request_mock_instance): @pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_account_id_bad_region_request(bad_region_request_mock_instance): - assert bad_region_request_mock_instance.account_id == EXPECTED_ACCOUNT_ID - - -# 'account_id' bad requests -@pytest.fixture -def bad_account_id_request_mock_instance(account_id_exception): - return get_test_aws_instance( - text={ - "instance_id": INSTANCE_ID_RESPONSE, - "region": AVAILABILITY_ZONE_RESPONSE, - "account_id": None, - }, - exception={"instance_id": None, "region": None, "account_id": account_id_exception}, - ) - - -@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) -def test_is_instance_bad_account_id_request(bad_account_id_request_mock_instance): - assert bad_account_id_request_mock_instance.is_instance - - -@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) -def test_instance_id_bad_account_id_request(bad_account_id_request_mock_instance): - assert bad_account_id_request_mock_instance.instance_id == EXPECTED_INSTANCE_ID - - -@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) -def test_region_bad_account_id_request(bad_account_id_request_mock_instance): - assert bad_account_id_request_mock_instance.region == EXPECTED_REGION - - -@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) -def test_account_id_bad_account_id_request(bad_account_id_request_mock_instance): - assert bad_account_id_request_mock_instance.account_id is None + assert bad_region_request_mock_instance.account_id is None # not found request @@ -261,7 +208,8 @@ def not_found_request_mock_instance(): m.get(url) not_found_aws_instance_object = AwsInstance() - return not_found_aws_instance_object + yield not_found_aws_instance_object + del not_found_aws_instance_object def test_is_instance_not_found_request(not_found_request_mock_instance): From 7b2ff1e159d7b1c23da6a5dcd5ec15c1f44326c7 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 2 May 2022 11:05:18 +0300 Subject: [PATCH 13/21] Common: Remove CloudInstance since aws is the only cloud supported This change simplifies the codebase by removing unnecessary inheritance and nested directory structure --- monkey/common/{cloud => aws}/__init__.py | 0 monkey/common/{cloud => }/aws/aws_instance.py | 3 +-- monkey/common/{cloud => }/aws/aws_service.py | 2 +- monkey/common/cloud/aws/__init__.py | 0 monkey/common/cloud/instance.py | 9 --------- monkey/common/cmd/aws/aws_cmd_runner.py | 2 +- monkey/infection_monkey/utils/aws_environment_check.py | 2 +- monkey/monkey_island/cc/resources/remote_run.py | 2 +- monkey/monkey_island/cc/services/remote_run_aws.py | 4 ++-- .../monkey_island/cc/services/reporting/aws_exporter.py | 2 +- .../unit_tests/common/cloud/aws/test_aws_instance.py | 2 +- .../unit_tests/common/cloud/aws/test_aws_service.py | 2 +- 12 files changed, 10 insertions(+), 20 deletions(-) rename monkey/common/{cloud => aws}/__init__.py (100%) rename monkey/common/{cloud => }/aws/aws_instance.py (97%) rename monkey/common/{cloud => }/aws/aws_service.py (97%) delete mode 100644 monkey/common/cloud/aws/__init__.py delete mode 100644 monkey/common/cloud/instance.py diff --git a/monkey/common/cloud/__init__.py b/monkey/common/aws/__init__.py similarity index 100% rename from monkey/common/cloud/__init__.py rename to monkey/common/aws/__init__.py diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/aws/aws_instance.py similarity index 97% rename from monkey/common/cloud/aws/aws_instance.py rename to monkey/common/aws/aws_instance.py index ced7d6ab5..9f7b81180 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/aws/aws_instance.py @@ -6,7 +6,6 @@ from typing import Optional, Tuple import requests -from common.cloud.instance import CloudInstance from common.utils.code_utils import Singleton AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254" @@ -25,7 +24,7 @@ class AwsInstanceInfo: account_id: Optional[str] = None -class AwsInstance(CloudInstance): +class AwsInstance: """ Class which gives useful information about the current instance you're on. """ diff --git a/monkey/common/cloud/aws/aws_service.py b/monkey/common/aws/aws_service.py similarity index 97% rename from monkey/common/cloud/aws/aws_service.py rename to monkey/common/aws/aws_service.py index 3cacc1a8f..84710f839 100644 --- a/monkey/common/cloud/aws/aws_service.py +++ b/monkey/common/aws/aws_service.py @@ -3,7 +3,7 @@ import logging import boto3 import botocore -from common.cloud.aws.aws_instance import AwsInstance +from common.aws.aws_instance import AwsInstance INSTANCE_INFORMATION_LIST_KEY = "InstanceInformationList" INSTANCE_ID_KEY = "InstanceId" diff --git a/monkey/common/cloud/aws/__init__.py b/monkey/common/cloud/aws/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/monkey/common/cloud/instance.py b/monkey/common/cloud/instance.py deleted file mode 100644 index 77376ee8e..000000000 --- a/monkey/common/cloud/instance.py +++ /dev/null @@ -1,9 +0,0 @@ -class CloudInstance(object): - """ - This is an abstract class which represents a cloud instance. - - The current machine can be a cloud instance (for example EC2 instance or Azure VM). - """ - - def is_instance(self) -> bool: - raise NotImplementedError() diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py index c1c65ecb9..03d4e9e4f 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -1,7 +1,7 @@ import logging import time -from common.cloud.aws.aws_service import AwsService +from common.aws.aws_service import AwsService from common.cmd.aws.aws_cmd_result import AwsCmdResult from common.cmd.cmd_runner import CmdRunner from common.cmd.cmd_status import CmdStatus diff --git a/monkey/infection_monkey/utils/aws_environment_check.py b/monkey/infection_monkey/utils/aws_environment_check.py index f508135ca..6bdbb5c85 100644 --- a/monkey/infection_monkey/utils/aws_environment_check.py +++ b/monkey/infection_monkey/utils/aws_environment_check.py @@ -1,6 +1,6 @@ import logging -from common.cloud.aws.aws_instance import AwsInstance +from common.aws.aws_instance import AwsInstance from infection_monkey.telemetry.aws_instance_telem import AWSInstanceTelemetry from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( LegacyTelemetryMessengerAdapter, diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index 0e6e6df10..864fb4848 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -4,7 +4,7 @@ import flask_restful from botocore.exceptions import ClientError, NoCredentialsError from flask import jsonify, make_response, request -from common.cloud.aws.aws_service import AwsService +from common.aws.aws_service import AwsService from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 26ec58e5c..dd15bff07 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -1,8 +1,8 @@ import logging from threading import Event -from common.cloud.aws.aws_instance import AwsInstance -from common.cloud.aws.aws_service import AwsService +from common.aws.aws_instance import AwsInstance +from common.aws.aws_service import AwsService from common.cmd.aws.aws_cmd_runner import AwsCmdRunner from common.cmd.cmd import Cmd from common.cmd.cmd_runner import CmdRunner diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index ce3f6a953..ed8925985 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -5,7 +5,7 @@ from datetime import datetime import boto3 from botocore.exceptions import UnknownServiceError -from common.cloud.aws.aws_instance import AwsInstance +from common.aws.aws_instance import AwsInstance from monkey_island.cc.services.reporting.exporter import Exporter __authors__ = ["maor.rayzin", "shay.nehmad"] diff --git a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py index 30cc0ce08..d36c92ac0 100644 --- a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py +++ b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py @@ -2,7 +2,7 @@ import pytest import requests import requests_mock -from common.cloud.aws.aws_instance import AWS_LATEST_METADATA_URI_PREFIX, AwsInstance +from common.aws.aws_instance import AWS_LATEST_METADATA_URI_PREFIX, AwsInstance INSTANCE_ID_RESPONSE = "i-1234567890abcdef0" diff --git a/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py index dc5ec3831..f66646b34 100644 --- a/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py +++ b/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py @@ -1,7 +1,7 @@ import json from unittest import TestCase -from common.cloud.aws.aws_service import filter_instance_data_from_aws_response +from common.aws.aws_service import filter_instance_data_from_aws_response class TestAwsService(TestCase): From f3a5a7090b60b20df3e78b3f7421b3ada666f537 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 2 May 2022 14:51:06 +0300 Subject: [PATCH 14/21] Agent, Island, Common: Refactor AwsService from class to package This also changes AwsInstance from singleton and instead the aws_service package is used as one --- monkey/common/aws/aws_instance.py | 4 - monkey/common/aws/aws_service.py | 104 +++++++++++------- monkey/common/cmd/aws/aws_cmd_runner.py | 4 +- .../utils/aws_environment_check.py | 10 +- monkey/monkey_island/cc/app.py | 6 - .../monkey_island/cc/resources/remote_run.py | 11 +- .../monkey_island/cc/services/initialize.py | 5 + .../cc/services/remote_run_aws.py | 33 ------ .../cc/services/reporting/aws_exporter.py | 4 +- .../cc/services/reporting/exporter_init.py | 5 +- 10 files changed, 79 insertions(+), 107 deletions(-) diff --git a/monkey/common/aws/aws_instance.py b/monkey/common/aws/aws_instance.py index 9f7b81180..d99c87117 100644 --- a/monkey/common/aws/aws_instance.py +++ b/monkey/common/aws/aws_instance.py @@ -6,8 +6,6 @@ from typing import Optional, Tuple import requests -from common.utils.code_utils import Singleton - AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254" AWS_LATEST_METADATA_URI_PREFIX = "http://{0}/latest/".format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS) ACCOUNT_ID_KEY = "accountId" @@ -29,8 +27,6 @@ class AwsInstance: Class which gives useful information about the current instance you're on. """ - __metaclass__ = Singleton - def __init__(self): self._is_instance, self._instance_info = AwsInstance._fetch_instance_info() diff --git a/monkey/common/aws/aws_service.py b/monkey/common/aws/aws_service.py index 84710f839..3198d6e0f 100644 --- a/monkey/common/aws/aws_service.py +++ b/monkey/common/aws/aws_service.py @@ -1,4 +1,7 @@ import logging +from functools import wraps +from threading import Event +from typing import Callable, Optional import boto3 import botocore @@ -26,49 +29,66 @@ def filter_instance_data_from_aws_response(response): ] -class AwsService(object): +aws_instance: Optional[AwsInstance] = None +AWS_INFO_FETCH_TIMEOUT = 10.0 # Seconds +init_done = Event() + + +def initialize(): + global aws_instance + aws_instance = AwsInstance() + init_done.set() + + +def wait_init_done(fnc: Callable): + @wraps(fnc) + def inner(): + awaited = init_done.wait(AWS_INFO_FETCH_TIMEOUT) + if not awaited: + logger.error( + f"AWS service couldn't initialize in time! " + f"Current timeout is {AWS_INFO_FETCH_TIMEOUT}, " + f"but AWS info took longer to fetch from metadata server." + ) + return + fnc() + + return inner + + +@wait_init_done +def is_on_aws(): + return aws_instance.is_instance + + +@wait_init_done +def get_region(): + return aws_instance.region + + +@wait_init_done +def get_client(client_type): + return boto3.client(client_type, region_name=aws_instance.region) + + +@wait_init_done +def get_instances(): """ - A wrapper class around the boto3 client and session modules, which supplies various AWS - services. + Get the information for all instances with the relevant roles. - This class will assume: - 1. That it's running on an EC2 instance - 2. That the instance is associated with the correct IAM role. See - https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role - for details. + This function will assume that it's running on an EC2 instance with the correct IAM role. + See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam + -role for details. + + :raises: botocore.exceptions.ClientError if can't describe local instance information. + :return: All visible instances from this instance """ + local_ssm_client = boto3.client("ssm", aws_instance.region) + try: + response = local_ssm_client.describe_instance_information() - region = None - - @staticmethod - def set_region(region): - AwsService.region = region - - @staticmethod - def get_client(client_type, region=None): - return boto3.client( - client_type, region_name=region if region is not None else AwsService.region - ) - - @staticmethod - def get_instances(): - """ - Get the information for all instances with the relevant roles. - - This function will assume that it's running on an EC2 instance with the correct IAM role. - See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam - -role for details. - - :raises: botocore.exceptions.ClientError if can't describe local instance information. - :return: All visible instances from this instance - """ - current_instance = AwsInstance() - local_ssm_client = boto3.client("ssm", current_instance.region) - try: - response = local_ssm_client.describe_instance_information() - - filtered_instances_data = filter_instance_data_from_aws_response(response) - return filtered_instances_data - except botocore.exceptions.ClientError as e: - logger.warning("AWS client error while trying to get instances: " + e) - raise e + filtered_instances_data = filter_instance_data_from_aws_response(response) + return filtered_instances_data + except botocore.exceptions.ClientError as e: + logger.warning("AWS client error while trying to get instances: " + e) + raise e diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py index 03d4e9e4f..1e00c6b35 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -1,7 +1,7 @@ import logging import time -from common.aws.aws_service import AwsService +from common.aws import aws_service from common.cmd.aws.aws_cmd_result import AwsCmdResult from common.cmd.cmd_runner import CmdRunner from common.cmd.cmd_status import CmdStatus @@ -18,7 +18,7 @@ class AwsCmdRunner(CmdRunner): super(AwsCmdRunner, self).__init__(is_linux) self.instance_id = instance_id self.region = region - self.ssm = AwsService.get_client("ssm", region) + self.ssm = aws_service.get_client("ssm", region) def query_command(self, command_id): time.sleep(2) diff --git a/monkey/infection_monkey/utils/aws_environment_check.py b/monkey/infection_monkey/utils/aws_environment_check.py index 6bdbb5c85..fe7316e57 100644 --- a/monkey/infection_monkey/utils/aws_environment_check.py +++ b/monkey/infection_monkey/utils/aws_environment_check.py @@ -1,6 +1,6 @@ import logging -from common.aws.aws_instance import AwsInstance +from common.aws import aws_service from infection_monkey.telemetry.aws_instance_telem import AWSInstanceTelemetry from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( LegacyTelemetryMessengerAdapter, @@ -10,16 +10,12 @@ from infection_monkey.utils.threading import create_daemon_thread logger = logging.getLogger(__name__) -def _running_on_aws(aws_instance: AwsInstance) -> bool: - return aws_instance.is_instance - - def _report_aws_environment(telemetry_messenger: LegacyTelemetryMessengerAdapter): logger.info("Collecting AWS info") - aws_instance = AwsInstance() + aws_instance = aws_service.initialize() - if _running_on_aws(aws_instance): + if aws_service.is_on_aws(): logger.info("Machine is an AWS instance") telemetry_messenger.send_telemetry(AWSInstanceTelemetry(aws_instance.instance_id)) else: diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 97f7dfca1..60cc0a026 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -1,7 +1,6 @@ import os import uuid from datetime import timedelta -from threading import Thread from typing import Type import flask_restful @@ -49,7 +48,6 @@ from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFinding from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder -from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.services.representations import output_json HOME_FILE = "index.html" @@ -104,10 +102,6 @@ def init_app_services(app): with app.app_context(): database.init() - # If running on AWS, this will initialize the instance data, which is used "later" in the - # execution of the island. Run on a daemon thread since it's slow. - Thread(target=RemoteRunAwsService.init, name="AWS check thread", daemon=True).start() - def init_app_url_rules(app): app.add_url_rule("/", "serve_home", serve_home) diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index 864fb4848..dd9a4eaf6 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -4,7 +4,7 @@ import flask_restful from botocore.exceptions import ClientError, NoCredentialsError from flask import jsonify, make_response, request -from common.aws.aws_service import AwsService +from common.aws import aws_service from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService @@ -19,10 +19,6 @@ NO_CREDS_ERROR_FORMAT = ( class RemoteRun(flask_restful.Resource): - def __init__(self): - super(RemoteRun, self).__init__() - RemoteRunAwsService.init() - def run_aws_monkeys(self, request_body): instances = request_body.get("instances") island_ip = request_body.get("island_ip") @@ -32,11 +28,11 @@ class RemoteRun(flask_restful.Resource): def get(self): action = request.args.get("action") if action == "list_aws": - is_aws = RemoteRunAwsService.is_running_on_aws() + is_aws = aws_service.is_on_aws() resp = {"is_aws": is_aws} if is_aws: try: - resp["instances"] = AwsService.get_instances() + resp["instances"] = aws_service.get_instances() except NoCredentialsError as e: resp["error"] = NO_CREDS_ERROR_FORMAT.format(e) return jsonify(resp) @@ -52,7 +48,6 @@ class RemoteRun(flask_restful.Resource): body = json.loads(request.data) resp = {} if body.get("type") == "aws": - RemoteRunAwsService.update_aws_region_authless() result = self.run_aws_monkeys(body) resp["result"] = result return jsonify(resp) diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 4a4b2e4af..06b2473f8 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -1,6 +1,8 @@ from pathlib import Path +from threading import Thread from common import DIContainer +from common.aws import aws_service from monkey_island.cc.services import DirectoryFileStorageService, IFileStorageService from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService @@ -14,6 +16,9 @@ def initialize_services(data_dir: Path) -> DIContainer: IFileStorageService, DirectoryFileStorageService(data_dir / "custom_pbas") ) + # Takes a while so it's best to start it in the background + Thread(target=aws_service.initialize, name="AwsService initialization", daemon=True).start() + # This is temporary until we get DI all worked out. PostBreachFilesService.initialize(container.resolve(IFileStorageService)) LocalMonkeyRunService.initialize(data_dir) diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index dd15bff07..3dbb15477 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -1,34 +1,13 @@ import logging -from threading import Event -from common.aws.aws_instance import AwsInstance -from common.aws.aws_service import AwsService from common.cmd.aws.aws_cmd_runner import AwsCmdRunner from common.cmd.cmd import Cmd from common.cmd.cmd_runner import CmdRunner logger = logging.getLogger(__name__) -AWS_INFO_FETCH_TIMEOUT = 10 # Seconds -aws_info_fetch_done = Event() class RemoteRunAwsService: - aws_instance = None - - def __init__(self): - pass - - @staticmethod - def init(): - """ - Initializes service. Subsequent calls to this function have no effect. - Must be called at least once (in entire monkey lifetime) before usage of functions - :return: None - """ - if RemoteRunAwsService.aws_instance is None: - RemoteRunAwsService.aws_instance = AwsInstance() - aws_info_fetch_done.set() - @staticmethod def run_aws_monkeys(instances, island_ip): """ @@ -47,18 +26,6 @@ class RemoteRunAwsService: lambda _, result: result.is_success, ) - @staticmethod - def is_running_on_aws(): - aws_info_fetch_done.wait(AWS_INFO_FETCH_TIMEOUT) - return RemoteRunAwsService.aws_instance.is_instance - - @staticmethod - def update_aws_region_authless(): - """ - Updates the AWS region without auth params (via IAM role) - """ - AwsService.set_region(RemoteRunAwsService.aws_instance.region) - @staticmethod def _run_aws_monkey_cmd_async(instance_id, is_linux, island_ip): """ diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index ed8925985..41d133b19 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -5,6 +5,7 @@ from datetime import datetime import boto3 from botocore.exceptions import UnknownServiceError +from common.aws import aws_service from common.aws.aws_instance import AwsInstance from monkey_island.cc.services.reporting.exporter import Exporter @@ -35,8 +36,7 @@ class AWSExporter(Exporter): logger.info("No issues were found by the monkey, no need to send anything") return True - # Not suppressing error here on purpose. - current_aws_region = AwsInstance().region + current_aws_region = aws_service.get_region() for machine in issues_list: for issue in issues_list[machine]: diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index c19f3d5e3..8a84cdadd 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -1,6 +1,6 @@ import logging -from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService +from common.aws import aws_service from monkey_island.cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager @@ -22,8 +22,7 @@ def populate_exporter_list(): def try_add_aws_exporter_to_manager(manager): # noinspection PyBroadException try: - RemoteRunAwsService.init() - if RemoteRunAwsService.is_running_on_aws(): + if aws_service.is_on_aws(): manager.add_exporter_to_list(AWSExporter) except Exception: logger.error("Failed adding aws exporter to manager. Exception info:", exc_info=True) From e89589762ef6c180b8314aa4431ec696e4b29e0c Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 2 May 2022 14:52:46 +0300 Subject: [PATCH 15/21] Common: Rever singleton from WeakValueDictionary to simple dictionary --- monkey/common/utils/code_utils.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index fce8453bf..810fbb0d7 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,8 +1,3 @@ -# abstract, static method decorator -# noinspection PyPep8Naming -from weakref import WeakValueDictionary - - class abstractstatic(staticmethod): __slots__ = () @@ -14,7 +9,7 @@ class abstractstatic(staticmethod): class Singleton(type): - _instances = WeakValueDictionary() + _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: From 271461779753c40fd01b479c7e90ba8542e353a2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 2 May 2022 09:10:37 -0400 Subject: [PATCH 16/21] Agent: Use AwsInstance instead of aws_service --- monkey/infection_monkey/utils/aws_environment_check.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/utils/aws_environment_check.py b/monkey/infection_monkey/utils/aws_environment_check.py index fe7316e57..203882425 100644 --- a/monkey/infection_monkey/utils/aws_environment_check.py +++ b/monkey/infection_monkey/utils/aws_environment_check.py @@ -1,6 +1,6 @@ import logging -from common.aws import aws_service +from common.aws.aws_instance import AwsInstance from infection_monkey.telemetry.aws_instance_telem import AWSInstanceTelemetry from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( LegacyTelemetryMessengerAdapter, @@ -13,9 +13,9 @@ logger = logging.getLogger(__name__) def _report_aws_environment(telemetry_messenger: LegacyTelemetryMessengerAdapter): logger.info("Collecting AWS info") - aws_instance = aws_service.initialize() + aws_instance = AwsInstance() - if aws_service.is_on_aws(): + if aws_instance.is_instance: logger.info("Machine is an AWS instance") telemetry_messenger.send_telemetry(AWSInstanceTelemetry(aws_instance.instance_id)) else: From ae83c2e3e05387b3b2bfc844de2c696dd17dde5c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 2 May 2022 09:16:59 -0400 Subject: [PATCH 17/21] Island: Relocate aws_service from common to monkey_island --- monkey/common/cmd/aws/aws_cmd_runner.py | 2 +- monkey/monkey_island/cc/resources/remote_run.py | 2 +- .../{common/aws => monkey_island/cc/services}/aws_service.py | 0 monkey/monkey_island/cc/services/initialize.py | 3 +-- monkey/monkey_island/cc/services/reporting/aws_exporter.py | 2 +- monkey/monkey_island/cc/services/reporting/exporter_init.py | 2 +- .../aws => monkey_island/cc/services}/test_aws_service.py | 2 +- 7 files changed, 6 insertions(+), 7 deletions(-) rename monkey/{common/aws => monkey_island/cc/services}/aws_service.py (100%) rename monkey/tests/unit_tests/{common/cloud/aws => monkey_island/cc/services}/test_aws_service.py (95%) diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py index 1e00c6b35..bc50c03a8 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -1,10 +1,10 @@ import logging import time -from common.aws import aws_service from common.cmd.aws.aws_cmd_result import AwsCmdResult from common.cmd.cmd_runner import CmdRunner from common.cmd.cmd_status import CmdStatus +from monkey_island.cc.services import aws_service logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index dd9a4eaf6..f918c9253 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -4,8 +4,8 @@ import flask_restful from botocore.exceptions import ClientError, NoCredentialsError from flask import jsonify, make_response, request -from common.aws import aws_service from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services import aws_service from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService CLIENT_ERROR_FORMAT = ( diff --git a/monkey/common/aws/aws_service.py b/monkey/monkey_island/cc/services/aws_service.py similarity index 100% rename from monkey/common/aws/aws_service.py rename to monkey/monkey_island/cc/services/aws_service.py diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 06b2473f8..faa3bcef9 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -2,8 +2,7 @@ from pathlib import Path from threading import Thread from common import DIContainer -from common.aws import aws_service -from monkey_island.cc.services import DirectoryFileStorageService, IFileStorageService +from monkey_island.cc.services import DirectoryFileStorageService, IFileStorageService, aws_service from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 41d133b19..7811ce60e 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -5,8 +5,8 @@ from datetime import datetime import boto3 from botocore.exceptions import UnknownServiceError -from common.aws import aws_service from common.aws.aws_instance import AwsInstance +from monkey_island.cc.services import aws_service from monkey_island.cc.services.reporting.exporter import Exporter __authors__ = ["maor.rayzin", "shay.nehmad"] diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index 8a84cdadd..bb2d568b9 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -1,6 +1,6 @@ import logging -from common.aws import aws_service +from monkey_island.cc.services import aws_service from monkey_island.cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager diff --git a/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_aws_service.py similarity index 95% rename from monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/test_aws_service.py index f66646b34..0d8a71f36 100644 --- a/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_aws_service.py @@ -1,7 +1,7 @@ import json from unittest import TestCase -from common.aws.aws_service import filter_instance_data_from_aws_response +from monkey_island.cc.services.aws_service import filter_instance_data_from_aws_response class TestAwsService(TestCase): From 56ea1708085b094c1f9e2d6ff13e2b9ea560b1d6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 2 May 2022 09:20:41 -0400 Subject: [PATCH 18/21] Island: Add get_account_id() to aws_service --- monkey/monkey_island/cc/services/aws_service.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey/monkey_island/cc/services/aws_service.py b/monkey/monkey_island/cc/services/aws_service.py index 3198d6e0f..b0f252608 100644 --- a/monkey/monkey_island/cc/services/aws_service.py +++ b/monkey/monkey_island/cc/services/aws_service.py @@ -66,6 +66,11 @@ def get_region(): return aws_instance.region +@wait_init_done +def get_account_id(): + return aws_instance.account_id + + @wait_init_done def get_client(client_type): return boto3.client(client_type, region_name=aws_instance.region) From ceebdea3a97990b1b875172ffca0df2d737c0b71 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 2 May 2022 09:21:02 -0400 Subject: [PATCH 19/21] Island: Use aws_service.get_account_id() in AWSExporter --- monkey/monkey_island/cc/services/reporting/aws_exporter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 7811ce60e..aeb76b4d5 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -5,7 +5,6 @@ from datetime import datetime import boto3 from botocore.exceptions import UnknownServiceError -from common.aws.aws_instance import AwsInstance from monkey_island.cc.services import aws_service from monkey_island.cc.services.reporting.exporter import Exporter @@ -90,7 +89,7 @@ class AWSExporter(Exporter): ) instance_arn = "arn:aws:ec2:" + str(region) + ":instance:{instance_id}" # Not suppressing error here on purpose. - account_id = AwsInstance().get_account_id() + account_id = aws_service.get_account_id() logger.debug("aws account id acquired: {}".format(account_id)) aws_finding = { From a466e97cb0e9cee8837dd4339b0dc518f46a8125 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 2 May 2022 09:28:41 -0400 Subject: [PATCH 20/21] Island: Move AwsCmdRunner to monkey_island/cc/server_utils/ --- .../cmd/aws => monkey_island/cc/server_utils}/aws_cmd_result.py | 0 .../cmd/aws => monkey_island/cc/server_utils}/aws_cmd_runner.py | 2 +- monkey/monkey_island/cc/services/remote_run_aws.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename monkey/{common/cmd/aws => monkey_island/cc/server_utils}/aws_cmd_result.py (100%) rename monkey/{common/cmd/aws => monkey_island/cc/server_utils}/aws_cmd_runner.py (95%) diff --git a/monkey/common/cmd/aws/aws_cmd_result.py b/monkey/monkey_island/cc/server_utils/aws_cmd_result.py similarity index 100% rename from monkey/common/cmd/aws/aws_cmd_result.py rename to monkey/monkey_island/cc/server_utils/aws_cmd_result.py diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/monkey_island/cc/server_utils/aws_cmd_runner.py similarity index 95% rename from monkey/common/cmd/aws/aws_cmd_runner.py rename to monkey/monkey_island/cc/server_utils/aws_cmd_runner.py index bc50c03a8..16c959197 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/monkey_island/cc/server_utils/aws_cmd_runner.py @@ -1,9 +1,9 @@ import logging import time -from common.cmd.aws.aws_cmd_result import AwsCmdResult from common.cmd.cmd_runner import CmdRunner from common.cmd.cmd_status import CmdStatus +from monkey_island.cc.server_utils.aws_cmd_result import AwsCmdResult from monkey_island.cc.services import aws_service logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 3dbb15477..c3219171c 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -1,8 +1,8 @@ import logging -from common.cmd.aws.aws_cmd_runner import AwsCmdRunner from common.cmd.cmd import Cmd from common.cmd.cmd_runner import CmdRunner +from monkey_island.cc.server_utils.aws_cmd_runner import AwsCmdRunner logger = logging.getLogger(__name__) From 3a98fdbf52fd275925fab04e59a2f3b53204b810 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 2 May 2022 09:50:56 -0400 Subject: [PATCH 21/21] UT: Use MappingProxyType for defaults in get_test_aws_instance() --- .../unit_tests/common/cloud/aws/test_aws_instance.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py index d36c92ac0..070b14092 100644 --- a/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py +++ b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py @@ -1,3 +1,5 @@ +from types import MappingProxyType + import pytest import requests import requests_mock @@ -37,14 +39,9 @@ EXPECTED_ACCOUNT_ID = "123456789012" def get_test_aws_instance( - text=None, - exception=None, + text=MappingProxyType({"instance_id": None, "region": None, "account_id": None}), + exception=MappingProxyType({"instance_id": None, "region": None, "account_id": None}), ): - if text is None: - text = {"instance_id": None, "region": None, "account_id": None} - if exception is None: - exception = {"instance_id": None, "region": None, "account_id": None} - with requests_mock.Mocker() as m: # request made to get instance_id url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id"