Merge branch '1662-repository-service' into develop

PR #2085
This commit is contained in:
Mike Salvatore 2022-07-13 11:58:42 -04:00
commit 2074c37081
14 changed files with 106 additions and 202 deletions

View File

@ -2,22 +2,22 @@ from http import HTTPStatus
from flask import make_response
from monkey_island.cc.repository import IAgentConfigurationRepository
from monkey_island.cc.resources.AbstractResource import AbstractResource
from monkey_island.cc.resources.request_authentication import jwt_required
from monkey_island.cc.services import RepositoryService
class ResetAgentConfiguration(AbstractResource):
urls = ["/api/reset-agent-configuration"]
def __init__(self, agent_configuration_repository: IAgentConfigurationRepository):
self._agent_configuration_repository = agent_configuration_repository
def __init__(self, repository_service: RepositoryService):
self._repository_service = repository_service
@jwt_required
def post(self):
"""
Reset the agent configuration to its default values
"""
self._agent_configuration_repository.reset_to_default()
self._repository_service.reset_agent_configuration()
return make_response({}, HTTPStatus.OK)

View File

@ -2,3 +2,4 @@ from .authentication_service import AuthenticationService
from .aws import AWSService
from .island_mode_service import IslandModeService
from .repository_service import RepositoryService

View File

@ -138,11 +138,6 @@ class ConfigService:
ConfigService.set_config_value(PBA_LINUX_FILENAME_PATH, linux_filename)
ConfigService.set_config_value(PBA_WINDOWS_FILENAME_PATH, windows_filename)
@staticmethod
def init_config():
if ConfigService.get_config(should_decrypt=False) != {}:
return
@staticmethod
def decrypt_config(config):
ConfigService._encrypt_or_decrypt_config(config, True)

View File

@ -6,11 +6,11 @@ from monkey_island.cc.database import mongo
from monkey_island.cc.models import Config
from monkey_island.cc.models.agent_controls import AgentControls
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
from monkey_island.cc.services.config import ConfigService
logger = logging.getLogger(__name__)
# NOTE: This service is being replaced by the RepositoryService
class Database(object):
def __init__(self):
pass
@ -24,7 +24,6 @@ class Database(object):
for x in mongo.db.collection_names()
if Database._should_drop(x, reset_config)
]
ConfigService.init_config()
Database.init_agent_controls()
logger.info("DB was reset")
return jsonify(status="OK")

View File

