From e12bc7ac2846e1d8ee55d4e2308d7311a7cef568 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 07:46:29 -0400 Subject: [PATCH 01/12] Tests: Remove unnecessary sleep(1) --- monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py b/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py index 2df8be4dd..90fd9032a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py +++ b/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py @@ -1,6 +1,5 @@ import logging import uuid -from time import sleep import pytest @@ -24,9 +23,9 @@ class TestMonkey: mia_monkey_ttl.save() mia_monkey = Monkey(guid=str(uuid.uuid4()), dead=False, ttl_ref=mia_monkey_ttl.id) mia_monkey.save() + # Emulate timeout - ttl is manually deleted here, since we're using mongomock and not a # real mongo instance. - sleep(1) mia_monkey_ttl.delete() dead_monkey = Monkey(guid=str(uuid.uuid4()), dead=True) From 2897755caeb2e216ea5ee7a67c73a89b108adc15 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 08:10:37 -0400 Subject: [PATCH 02/12] Tests: Move contents of tests/conftest.py --- monkey/tests/conftest.py | 23 ----------------------- monkey/tests/unit_tests/conftest.py | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 23 deletions(-) delete mode 100644 monkey/tests/conftest.py diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py deleted file mode 100644 index fc44af014..000000000 --- a/monkey/tests/conftest.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import sys -from pathlib import Path - -import pytest - -MONKEY_BASE_PATH = str(Path(__file__).parent.parent) -sys.path.insert(0, MONKEY_BASE_PATH) - - -@pytest.fixture(scope="session") -def data_for_tests_dir(pytestconfig): - return Path(os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests")) - - -@pytest.fixture(scope="session") -def stable_file(data_for_tests_dir) -> Path: - return data_for_tests_dir / "stable_file.txt" - - -@pytest.fixture(scope="session") -def stable_file_sha256_hash() -> str: - return "d9dcaadc91261692dafa86e7275b1bf39bb7e19d2efcfacd6fe2bfc9a1ae1062" diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py index 8e0fc96d9..64b2737c4 100644 --- a/monkey/tests/unit_tests/conftest.py +++ b/monkey/tests/unit_tests/conftest.py @@ -1,5 +1,27 @@ +import os +import sys +from pathlib import Path + import pytest +MONKEY_BASE_PATH = str(Path(__file__).parent.parent.parent) +sys.path.insert(0, MONKEY_BASE_PATH) + + +@pytest.fixture(scope="session") +def data_for_tests_dir(pytestconfig): + return Path(os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests")) + + +@pytest.fixture(scope="session") +def stable_file(data_for_tests_dir) -> Path: + return data_for_tests_dir / "stable_file.txt" + + +@pytest.fixture(scope="session") +def stable_file_sha256_hash() -> str: + return "d9dcaadc91261692dafa86e7275b1bf39bb7e19d2efcfacd6fe2bfc9a1ae1062" + @pytest.fixture def patched_home_env(monkeypatch, tmp_path): From 5222230487006abcbf14b729c655caeadf24f834 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 08:11:58 -0400 Subject: [PATCH 03/12] Tests: Add monkeypatch_session fixture --- monkey/tests/unit_tests/conftest.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py index 64b2737c4..3099263b0 100644 --- a/monkey/tests/unit_tests/conftest.py +++ b/monkey/tests/unit_tests/conftest.py @@ -3,6 +3,7 @@ import sys from pathlib import Path import pytest +from _pytest.monkeypatch import MonkeyPatch MONKEY_BASE_PATH = str(Path(__file__).parent.parent.parent) sys.path.insert(0, MONKEY_BASE_PATH) @@ -28,3 +29,13 @@ def patched_home_env(monkeypatch, tmp_path): monkeypatch.setenv("HOME", str(tmp_path)) return tmp_path + + +# The monkeypatch fixture is function scoped, so it cannot be used by session-scoped fixtures. This +# monkeypatch_session fixture can be session-scoped. For more information, see +# https://github.com/pytest-dev/pytest/issues/363#issuecomment-406536200 +@pytest.fixture(scope="session") +def monkeypatch_session(): + monkeypatch_ = MonkeyPatch() + yield monkeypatch_ + monkeypatch_.undo() From 162e375c871bbc10cc7e8aa2a7a21b993b3ca69d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 08:14:49 -0400 Subject: [PATCH 04/12] Tests: Make flask_client a session-scoped fixture --- .../unit_tests/monkey_island/cc/resources/conftest.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py index 0e82fe163..3ca40a11a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py @@ -9,12 +9,9 @@ import monkey_island.cc.resources.island_mode from monkey_island.cc.services.representations import output_json -# We can't scope to module, because monkeypatch is a function scoped decorator. -# Potential solutions: https://github.com/pytest-dev/pytest/issues/363#issuecomment-406536200 or -# https://stackoverflow.com/questions/53963822/python-monkeypatch-setattr-with-pytest-fixture-at-module-scope -@pytest.fixture(scope="function") -def flask_client(monkeypatch): - monkeypatch.setattr(flask_jwt_extended, "verify_jwt_in_request", lambda: None) +@pytest.fixture(scope="session") +def flask_client(monkeypatch_session): + monkeypatch_session.setattr(flask_jwt_extended, "verify_jwt_in_request", lambda: None) with mock_init_app().test_client() as client: yield client From ac9bd8dee76ad39a54965a132b59dfc4d0ea2b1f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 08:56:48 -0400 Subject: [PATCH 05/12] Tests: Remove two extra calls to encrypt_string() Calls to encrypt_string() result in calls to pyAesCrypt.encryptStream(). These calls are very slow (about .150ms). Modifying these tests to use static ciphertext instead of encrypting the file each time saves approximately 300ms when running the unit test suite. --- .../utils/ciphertexts_for_encryption_test.py | 58 +++++++++++++++++++ .../services/utils/test_config_encryption.py | 13 ++--- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py index b35513a2a..0fa7131e0 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py @@ -1,3 +1,61 @@ +VALID_CIPHER_TEXT = ( + "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" + "5pnm3dizp6Q=" +) + MALFORMED_CIPHER_TEXT_CORRUPTED = ( "QUVTAgAAGM0NKEYTESTURfQlkAcHlBZXNDcnlwdCA2LjAuMACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index 23b399cc5..086b30109 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -1,6 +1,7 @@ import pytest from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption_test import ( MALFORMED_CIPHER_TEXT_CORRUPTED, + VALID_CIPHER_TEXT, ) from monkey_island.cc.services.utils.encryption import ( @@ -20,18 +21,16 @@ def test_encrypt_decrypt_string(monkey_config_json): assert decrypt_ciphertext(encrypted_config, PASSWORD) == monkey_config_json -def test_encrypt_decrypt_string__wrong_password(monkey_config_json): - encrypted_config = encrypt_string(monkey_config_json, PASSWORD) +def test_decrypt_string__wrong_password(monkey_config_json): with pytest.raises(InvalidCredentialsError): - decrypt_ciphertext(encrypted_config, INCORRECT_PASSWORD) + decrypt_ciphertext(VALID_CIPHER_TEXT, INCORRECT_PASSWORD) -def test_encrypt_decrypt_string__malformed_corrupted(): +def test_decrypt_string__malformed_corrupted(): with pytest.raises(ValueError): decrypt_ciphertext(MALFORMED_CIPHER_TEXT_CORRUPTED, PASSWORD) -def test_encrypt_decrypt_string__decrypt_no_password(monkey_config_json): - encrypted_config = encrypt_string(monkey_config_json, PASSWORD) +def test_decrypt_string__no_password(monkey_config_json): with pytest.raises(InvalidCredentialsError): - decrypt_ciphertext(encrypted_config, "") + decrypt_ciphertext(VALID_CIPHER_TEXT, "") From f0033d0c7cb2e6a42c65b45fcc4081beb2747ccf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 12:26:32 -0400 Subject: [PATCH 06/12] Tests: Convert test_get_all_mitigations() from unittest to pytest --- .../attack/test_mitre_api_interface.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py b/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py index 44297795c..b6b9acbc7 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py @@ -1,14 +1,11 @@ -from unittest import TestCase - from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface -class TestMitreApiInterface(TestCase): - def test_get_all_mitigations(self): - mitigations = MitreApiInterface.get_all_mitigations() - self.assertIsNotNone((len(mitigations.items()) >= 282)) - mitigation = next(iter(mitigations.values())) - self.assertEqual(mitigation["type"], "course-of-action") - self.assertIsNotNone(mitigation["name"]) - self.assertIsNotNone(mitigation["description"]) - self.assertIsNotNone(mitigation["external_references"]) +def test_get_all_mitigations(): + mitigations = MitreApiInterface.get_all_mitigations() + assert len(mitigations.items()) >= 282 + mitigation = next(iter(mitigations.values())) + assert mitigation["type"] == "course-of-action" + assert mitigation["name"] is not None + assert mitigation["description"] is not None + assert mitigation["external_references"] is not None From ac52c308f36addfdf86a706e9add54b7020c3e11 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 12:27:54 -0400 Subject: [PATCH 07/12] Tests: Mark slow tests with @pytest.mark.slow This allows you to skip slow tests by running `pytest -m 'not slow'`. --- .../monkey_island/cc/resources/test_configuration_import.py | 1 + .../cc/services/attack/test_mitre_api_interface.py | 3 +++ .../monkey_island/cc/services/utils/test_config_encryption.py | 4 ++++ pyproject.toml | 1 + 4 files changed, 9 insertions(+) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py index ed1d908cf..e9672ebdf 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py @@ -13,6 +13,7 @@ def test_is_config_encrypted__json(monkey_config_json): assert not ConfigurationImport.is_config_encrypted(monkey_config_json) +@pytest.mark.slow def test_is_config_encrypted__ciphertext(monkey_config_json): encrypted_config = encrypt_string(monkey_config_json, PASSWORD) assert ConfigurationImport.is_config_encrypted(encrypted_config) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py b/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py index b6b9acbc7..f93afc8d5 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py @@ -1,6 +1,9 @@ +import pytest + from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface +@pytest.mark.slow def test_get_all_mitigations(): mitigations = MitreApiInterface.get_all_mitigations() assert len(mitigations.items()) >= 282 diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index 086b30109..fd3191f50 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -16,21 +16,25 @@ PASSWORD = "hello123" INCORRECT_PASSWORD = "goodbye321" +@pytest.mark.slow def test_encrypt_decrypt_string(monkey_config_json): encrypted_config = encrypt_string(monkey_config_json, PASSWORD) assert decrypt_ciphertext(encrypted_config, PASSWORD) == monkey_config_json +@pytest.mark.slow def test_decrypt_string__wrong_password(monkey_config_json): with pytest.raises(InvalidCredentialsError): decrypt_ciphertext(VALID_CIPHER_TEXT, INCORRECT_PASSWORD) +@pytest.mark.slow def test_decrypt_string__malformed_corrupted(): with pytest.raises(ValueError): decrypt_ciphertext(MALFORMED_CIPHER_TEXT_CORRUPTED, PASSWORD) +@pytest.mark.slow def test_decrypt_string__no_password(monkey_config_json): with pytest.raises(InvalidCredentialsError): decrypt_ciphertext(VALID_CIPHER_TEXT, "") diff --git a/pyproject.toml b/pyproject.toml index 319c4cc1f..05c8dfe81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ log_cli_format = "%(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d log_cli_date_format = "%H:%M:%S" addopts = "-v --capture=sys tests/unit_tests" norecursedirs = "node_modules dist" +markers = ["slow: mark test as slow"] [tool.vulture] exclude = ["monkey/monkey_island/cc/ui/", "monkey/tests/"] From 41cf0f07c33ecf83c3b2efbd1c46394af75a7bd6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 12:38:56 -0400 Subject: [PATCH 08/12] Tests: Address mongomock deprication warnings --- .../ransomware/test_ransomware_report.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index f59edbbc3..3f7ce4c57 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -23,14 +23,14 @@ def fake_mongo(monkeypatch): @pytest.mark.usefixtures("uses_database") def test_get_encrypted_files_table(fake_mongo, monkeypatch): - fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) - fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) - fake_mongo.db.edge.insert(EDGE_EXPLOITED) - fake_mongo.db.edge.insert(EDGE_SCANNED) - fake_mongo.db.telemetry.insert(ENCRYPTED) - fake_mongo.db.telemetry.insert(ENCRYPTED_2) - fake_mongo.db.telemetry.insert(ENCRYPTION_ERROR) - fake_mongo.db.telemetry.insert(ENCRYPTION_ONE_FILE) + fake_mongo.db.monkey.insert_one(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert_one(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert_one(EDGE_EXPLOITED) + fake_mongo.db.edge.insert_one(EDGE_SCANNED) + fake_mongo.db.telemetry.insert_one(ENCRYPTED) + fake_mongo.db.telemetry.insert_one(ENCRYPTED_2) + fake_mongo.db.telemetry.insert_one(ENCRYPTION_ERROR) + fake_mongo.db.telemetry.insert_one(ENCRYPTION_ONE_FILE) monkeypatch.setattr( ReportService, @@ -58,11 +58,11 @@ def test_get_encrypted_files_table(fake_mongo, monkeypatch): @pytest.mark.usefixtures("uses_database") def test_get_encrypted_files_table__only_errors(fake_mongo, monkeypatch): - fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) - fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) - fake_mongo.db.edge.insert(EDGE_EXPLOITED) - fake_mongo.db.edge.insert(EDGE_SCANNED) - fake_mongo.db.telemetry.insert(ENCRYPTION_ERROR) + fake_mongo.db.monkey.insert_one(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert_one(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert_one(EDGE_EXPLOITED) + fake_mongo.db.edge.insert_one(EDGE_SCANNED) + fake_mongo.db.telemetry.insert_one(ENCRYPTION_ERROR) monkeypatch.setattr( ReportService, @@ -84,10 +84,10 @@ def test_get_encrypted_files_table__only_errors(fake_mongo, monkeypatch): @pytest.mark.usefixtures("uses_database") def test_get_encrypted_files_table__no_telemetries(fake_mongo, monkeypatch): - fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) - fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) - fake_mongo.db.edge.insert(EDGE_EXPLOITED) - fake_mongo.db.edge.insert(EDGE_SCANNED) + fake_mongo.db.monkey.insert_one(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert_one(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert_one(EDGE_EXPLOITED) + fake_mongo.db.edge.insert_one(EDGE_SCANNED) monkeypatch.setattr( ReportService, From 845c9d9ac363f0b32a90f0ca9b1bed17de05bd4b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 12:41:35 -0400 Subject: [PATCH 09/12] Tests: Address deprecation warning in config.py --- monkey/monkey_island/cc/services/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index acb12d48a..55cd70b3b 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -360,7 +360,7 @@ class ConfigService: parent_config_arr = config_arr config_arr = config_arr[config_key_part] - if isinstance(config_arr, collections.Sequence) and not isinstance(config_arr, str): + if isinstance(config_arr, collections.abc.Sequence) and not isinstance(config_arr, str): for i in range(len(config_arr)): # Check if array of shh key pairs and then decrypt if isinstance(config_arr[i], dict) and "public_key" in config_arr[i]: From 2496ed08896e815637ff72884ba90492d429cd88 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 14:06:31 -0400 Subject: [PATCH 10/12] Tests: Use SSHExploiter instead of WmiExploiter in expliot telem tests WmiExploiter relies on impacket. Importing impacket is slow, which has a negative impact on the speed of pytest collection. SSHExploiter is much quicker to import. --- .../infection_monkey/telemetry/test_exploit_telem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py index 95f853922..6ecfeba1a 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py @@ -2,7 +2,7 @@ import json import pytest -from infection_monkey.exploit.wmiexec import WmiExploiter +from infection_monkey.exploit.sshexec import SSHExploiter from infection_monkey.model.host import VictimHost from infection_monkey.telemetry.exploit_telem import ExploitTelem @@ -19,10 +19,10 @@ HOST_AS_DICT = { "default_tunnel": None, "default_server": None, } -EXPLOITER = WmiExploiter(HOST) -EXPLOITER_NAME = "WmiExploiter" +EXPLOITER = SSHExploiter(HOST) +EXPLOITER_NAME = "SSHExploiter" EXPLOITER_INFO = { - "display_name": WmiExploiter._EXPLOITED_SERVICE, + "display_name": SSHExploiter._EXPLOITED_SERVICE, "started": "", "finished": "", "vulnerable_urls": [], From d9a1f229697569446ce1e96830ea1b004677b5de Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 14:26:40 -0400 Subject: [PATCH 11/12] Tests: Mark ZeroLogon tests as slow The ZerologonExploiter relies on impacket. Importing impacket is slow (approximately .72s). By moving the import statement in zerologon tests and marking them as slow, the import (and tests) can now be skipped by running `pytest -m 'not slow'`. --- .../infection_monkey/exploit/test_zerologon.py | 9 ++++++++- .../exploit/zerologon_utils/test_vuln_assessment.py | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py index a2956887f..95beb1778 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py @@ -1,6 +1,5 @@ import pytest -from infection_monkey.exploit.zerologon import ZerologonExploiter from infection_monkey.model.host import VictimHost DOMAIN_NAME = "domain-name" @@ -15,6 +14,8 @@ NT_HASHES = ["def456", "765vut"] @pytest.fixture def zerologon_exploiter_object(monkeypatch): + from infection_monkey.exploit.zerologon import ZerologonExploiter + def mock_report_login_attempt(**kwargs): return None @@ -25,11 +26,13 @@ def zerologon_exploiter_object(monkeypatch): return obj +@pytest.mark.slow def test_assess_exploit_attempt_result_no_error(zerologon_exploiter_object): dummy_exploit_attempt_result = {"ErrorCode": 0} assert zerologon_exploiter_object.assess_exploit_attempt_result(dummy_exploit_attempt_result) +@pytest.mark.slow def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object): dummy_exploit_attempt_result = {"ErrorCode": 1} assert not zerologon_exploiter_object.assess_exploit_attempt_result( @@ -37,6 +40,7 @@ def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object): ) +@pytest.mark.slow def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object): dummy_restoration_attempt_result = object() assert zerologon_exploiter_object.assess_restoration_attempt_result( @@ -44,6 +48,7 @@ def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object): ) +@pytest.mark.slow def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_object): dummy_restoration_attempt_result = False assert not zerologon_exploiter_object.assess_restoration_attempt_result( @@ -51,6 +56,7 @@ def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_obje ) +@pytest.mark.slow def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object): mock_dumped_secrets = [ f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS)) @@ -71,6 +77,7 @@ def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object): assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds +@pytest.mark.slow def test__extract_user_creds_from_secrets_bad_data(zerologon_exploiter_object): mock_dumped_secrets = [ f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS)) diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py b/monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py index 525cd8e3f..c0635939c 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py @@ -2,7 +2,6 @@ import pytest from nmb.NetBIOS import NetBIOS from common.utils.exceptions import DomainControllerNameFetchError -from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details from infection_monkey.model.host import VictimHost DOMAIN_NAME = "domain-name" @@ -21,7 +20,10 @@ def _get_stub_queryIPForName(netbios_names): return stub_queryIPForName +@pytest.mark.slow def test_get_dc_details_multiple_netbios_names(host, monkeypatch): + from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details + NETBIOS_NAMES = ["Name1", "Name2", "Name3"] stub_queryIPForName = _get_stub_queryIPForName(NETBIOS_NAMES) @@ -33,7 +35,10 @@ def test_get_dc_details_multiple_netbios_names(host, monkeypatch): assert dc_handle == f"\\\\{NETBIOS_NAMES[0]}" +@pytest.mark.slow def test_get_dc_details_no_netbios_names(host, monkeypatch): + from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details + NETBIOS_NAMES = [] stub_queryIPForName = _get_stub_queryIPForName(NETBIOS_NAMES) From adb1006b5781a9640b3d4af6f4a1ebd6bdf240ca Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Jul 2021 15:01:40 -0400 Subject: [PATCH 12/12] Update the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c4f87216..c8ad91472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - MongoDb now gets launched by the Island via python. #1148 - Create/check data directory on Island init. #1170 - The formatting of some log messages to make them more readable. #1283 +- Some unit tests to run faster. #1125 ### Removed - Relevant dead code as reported by Vulture. #1149