From c56c866263fb3f55967a32f2b3e436d543c0dc22 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 1 Aug 2022 12:32:49 +0200 Subject: [PATCH 1/7] Island: Rename `api/island-mode` to `api/island/mode` --- envs/monkey_zoo/blackbox/island_client/monkey_island_client.py | 2 +- monkey/monkey_island/cc/resources/island_mode.py | 2 +- monkey/monkey_island/cc/ui/src/components/Main.tsx | 2 +- monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx | 2 +- .../cc/ui/src/components/ui-components/IslandResetModal.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py index 4b1251aae..e287c8998 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -120,7 +120,7 @@ class MonkeyIslandClient(object): assert False def _reset_island_mode(self): - if self.requests.post("api/island-mode", data='{"mode": "unset"}').ok: + if self.requests.post("api/island/mode", data='{"mode": "unset"}').ok: LOGGER.info("Resseting island mode after the test.") else: LOGGER.error("Failed to reset island mode") diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 84f30c4ce..047320306 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -12,7 +12,7 @@ logger = logging.getLogger(__name__) class IslandMode(AbstractResource): - urls = ["/api/island-mode"] + urls = ["/api/island/mode"] def __init__(self, island_mode_service: IslandModeService): self._island_mode_service = island_mode_service diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index 4a4370294..d65c71a67 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -113,7 +113,7 @@ class AppComponent extends AuthComponent { }; setMode = () => { - return IslandHttpClient.get('/api/island-mode') + return IslandHttpClient.get('/api/island/mode') .then(res => { this.setState({islandMode: res.body.mode}); }); diff --git a/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx index d95a84dfe..8f89189ec 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx +++ b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx @@ -74,7 +74,7 @@ const LandingPageComponent = (props: Props) => { } function setScenario(scenario: string) { - IslandHttpClient.post('/api/island-mode', {'mode': scenario}) + IslandHttpClient.post('/api/island/mode', {'mode': scenario}) .then(() => { props.onStatusChange(); }); diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx index 639ee2c19..9717bb079 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx @@ -113,7 +113,7 @@ const IslandResetModal = (props: Props) => { }}) .then(res => { if (res.status === 200) { - return auth.authFetch('/api/island-mode', {method: 'POST', body: '{"mode": "unset"}'}) + return auth.authFetch('/api/island/mode', {method: 'POST', body: '{"mode": "unset"}'}) }}) .then(res => { if (res.status !== 200) { From 1b562e723f3e5884e71f1e2e746acdf5af2170ef Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 1 Aug 2022 12:55:25 +0200 Subject: [PATCH 2/7] Island: Change `POST` to `PUT` in `api/island/mode` --- .../blackbox/island_client/monkey_island_client.py | 2 +- .../blackbox/island_client/monkey_island_requests.py | 6 ++++++ monkey/monkey_island/cc/resources/island_mode.py | 12 ++++++------ .../cc/ui/src/components/IslandHttpClient.tsx | 4 ++-- .../cc/ui/src/components/pages/LandingPage.tsx | 2 +- .../components/ui-components/IslandResetModal.tsx | 2 +- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py index e287c8998..7b359abb3 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -120,7 +120,7 @@ class MonkeyIslandClient(object): assert False def _reset_island_mode(self): - if self.requests.post("api/island/mode", data='{"mode": "unset"}').ok: + if self.requests.put("api/island/mode", data='{"mode": "unset"}').ok: LOGGER.info("Resseting island mode after the test.") else: LOGGER.error("Failed to reset island mode") diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py index ff3b32e92..f26dc9e8a 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py @@ -82,6 +82,12 @@ class MonkeyIslandRequests(object): self.addr + url, data=data, headers=self.get_jwt_header(), verify=False ) + @_Decorators.refresh_jwt_token + def put(self, url, data): + return requests.put( # noqa: DUO123 + self.addr + url, data=data, headers=self.get_jwt_header(), verify=False + ) + @_Decorators.refresh_jwt_token def post_json(self, url, json: Dict): return requests.post( # noqa: DUO123 diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 047320306..2eb1ba627 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -1,5 +1,6 @@ import json import logging +from http import HTTPStatus from flask import make_response, request @@ -17,9 +18,8 @@ class IslandMode(AbstractResource): def __init__(self, island_mode_service: IslandModeService): self._island_mode_service = island_mode_service - # API Spec: Instead of POST, this should be PUT @jwt_required - def post(self): + def put(self): try: body = json.loads(request.data) mode = IslandModeEnum(body.get("mode")) @@ -29,13 +29,13 @@ class IslandMode(AbstractResource): # TODO: Do any of these returns need a body and make_response? What happens if we just # return the response code? # API Spec: This should be 204 (NO CONTENT) - return make_response({}, 200) + return make_response({}, HTTPStatus.NO_CONTENT) except (AttributeError, json.decoder.JSONDecodeError): - return make_response({}, 400) + return make_response({}, HTTPStatus.BAD_REQUEST) except ValueError: - return make_response({}, 422) + return make_response({}, HTTPStatus.UNPROCESSABLE_ENTITY) @jwt_required def get(self): island_mode = self._island_mode_service.get_mode() - return make_response({"mode": island_mode.value}, 200) + return make_response({"mode": island_mode.value}, HTTPStatus.OK) diff --git a/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx b/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx index 1c9571011..ef8d8adf5 100644 --- a/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx +++ b/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx @@ -12,11 +12,11 @@ export class Response{ } class IslandHttpClient extends AuthComponent { - post(endpoint: string, contents: any): Promise{ + put(endpoint: string, contents: any): Promise{ let status = null; return this.authFetch(endpoint, { - method: 'POST', + method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(contents) }) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx index 8f89189ec..3c2bf1037 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx +++ b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx @@ -74,7 +74,7 @@ const LandingPageComponent = (props: Props) => { } function setScenario(scenario: string) { - IslandHttpClient.post('/api/island/mode', {'mode': scenario}) + IslandHttpClient.put('/api/island/mode', {'mode': scenario}) .then(() => { props.onStatusChange(); }); diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx index 9717bb079..e3f798799 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx @@ -113,7 +113,7 @@ const IslandResetModal = (props: Props) => { }}) .then(res => { if (res.status === 200) { - return auth.authFetch('/api/island/mode', {method: 'POST', body: '{"mode": "unset"}'}) + return auth.authFetch('/api/island/mode', {method: 'PUT', body: '{"mode": "unset"}'}) }}) .then(res => { if (res.status !== 200) { From b69b0468c58d6aad2e17ab15e7b76f5e42db3f9c Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 1 Aug 2022 14:07:09 +0200 Subject: [PATCH 3/7] Island: Use HTTPStatus in `api/island/mode` resource --- monkey/monkey_island/cc/resources/island_mode.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 2eb1ba627..5bd695b3d 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -26,14 +26,11 @@ class IslandMode(AbstractResource): self._island_mode_service.set_mode(mode) - # TODO: Do any of these returns need a body and make_response? What happens if we just - # return the response code? - # API Spec: This should be 204 (NO CONTENT) - return make_response({}, HTTPStatus.NO_CONTENT) + return {}, HTTPStatus.NO_CONTENT except (AttributeError, json.decoder.JSONDecodeError): - return make_response({}, HTTPStatus.BAD_REQUEST) + return {}, HTTPStatus.BAD_REQUEST except ValueError: - return make_response({}, HTTPStatus.UNPROCESSABLE_ENTITY) + return {}, HTTPStatus.UNPROCESSABLE_ENTITY @jwt_required def get(self): From 0dabfe53ba4e30ba19da460f1907ffadc0fd9d4a Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 1 Aug 2022 14:16:25 +0200 Subject: [PATCH 4/7] UI: Check if response is successful instead of http status code --- .../src/components/ui-components/IslandResetModal.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx index e3f798799..67f3256b4 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/IslandResetModal.tsx @@ -96,7 +96,7 @@ const IslandResetModal = (props: Props) => { function clearSimulationData(callback: () => void) { auth.authFetch('/api/clear-simulation-data', {method: 'POST'}) .then(res => { - if (res.status === 200) { + if (res.ok) { callback(); } }) @@ -104,19 +104,19 @@ const IslandResetModal = (props: Props) => { function resetAll() { auth.authFetch('/api/reset-agent-configuration', {method: 'POST'}) .then(res => { - if (res.status === 200) { + if (res.ok) { return auth.authFetch('/api/clear-simulation-data', {method: 'POST'}) }}) .then(res => { - if (res.status === 200) { + if (res.ok) { return auth.authFetch('/api/propagation-credentials/configured-credentials', {method: 'PUT', body:'[]'}) }}) .then(res => { - if (res.status === 200) { + if (res.ok) { return auth.authFetch('/api/island/mode', {method: 'PUT', body: '{"mode": "unset"}'}) }}) .then(res => { - if (res.status !== 200) { + if (!res.ok) { throw 'Error resetting the simulation' } }) From 9075f01032ca2416e9c05c5ea822005cd254b563 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 1 Aug 2022 14:19:49 +0200 Subject: [PATCH 5/7] Changelog: Add entry for renaming `api/island-mode` to `api/island/mode` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb5f05a03..23a9b53d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Agent configuration structure. #1996, #1998, #1961, #1997, #1994, #1741, #1761, #1695, #1605, #2028, #2003 - `/api/island-mode` to accept and return new "unset" mode. #2036 +- The `/api/island-mode` to `/api/island/mode`. #2106 ### Removed - VSFTPD exploiter. #1533 From 8e62bef8985db8b35612a865876037b8fe7f70cf Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 1 Aug 2022 15:05:36 +0200 Subject: [PATCH 6/7] UT: Fix `api/island/mode` tests --- .../monkey_island/cc/resources/test_island_mode.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index 780553eaa..8635784a4 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -36,14 +36,14 @@ def flask_client(build_flask_client): [IslandMode.RANSOMWARE.value, IslandMode.ADVANCED.value, IslandMode.UNSET.value], ) def test_island_mode_post(flask_client, mode): - resp = flask_client.post( + resp = flask_client.put( IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True ) - assert resp.status_code == 200 + assert resp.status_code == 204 def test_island_mode_post__invalid_mode(flask_client): - resp = flask_client.post( + resp = flask_client.put( IslandModeResource.urls[0], data=json.dumps({"mode": "bogus mode"}), follow_redirects=True ) assert resp.status_code == 422 @@ -51,7 +51,7 @@ def test_island_mode_post__invalid_mode(flask_client): @pytest.mark.parametrize("invalid_json", ["42", "{test"]) def test_island_mode_post__invalid_json(flask_client, invalid_json): - resp = flask_client.post(IslandModeResource.urls[0], data="{test", follow_redirects=True) + resp = flask_client.put(IslandModeResource.urls[0], data="{test", follow_redirects=True) assert resp.status_code == 400 @@ -63,7 +63,7 @@ def test_island_mode_post__internal_server_error(build_flask_client): container.register_instance(IslandModeService, mock_island_mode_service) with build_flask_client(container) as flask_client: - resp = flask_client.post( + resp = flask_client.put( IslandModeResource.urls[0], data=json.dumps({"mode": IslandMode.RANSOMWARE.value}), follow_redirects=True, @@ -74,7 +74,7 @@ def test_island_mode_post__internal_server_error(build_flask_client): @pytest.mark.parametrize("mode", [IslandMode.RANSOMWARE.value, IslandMode.ADVANCED.value]) def test_island_mode_endpoint(flask_client, mode): - flask_client.post( + flask_client.put( IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True ) resp = flask_client.get(IslandModeResource.urls[0], follow_redirects=True) @@ -83,7 +83,7 @@ def test_island_mode_endpoint(flask_client, mode): def test_island_mode_endpoint__invalid_mode(flask_client): - resp_post = flask_client.post( + resp_post = flask_client.put( IslandModeResource.urls[0], data=json.dumps({"mode": "bogus_mode"}), follow_redirects=True ) resp_get = flask_client.get(IslandModeResource.urls[0], follow_redirects=True) From f19d489fc61971d990905095e5a5e97e88b5b9f9 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 1 Aug 2022 17:04:36 +0200 Subject: [PATCH 7/7] UT: Use HTTPStatus in test_island_mode --- .../monkey_island/cc/resources/test_island_mode.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index 8635784a4..642c5c936 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -1,4 +1,5 @@ import json +from http import HTTPStatus from unittest.mock import MagicMock import pytest @@ -39,20 +40,20 @@ def test_island_mode_post(flask_client, mode): resp = flask_client.put( IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True ) - assert resp.status_code == 204 + assert resp.status_code == HTTPStatus.NO_CONTENT def test_island_mode_post__invalid_mode(flask_client): resp = flask_client.put( IslandModeResource.urls[0], data=json.dumps({"mode": "bogus mode"}), follow_redirects=True ) - assert resp.status_code == 422 + assert resp.status_code == HTTPStatus.UNPROCESSABLE_ENTITY @pytest.mark.parametrize("invalid_json", ["42", "{test"]) def test_island_mode_post__invalid_json(flask_client, invalid_json): resp = flask_client.put(IslandModeResource.urls[0], data="{test", follow_redirects=True) - assert resp.status_code == 400 + assert resp.status_code == HTTPStatus.BAD_REQUEST def test_island_mode_post__internal_server_error(build_flask_client): @@ -69,7 +70,7 @@ def test_island_mode_post__internal_server_error(build_flask_client): follow_redirects=True, ) - assert resp.status_code == 500 + assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR @pytest.mark.parametrize("mode", [IslandMode.RANSOMWARE.value, IslandMode.ADVANCED.value]) @@ -78,7 +79,7 @@ def test_island_mode_endpoint(flask_client, mode): IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True ) resp = flask_client.get(IslandModeResource.urls[0], follow_redirects=True) - assert resp.status_code == 200 + assert resp.status_code == HTTPStatus.OK assert json.loads(resp.data)["mode"] == mode @@ -87,5 +88,5 @@ def test_island_mode_endpoint__invalid_mode(flask_client): IslandModeResource.urls[0], data=json.dumps({"mode": "bogus_mode"}), follow_redirects=True ) resp_get = flask_client.get(IslandModeResource.urls[0], follow_redirects=True) - assert resp_post.status_code == 422 + assert resp_post.status_code == HTTPStatus.UNPROCESSABLE_ENTITY assert json.loads(resp_get.data)["mode"] == IslandMode.UNSET.value