diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 172513d91..0642f1768 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -20,6 +20,7 @@ from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import ( ) from monkey_island.cc.resources.bootloader import Bootloader from monkey_island.cc.resources.client_run import ClientRun +from monkey_island.cc.resources.configuration_export import ConfigurationExport from monkey_island.cc.resources.edge import Edge from monkey_island.cc.resources.environment import Environment from monkey_island.cc.resources.island_configuration import IslandConfiguration @@ -135,6 +136,7 @@ def init_api_resources(api): ) api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") + api.add_resource(ConfigurationExport, "/api/configuration/export", "/api/configuration/export/") api.add_resource( MonkeyDownload, "/api/monkey/download", diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py new file mode 100644 index 000000000..4cecce634 --- /dev/null +++ b/monkey/monkey_island/cc/resources/configuration_export.py @@ -0,0 +1,42 @@ +import json +import os + +import flask_restful +from flask import jsonify, request + +from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.utils.file_handler import encrypt_file_with_password + + +class ConfigurationExport(flask_restful.Resource): + @jwt_required + def get(self): + return jsonify( + config_encrypted=self.file_encryption_successful, + plaintext_removed=self.plaintext_file_removal_successful, + ) + + @jwt_required + def post(self): + data = json.loads(request.data) + + config = ConfigService.get_config() + + config_filename = "monkey.conf" + plaintext_config_path = os.path.join(DEFAULT_DATA_DIR, config_filename) + with open(plaintext_config_path) as file: + json.dump(config, file) + + self.file_encryption_successful = self.plaintext_file_removal_successful = False + if "password" in data: + encrypted_config_path = os.path.join(DEFAULT_DATA_DIR, f"encrypted_{config_filename}") + ( + self.file_encryption_successful, + self.plaintext_file_removal_successful, + ) = encrypt_file_with_password( + plaintext_config_path, encrypted_config_path, data["password"] + ) + + return self.get() diff --git a/monkey/monkey_island/cc/services/utils/file_handler.py b/monkey/monkey_island/cc/services/utils/file_handler.py new file mode 100644 index 000000000..db71ea08a --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/file_handler.py @@ -0,0 +1,39 @@ +import logging +import os +from typing import Optional, Tuple + +import pyAesCrypt + +logger = logging.getLogger(__name__) + + +def encrypt_file_with_password( + plaintext_file_path: str, + encrypted_file_path: str, + password: str, + should_remove_plaintext_file: bool = True, +) -> Tuple[bool, Optional[bool]]: + + file_encryption_successful = False + try: + pyAesCrypt.encryptFile(plaintext_file_path, encrypted_file_path, password) + file_encryption_successful = True + except Exception as ex: + logger.error(f"Could not encrypt config file: {str(ex)}") + + plaintext_file_removal_successful = False + if file_encryption_successful and should_remove_plaintext_file: + plaintext_file_removal_successful = remove_file(plaintext_file_path) + + return file_encryption_successful, plaintext_file_removal_successful + + +def remove_file(path: str) -> bool: + file_removal_successful = False + try: + os.remove_file(path) + file_removal_successful = True + except Exception as ex: + logger.error(f"Could not remove plaintext file: {str(ex)}") + + return file_removal_successful