diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 408839b6c..52b6bd30b 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -13,6 +13,7 @@ from monkey_island.cc.database import database, mongo from monkey_island.cc.resources import ( AgentBinaries, ClearSimulationData, + PropagationCredentials, RemoteRun, ResetAgentConfiguration, ) @@ -42,7 +43,6 @@ from monkey_island.cc.resources.node import Node from monkey_island.cc.resources.node_states import NodeStates from monkey_island.cc.resources.pba_file_download import PBAFileDownload from monkey_island.cc.resources.pba_file_upload import FileUpload -from monkey_island.cc.resources.propagation_credentials import PropagationCredentials from monkey_island.cc.resources.ransomware_report import RansomwareReport from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.security_report import SecurityReport diff --git a/monkey/monkey_island/cc/resources/__init__.py b/monkey/monkey_island/cc/resources/__init__.py index 7f0385c32..bd0ed0d18 100644 --- a/monkey/monkey_island/cc/resources/__init__.py +++ b/monkey/monkey_island/cc/resources/__init__.py @@ -2,3 +2,4 @@ from .remote_run import RemoteRun from .agent_binaries import AgentBinaries from .clear_simulation_data import ClearSimulationData from .reset_agent_configuration import ResetAgentConfiguration +from .propagation_credentials import PropagationCredentials diff --git a/monkey/monkey_island/cc/resources/propagation_credentials.py b/monkey/monkey_island/cc/resources/propagation_credentials.py index 8d72fe427..9fb59ad0e 100644 --- a/monkey/monkey_island/cc/resources/propagation_credentials.py +++ b/monkey/monkey_island/cc/resources/propagation_credentials.py @@ -1,17 +1,55 @@ -from flask import make_response +from http import HTTPStatus + +from flask import make_response, request from common.credentials import Credentials from monkey_island.cc.repository import ICredentialsRepository from monkey_island.cc.resources.AbstractResource import AbstractResource +_configured_collection = "configured-credentials" +_stolen_collection = "stolen-credentials" + class PropagationCredentials(AbstractResource): - urls = ["/api/propagation-credentials"] + urls = ["/api/propagation-credentials/", "/api/propagation-credentials/"] def __init__(self, credentials_repository: ICredentialsRepository): self._credentials_repository = credentials_repository - def get(self): - propagation_credentials = self._credentials_repository.get_all_credentials() + def get(self, collection=None): + if collection == _configured_collection: + propagation_credentials = self._credentials_repository.get_configured_credentials() + elif collection == _stolen_collection: + propagation_credentials = self._credentials_repository.get_stolen_credentials() + elif collection is None: + propagation_credentials = self._credentials_repository.get_all_credentials() + else: + return {}, HTTPStatus.NOT_FOUND - return make_response(Credentials.to_json_array(propagation_credentials), 200) + return make_response(Credentials.to_json_array(propagation_credentials), HTTPStatus.OK) + + def post(self, collection=None): + credentials = [Credentials.from_json(c) for c in request.json] + + if collection == _configured_collection: + self._credentials_repository.save_configured_credentials(credentials) + elif collection == _stolen_collection: + self._credentials_repository.save_stolen_credentials(credentials) + elif collection is None: + return {}, HTTPStatus.METHOD_NOT_ALLOWED + else: + return {}, HTTPStatus.NOT_FOUND + + return {}, HTTPStatus.NO_CONTENT + + def delete(self, collection=None): + if collection == _configured_collection: + self._credentials_repository.remove_configured_credentials() + elif collection == _stolen_collection: + self._credentials_repository.remove_stolen_credentials() + elif collection is None: + self._credentials_repository.remove_all_credentials() + else: + return {}, HTTPStatus.NOT_FOUND + + return {}, HTTPStatus.NO_CONTENT diff --git a/monkey/tests/data_for_tests/propagation_credentials.py b/monkey/tests/data_for_tests/propagation_credentials.py new file mode 100644 index 000000000..df6c213cf --- /dev/null +++ b/monkey/tests/data_for_tests/propagation_credentials.py @@ -0,0 +1,26 @@ +from common.credentials import Credentials, LMHash, NTHash, Password, Username + +username = "m0nk3y_user" +special_username = "m0nk3y.user" +nt_hash = "C1C58F96CDF212B50837BC11A00BE47C" +lm_hash = "299BD128C1101FD6299BD128C1101FD6" +password_1 = "trytostealthis" +password_2 = "password" +password_3 = "12345678" + +PROPAGATION_CREDENTIALS_1 = Credentials( + identities=(Username(username),), + secrets=(NTHash(nt_hash), LMHash(lm_hash), Password(password_1)), +) +PROPAGATION_CREDENTIALS_2 = Credentials( + identities=(Username(username), Username(special_username)), + secrets=(Password(password_1), Password(password_2), Password(password_3)), +) +PROPAGATION_CREDENTIALS_3 = Credentials( + identities=(Username(username),), + secrets=(Password(password_1),), +) +PROPAGATION_CREDENTIALS_4 = Credentials( + identities=(Username(username),), + secrets=(Password(password_2),), +) diff --git a/monkey/tests/monkey_island/__init__.py b/monkey/tests/monkey_island/__init__.py index ee3098390..f7033842d 100644 --- a/monkey/tests/monkey_island/__init__.py +++ b/monkey/tests/monkey_island/__init__.py @@ -3,8 +3,4 @@ from .mock_file_repository import MockFileRepository, FILE_CONTENTS, FILE_NAME from .open_error_file_repository import OpenErrorFileRepository from .in_memory_agent_configuration_repository import InMemoryAgentConfigurationRepository from .in_memory_simulation_configuration import InMemorySimulationRepository -from .stub_propagation_credentials_repository import StubPropagationCredentialsRepository -from .stub_propagation_credentials_repository import ( - PROPAGATION_CREDENTIALS_1, - PROPAGATION_CREDENTIALS_2, -) +from .in_memory_credentials_repository import InMemoryCredentialsRepository diff --git a/monkey/tests/monkey_island/in_memory_credentials_repository.py b/monkey/tests/monkey_island/in_memory_credentials_repository.py new file mode 100644 index 000000000..6eb8155e8 --- /dev/null +++ b/monkey/tests/monkey_island/in_memory_credentials_repository.py @@ -0,0 +1,35 @@ +from typing import Sequence + +from common.credentials import Credentials +from monkey_island.cc.repository import ICredentialsRepository + + +class InMemoryCredentialsRepository(ICredentialsRepository): + def __init__(self): + self._configured_credentials = [] + self._stolen_credentials = [] + + def get_configured_credentials(self) -> Sequence[Credentials]: + return self._configured_credentials + + def get_stolen_credentials(self) -> Sequence[Credentials]: + return self._stolen_credentials + + def get_all_credentials(self) -> Sequence[Credentials]: + return [*self._configured_credentials, *self._stolen_credentials] + + def save_configured_credentials(self, credentials: Sequence[Credentials]): + self._configured_credentials.extend(credentials) + + def save_stolen_credentials(self, credentials: Sequence[Credentials]): + self._stolen_credentials.extend(credentials) + + def remove_configured_credentials(self): + self._configured_credentials = [] + + def remove_stolen_credentials(self): + self._stolen_credentials = [] + + def remove_all_credentials(self): + self.remove_configured_credentials() + self.remove_stolen_credentials() diff --git a/monkey/tests/monkey_island/stub_propagation_credentials_repository.py b/monkey/tests/monkey_island/stub_propagation_credentials_repository.py deleted file mode 100644 index 237d28b85..000000000 --- a/monkey/tests/monkey_island/stub_propagation_credentials_repository.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Sequence - -from common.credentials import Credentials, LMHash, NTHash, Password, Username -from monkey_island.cc.repository import ICredentialsRepository - -fake_username = "m0nk3y_user" -fake_special_username = "m0nk3y.user" -fake_nt_hash = "C1C58F96CDF212B50837BC11A00BE47C" -fake_lm_hash = "299BD128C1101FD6299BD128C1101FD6" -fake_password_1 = "trytostealthis" -fake_password_2 = "password" -fake_password_3 = "12345678" -PROPAGATION_CREDENTIALS_1 = Credentials( - identities=(Username(fake_username),), - secrets=(NTHash(fake_nt_hash), LMHash(fake_lm_hash), Password(fake_password_1)), -) -PROPAGATION_CREDENTIALS_2 = Credentials( - identities=(Username(fake_username), Username(fake_special_username)), - secrets=(Password(fake_password_1), Password(fake_password_2), Password(fake_password_3)), -) - - -class StubPropagationCredentialsRepository(ICredentialsRepository): - def get_configured_credentials(self) -> Sequence[Credentials]: - pass - - def get_stolen_credentials(self) -> Sequence[Credentials]: - pass - - def get_all_credentials(self) -> Sequence[Credentials]: - - return [PROPAGATION_CREDENTIALS_1, PROPAGATION_CREDENTIALS_2] - - def save_configured_credentials(self, credentials: Sequence[Credentials]): - pass - - def save_stolen_credentials(self, credentials: Sequence[Credentials]): - pass - - def remove_configured_credentials(self): - pass - - def remove_stolen_credentials(self): - pass - - def remove_all_credentials(self): - pass diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py index 728aef1c3..111ad7e30 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py @@ -1,34 +1,148 @@ +import json +from http import HTTPStatus +from typing import Sequence +from urllib.parse import urljoin + import pytest from tests.common import StubDIContainer -from tests.monkey_island import ( +from tests.data_for_tests.propagation_credentials import ( PROPAGATION_CREDENTIALS_1, PROPAGATION_CREDENTIALS_2, - StubPropagationCredentialsRepository, + PROPAGATION_CREDENTIALS_3, + PROPAGATION_CREDENTIALS_4, ) -from tests.unit_tests.monkey_island.conftest import get_url_for_resource +from tests.monkey_island import InMemoryCredentialsRepository from common.credentials import Credentials from monkey_island.cc.repository import ICredentialsRepository -from monkey_island.cc.resources.propagation_credentials import PropagationCredentials +from monkey_island.cc.resources import PropagationCredentials +from monkey_island.cc.resources.propagation_credentials import ( + _configured_collection, + _stolen_collection, +) + +ALL_CREDENTIALS_URL = PropagationCredentials.urls[0] +CONFIGURED_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL, _configured_collection) +STOLEN_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL, _stolen_collection) @pytest.fixture -def flask_client(build_flask_client): +def credentials_repository(): + return InMemoryCredentialsRepository() + + +@pytest.fixture +def flask_client(build_flask_client, credentials_repository): container = StubDIContainer() - container.register(ICredentialsRepository, StubPropagationCredentialsRepository) + container.register_instance(ICredentialsRepository, credentials_repository) with build_flask_client(container) as flask_client: yield flask_client -def test_propagation_credentials_endpoint_get(flask_client): - propagation_credentials_url = get_url_for_resource(PropagationCredentials) +def test_propagation_credentials_endpoint_get(flask_client, credentials_repository): + credentials_repository.save_configured_credentials( + [PROPAGATION_CREDENTIALS_1, PROPAGATION_CREDENTIALS_3] + ) + credentials_repository.save_stolen_credentials( + [PROPAGATION_CREDENTIALS_2, PROPAGATION_CREDENTIALS_4] + ) - resp = flask_client.get(propagation_credentials_url) + resp = flask_client.get(ALL_CREDENTIALS_URL) actual_propagation_credentials = Credentials.from_json_array(resp.text) - assert resp.status_code == 200 + assert resp.status_code == HTTPStatus.OK + assert len(actual_propagation_credentials) == 4 + assert PROPAGATION_CREDENTIALS_1 in actual_propagation_credentials + assert PROPAGATION_CREDENTIALS_2 in actual_propagation_credentials + assert PROPAGATION_CREDENTIALS_3 in actual_propagation_credentials + assert PROPAGATION_CREDENTIALS_4 in actual_propagation_credentials + + +def pre_populate_repository( + url: str, credentials_repository: ICredentialsRepository, credentials: Sequence[Credentials] +): + if "configured" in url: + credentials_repository.save_configured_credentials(credentials) + else: + credentials_repository.save_stolen_credentials(credentials) + + +@pytest.mark.parametrize("url", [CONFIGURED_CREDENTIALS_URL, STOLEN_CREDENTIALS_URL]) +def test_propagation_credentials_endpoint__get_stolen(flask_client, credentials_repository, url): + pre_populate_repository( + url, credentials_repository, [PROPAGATION_CREDENTIALS_1, PROPAGATION_CREDENTIALS_2] + ) + + resp = flask_client.get(url) + actual_propagation_credentials = Credentials.from_json_array(resp.text) + + assert resp.status_code == HTTPStatus.OK assert len(actual_propagation_credentials) == 2 assert actual_propagation_credentials[0] == PROPAGATION_CREDENTIALS_1 assert actual_propagation_credentials[1] == PROPAGATION_CREDENTIALS_2 + + +@pytest.mark.parametrize("url", [CONFIGURED_CREDENTIALS_URL, STOLEN_CREDENTIALS_URL]) +def test_propagation_credentials_endpoint__post_stolen(flask_client, credentials_repository, url): + pre_populate_repository(url, credentials_repository, [PROPAGATION_CREDENTIALS_1]) + + resp = flask_client.post( + url, + json=[ + Credentials.to_json(PROPAGATION_CREDENTIALS_2), + Credentials.to_json(PROPAGATION_CREDENTIALS_3), + ], + ) + assert resp.status_code == HTTPStatus.NO_CONTENT + + resp = flask_client.get(url) + retrieved_propagation_credentials = Credentials.from_json_array(resp.text) + + assert resp.status_code == HTTPStatus.OK + assert len(retrieved_propagation_credentials) == 3 + assert PROPAGATION_CREDENTIALS_1 in retrieved_propagation_credentials + assert PROPAGATION_CREDENTIALS_2 in retrieved_propagation_credentials + assert PROPAGATION_CREDENTIALS_3 in retrieved_propagation_credentials + + +@pytest.mark.parametrize("url", [CONFIGURED_CREDENTIALS_URL, STOLEN_CREDENTIALS_URL]) +def test_stolen_propagation_credentials_endpoint_delete(flask_client, credentials_repository, url): + pre_populate_repository( + url, credentials_repository, [PROPAGATION_CREDENTIALS_1, PROPAGATION_CREDENTIALS_2] + ) + resp = flask_client.delete(url) + assert resp.status_code == HTTPStatus.NO_CONTENT + + resp = flask_client.get(url) + assert len(json.loads(resp.text)) == 0 + + +def test_propagation_credentials_endpoint__propagation_credentials_post_not_allowed(flask_client): + resp = flask_client.post(ALL_CREDENTIALS_URL, json=[]) + assert resp.status_code == HTTPStatus.METHOD_NOT_ALLOWED + + +NON_EXISTENT_COLLECTION_URL = urljoin(ALL_CREDENTIALS_URL, "bogus-credentials") + + +def test_propagation_credentials_endpoint__get_not_found(flask_client): + resp = flask_client.get(NON_EXISTENT_COLLECTION_URL) + assert resp.status_code == HTTPStatus.NOT_FOUND + + +def test_propagation_credentials_endpoint__post_not_found(flask_client): + resp = flask_client.post( + NON_EXISTENT_COLLECTION_URL, + json=[ + Credentials.to_json(PROPAGATION_CREDENTIALS_2), + Credentials.to_json(PROPAGATION_CREDENTIALS_3), + ], + ) + assert resp.status_code == HTTPStatus.NOT_FOUND + + +def test_propagation_credentials_endpoint__delete_not_found(flask_client): + resp = flask_client.delete(NON_EXISTENT_COLLECTION_URL) + assert resp.status_code == HTTPStatus.NOT_FOUND