@ -32,8 +32,7 @@ from monkey_island.cc.repository import (
)
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.server_utils.encryption import ILockableEncryptor, RepositoryEncryptor
from monkey_island.cc.services import AWSService, IslandModeService
from monkey_island.cc.services.post_breach_files import PostBreachFilesService
from monkey_island.cc.services import AWSService, IslandModeService, RepositoryService
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import (
CredentialsParser,
@ -70,7 +69,6 @@ def initialize_services(data_dir: Path) -> DIContainer:
_patch_credentials_parser(container)
# This is temporary until we get DI all worked out.
PostBreachFilesService.initialize(container.resolve(IFileRepository))
ReportService.initialize(container.resolve(AWSService))
return container
@ -148,6 +146,7 @@ def _register_services(container: DIContainer):
container.register_instance(LocalMonkeyRunService, container.resolve(LocalMonkeyRunService))
container.register_instance(IslandModeService, container.resolve(IslandModeService))
container.register_instance(AuthenticationService, container.resolve(AuthenticationService))
container.register_instance(RepositoryService, container.resolve(RepositoryService))
def _patch_credentials_parser(container: DIContainer):

View File

@ -1,24 +0,0 @@
import logging
from monkey_island.cc.repository import IFileRepository
logger = logging.getLogger(__name__)
# TODO: This service wraps an IFileStorageService for the sole purpose of making the
# `remove_PBA_files()` method available to the ConfigService. This whole service can be
# removed once ConfigService is refactored to be stateful (it already is but everything is
# still statically/globally scoped) and use dependency injection.
class PostBreachFilesService:
_file_storage_service = None
# TODO: A number of these services should be instance objects instead of
# static/singleton hybrids. At the moment, this requires invasive refactoring that's
# not a priority.
@classmethod
def initialize(cls, file_storage_service: IFileRepository):
cls._file_storage_service = file_storage_service
@classmethod
def remove_PBA_files(cls):
cls._file_storage_service.delete_all_files()

View File

@ -0,0 +1,40 @@
from monkey_island.cc.repository import IAgentConfigurationRepository, IFileRepository
class RepositoryService:
def __init__(
self,
agent_configuration_repository: IAgentConfigurationRepository,
file_repository: IFileRepository,
):
self._agent_configuration_repository = agent_configuration_repository
self._file_repository = file_repository
def reset_agent_configuration(self):
# NOTE: This method will be replaced by an event when we implement pub/sub in the island.
# Different plugins and components will be able to register for the event and reset
# their configurations.
self._remove_pba_files()
self._agent_configuration_repository.reset_to_default()
def _remove_pba_files(self):
agent_configuration = self._agent_configuration_repository.get_configuration()
custom_pbas = agent_configuration.custom_pbas
if custom_pbas.linux_filename:
self._file_repository.delete_file(custom_pbas.linux_filename)
if custom_pbas.windows_filename:
self._file_repository.delete_file(custom_pbas.windows_filename)
def unlock(self):
raise NotImplementedError
def reset_key(self):
raise NotImplementedError
def clear_simulation_data(self):
# NOTE: This method will be replaced by an event when we implement pub/sub in the island.
# Different plugins and components will be able to register for the event and clear
# any configuration data they've collected.
raise NotImplementedError

View File

@ -235,18 +235,9 @@ class ConfigurePageComponent extends AuthComponent {
this.props.onStatusChange();
}
)
.then(() => {
this.removePBAfile(API_PBA_WINDOWS, this.setPbaFilenameWindows)
this.removePBAfile(API_PBA_LINUX, this.setPbaFilenameLinux)
})
.then(this.authFetch(CONFIGURED_PROPAGATION_CREDENTIALS_URL, {method: 'DELETE'}));
.then(this.authFetch(CONFIGURED_PROPAGATION_CREDENTIALS_URL, {method: 'DELETE'})) ;
};
removePBAfile(apiEndpoint, setFilenameFnc) {
this.sendPbaRemoveRequest(apiEndpoint)
setFilenameFnc('')
}
sendPbaRemoveRequest(apiEndpoint) {
let request_options = {
method: 'DELETE',

View File

@ -1,92 +0,0 @@
{
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001,
9200
],
"PBA_linux_filename": "test.sh",
"PBA_windows_filename": "test.ps1",
"blocked_ips": ["192.168.1.1", "192.168.1.100"],
"custom_PBA_linux_cmd": "bash test.sh",
"custom_PBA_windows_cmd": "powershell test.ps1",
"depth": 2,
"exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"],
"exploit_ntlm_hash_list": ["nt_hash_1", "nt_hash_2", "nt_hash_3"],
"exploit_password_list": [
"test",
"iloveyou",
"12345"
],
"exploit_ssh_keys": [
{
"public_key": "my_public_key",
"private_key": "my_private_key"
}
],
"credential_collectors": ["MimikatzCollector", "SSHCollector"],
"exploit_user_list": [
"Administrator",
"root",
"user",
"ubuntu"
],
"exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"SSHExploiter",
"ZerologonExploiter",
"HadoopExploiter",
"MSSQLExploiter",
"PowerShellExploiter",
"Log4ShellExploiter"
],
"finger_classes": [
"SMBFinger",
"SSHFinger",
"HTTPFinger",
"MSSQLFinger",
"ElasticFinger"
],
"inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"],
"keep_tunnel_open_time": 60,
"local_network_scan": true,
"ping_scan_timeout": 1000,
"post_breach_actions": [
"CommunicateAsBackdoorUser",
"ModifyShellStartupFiles",
"ScheduleJobs",
"Timestomping",
"AccountDiscovery"
],
"ransomware": {
"encryption": {
"enabled": true,
"directories": {
"linux_target_dir": "/tmp/ransomware-target",
"windows_target_dir": "C:\\windows\\temp\\ransomware-target"
}
},
"other_behaviors": {
"readme": true
}
},
"subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"],
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
7001,
8088
]
}

View File

@ -1,7 +1,5 @@
import json
import sys
from pathlib import Path
from typing import Callable, Dict
import pytest
from _pytest.monkeypatch import MonkeyPatch
@ -49,15 +47,6 @@ def monkey_configs_dir(data_for_tests_dir) -> Path:
return data_for_tests_dir / "monkey_configs"
@pytest.fixture
def load_monkey_config(data_for_tests_dir) -> Callable[[str], Dict]:
def inner(filename: str) -> Dict:
config_path = data_for_tests_dir / "monkey_configs" / filename
return json.loads(open(config_path, "r").read())
return inner
@pytest.fixture
def default_agent_configuration() -> AgentConfiguration:
return DEFAULT_AGENT_CONFIGURATION

