Merge pull request #1318 from guardicore/ransomware_quickstart_endpoint

Ransomware quickstart endpoint
This commit is contained in:
Mike Salvatore 2021-07-13 11:05:23 -04:00 committed by GitHub
commit c89416f256
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 144 additions and 4 deletions

View File

@ -26,6 +26,7 @@ from monkey_island.cc.resources.edge import Edge
from monkey_island.cc.resources.environment import Environment from monkey_island.cc.resources.environment import Environment
from monkey_island.cc.resources.island_configuration import IslandConfiguration from monkey_island.cc.resources.island_configuration import IslandConfiguration
from monkey_island.cc.resources.island_logs import IslandLog from monkey_island.cc.resources.island_logs import IslandLog
from monkey_island.cc.resources.island_mode import IslandMode
from monkey_island.cc.resources.local_run import LocalRun from monkey_island.cc.resources.local_run import LocalRun
from monkey_island.cc.resources.log import Log from monkey_island.cc.resources.log import Log
from monkey_island.cc.resources.monkey import Monkey from monkey_island.cc.resources.monkey import Monkey
@ -132,6 +133,8 @@ def init_api_resources(api):
api.add_resource( api.add_resource(
Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/<string:monkey_guid>" Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/<string:monkey_guid>"
) )
api.add_resource(IslandMode, "/api/island-mode")
api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/")
api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/")
api.add_resource(ConfigurationExport, "/api/configuration/export") api.add_resource(ConfigurationExport, "/api/configuration/export")

View File

@ -0,0 +1,5 @@
from mongoengine import Document, StringField
class IslandMode(Document):
mode = StringField()

View File

@ -0,0 +1,28 @@
import json
import logging
import flask_restful
from flask import make_response, request
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.mode.island_mode_service import set_mode
from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
logger = logging.getLogger(__name__)
class IslandMode(flask_restful.Resource):
@jwt_required
def post(self):
try:
body = json.loads(request.data)
mode_str = body.get("mode")
mode = IslandModeEnum(mode_str)
set_mode(mode)
return make_response({}, 200)
except (AttributeError, json.decoder.JSONDecodeError):
return make_response({}, 400)
except ValueError:
return make_response({}, 422)

View File

@ -0,0 +1,8 @@
from monkey_island.cc.models.island_mode_model import IslandMode
from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
def set_mode(mode: IslandModeEnum):
island_mode_model = IslandMode()
island_mode_model.mode = mode.value
island_mode_model.save()

View File

@ -0,0 +1,6 @@
from enum import Enum
class IslandModeEnum(Enum):
RANSOMWARE = "ransomware"
ADVANCED = "advanced"

View File

@ -0,0 +1,32 @@
import flask_jwt_extended
import flask_restful
import pytest
from flask import Flask
import monkey_island.cc.app
import monkey_island.cc.resources.auth.auth
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)
with mock_init_app().test_client() as client:
yield client
def mock_init_app():
app = Flask(__name__)
api = flask_restful.Api(app)
api.representations = {"application/json": output_json}
monkey_island.cc.app.init_app_url_rules(app)
monkey_island.cc.app.init_api_resources(api)
return app

View File

@ -0,0 +1,56 @@
import json
import pytest
from tests.utils import raise_
from monkey_island.cc.models.island_mode_model import IslandMode
from monkey_island.cc.resources import island_mode as island_mode_resource
@pytest.fixture(scope="function")
def uses_database():
IslandMode.objects().delete()
@pytest.mark.parametrize("mode", ["ransomware", "advanced"])
def test_island_mode_post(flask_client, mode):
resp = flask_client.post(
"/api/island-mode", data=json.dumps({"mode": mode}), follow_redirects=True
)
assert resp.status_code == 200
def test_island_mode_post__invalid_mode(flask_client):
resp = flask_client.post(
"/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True
)
assert resp.status_code == 422
@pytest.mark.parametrize("invalid_json", ["42", "{test"])
def test_island_mode_post__invalid_json(flask_client, invalid_json):
resp = flask_client.post("/api/island-mode", data="{test", follow_redirects=True)
assert resp.status_code == 400
def test_island_mode_post__internal_server_error(monkeypatch, flask_client):
monkeypatch.setattr(island_mode_resource, "set_mode", lambda x: raise_(Exception()))
resp = flask_client.post(
"/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True
)
assert resp.status_code == 500
def test_island_mode_post__set_model(flask_client, uses_database):
flask_client.post(
"/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True
)
assert IslandMode.objects[0].mode == "ransomware"
def test_island_mode_post__set_invalid_model(flask_client, uses_database):
flask_client.post(
"/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True
)
assert len(IslandMode.objects) == 0

View File

@ -2,15 +2,12 @@ import os
import pytest import pytest
from tests.monkey_island.utils import assert_windows_permissions from tests.monkey_island.utils import assert_windows_permissions
from tests.utils import raise_
from monkey_island.cc.server_utils.file_utils import is_windows_os from monkey_island.cc.server_utils.file_utils import is_windows_os
from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.post_breach_files import PostBreachFilesService
def raise_(ex):
raise ex
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def custom_pba_directory(tmpdir): def custom_pba_directory(tmpdir):
PostBreachFilesService.initialize(tmpdir) PostBreachFilesService.initialize(tmpdir)

View File

@ -18,3 +18,7 @@ def hash_file(filepath: Path):
sha256.update(block) sha256.update(block)
return sha256.hexdigest() return sha256.hexdigest()
def raise_(ex):
raise ex

View File

@ -171,6 +171,7 @@ MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/
import_status # monkey_island\cc\resources\configuration_import.py:19 import_status # monkey_island\cc\resources\configuration_import.py:19
config_schema # monkey_island\cc\resources\configuration_import.py:25 config_schema # monkey_island\cc\resources\configuration_import.py:25
exception_stream # unused attribute (monkey_island/cc/server_setup.py:104) exception_stream # unused attribute (monkey_island/cc/server_setup.py:104)
ADVANCED # unused attribute (monkey/monkey_island/cc/services/mode/mode_enum.py:6:)
# these are not needed for it to work, but may be useful extra information to understand what's going on # these are not needed for it to work, but may be useful extra information to understand what's going on
WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23)