View File

@ -4,11 +4,6 @@ from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401
from monkey_island.cc.server_utils.encryption import unlock_datastore_encryptor
@pytest.fixture
def flat_monkey_config(load_monkey_config):
return load_monkey_config("flat_config.json")
@pytest.fixture
def uses_encryptor(data_for_tests_dir):
secret = "m0nk3y_u53r:3cr3t_p455w0rd"

View File

@ -1,43 +0,0 @@
import io
import os
import pytest
from tests.utils import raise_
from monkey_island.cc.repository import LocalStorageFileRepository
from monkey_island.cc.services.post_breach_files import PostBreachFilesService
@pytest.fixture
def local_storage_file_repository(tmp_path):
return LocalStorageFileRepository(tmp_path)
@pytest.fixture(autouse=True)
def post_breach_files_service(local_storage_file_repository):
PostBreachFilesService.initialize(local_storage_file_repository)
def test_remove_pba_files(local_storage_file_repository, tmp_path):
local_storage_file_repository.save_file("linux_file", io.BytesIO(b""))
local_storage_file_repository.save_file("windows_file", io.BytesIO(b""))
assert not dir_is_empty(tmp_path)
PostBreachFilesService.remove_PBA_files()
assert dir_is_empty(tmp_path)
def dir_is_empty(dir_path):
dir_contents = os.listdir(dir_path)
return len(dir_contents) == 0
def test_remove_failure(local_storage_file_repository, monkeypatch):
monkeypatch.setattr(os, "remove", lambda x: raise_(OSError("Permission denied")))
try:
local_storage_file_repository.save_file("windows_file", io.BytesIO(b""))
PostBreachFilesService.remove_PBA_files()
except Exception as ex:
pytest.fail(f"Unxepected exception: {ex}")

View File

@ -0,0 +1,57 @@
from dataclasses import replace
from unittest.mock import MagicMock
import pytest
from tests.monkey_island import InMemoryAgentConfigurationRepository
from common.configuration import AgentConfiguration
from monkey_island.cc.repository import IAgentConfigurationRepository, IFileRepository
from monkey_island.cc.services import RepositoryService
LINUX_FILENAME = "linux_pba_file.sh"
WINDOWS_FILENAME = "windows_pba_file.ps1"
@pytest.fixture
def agent_configuration(default_agent_configuration) -> AgentConfiguration:
custom_pbas = replace(
default_agent_configuration.custom_pbas,
linux_filename=LINUX_FILENAME,
windows_filename=WINDOWS_FILENAME,
)
return replace(default_agent_configuration, custom_pbas=custom_pbas)
@pytest.fixture
def agent_configuration_repository(agent_configuration) -> IAgentConfigurationRepository:
agent_configuration_repository = InMemoryAgentConfigurationRepository()
agent_configuration_repository.store_configuration(agent_configuration)
return agent_configuration_repository
@pytest.fixture
def mock_file_repository():
return MagicMock(spec=IFileRepository)
def test_reset_configuration__remove_pba_files(
agent_configuration_repository, mock_file_repository
):
repository_service = RepositoryService(agent_configuration_repository, mock_file_repository)
repository_service.reset_agent_configuration()
assert mock_file_repository.delete_file.called_with(LINUX_FILENAME)
assert mock_file_repository.delete_file.called_with(WINDOWS_FILENAME)
def test_reset_configuration__agent_configuration_changed(
agent_configuration_repository, agent_configuration, mock_file_repository
):
mock_file_repository = MagicMock(spec=IFileRepository)
repository_service = RepositoryService(agent_configuration_repository, mock_file_repository)
repository_service.reset_agent_configuration()
assert agent_configuration_repository.get_configuration() != agent_configuration

View File

@ -19,9 +19,6 @@ def patch_attack_mitigations_path(monkeypatch, data_for_tests_dir):
@pytest.fixture(scope="module", autouse=True)
def patch_dependencies(monkeypatch_session):
monkeypatch_session.setattr(
"monkey_island.cc.services.config.ConfigService.init_config", lambda: None
)
monkeypatch_session.setattr(
"monkey_island.cc.services.database.jsonify", MagicMock(return_value=True)
)