From 12eee922640936ff8235255d797c389a008277a4 Mon Sep 17 00:00:00 2001 From: rbowden-r7 <144030336+rbowden-r7@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:40:39 +0000 Subject: [PATCH] [PLGN-429] - Sentinelone -Adding new action for move agents between sites (#2097) (#2098) * [PLGN-429] - Sentinelone -Adding new action for move agents between sites (#2097) * PLGN-429-Adding new action for move agents between sites * PLGN-429-renaming var from filet to agents_filter * PLGN-429-making ID capital letetrs and rewording version history text * PLGN-429-making ID capital letetrs and rewording version history text * PLGN-429-Adding in required user permissons to description * PLGN-429-rewording required user permissons to description * PLGN-429-Making sure type string is not in quotes, minor fix to description of action (#2099) * PLGN-429-Making sure type string is not in quotes, minor fix to description of action * PLGN-429-Making sure type string is not in quotes, minor fix to description of action --- plugins/sentinelone/.CHECKSUM | 10 ++- plugins/sentinelone/Dockerfile | 2 +- plugins/sentinelone/bin/komand_sentinelone | 4 +- plugins/sentinelone/help.md | 39 +++++++++++ .../komand_sentinelone/actions/__init__.py | 2 + .../actions/move_between_sites/__init__.py | 2 + .../actions/move_between_sites/action.py | 26 +++++++ .../actions/move_between_sites/schema.py | 67 +++++++++++++++++++ .../komand_sentinelone/util/api.py | 5 ++ plugins/sentinelone/plugin.spec.yaml | 25 ++++++- plugins/sentinelone/setup.py | 2 +- .../expected/move_between_sites_data.json.exp | 3 + .../move_between_sites_minimum.json.exp | 3 + .../inputs/move_between_sites_data.json.inp | 6 ++ .../move_between_sites_minimum.json.inp | 3 + .../move_between_sites_data.json.resp | 5 ++ .../move_between_sites_minimum.json.resp | 5 ++ .../unit_test/test_move_between_sites.py | 36 ++++++++++ plugins/sentinelone/unit_test/util.py | 5 ++ 19 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 plugins/sentinelone/komand_sentinelone/actions/move_between_sites/__init__.py create mode 100644 plugins/sentinelone/komand_sentinelone/actions/move_between_sites/action.py create mode 100644 plugins/sentinelone/komand_sentinelone/actions/move_between_sites/schema.py create mode 100644 plugins/sentinelone/unit_test/expected/move_between_sites_data.json.exp create mode 100644 plugins/sentinelone/unit_test/expected/move_between_sites_minimum.json.exp create mode 100644 plugins/sentinelone/unit_test/inputs/move_between_sites_data.json.inp create mode 100644 plugins/sentinelone/unit_test/inputs/move_between_sites_minimum.json.inp create mode 100644 plugins/sentinelone/unit_test/responses/move_between_sites_data.json.resp create mode 100644 plugins/sentinelone/unit_test/responses/move_between_sites_minimum.json.resp create mode 100644 plugins/sentinelone/unit_test/test_move_between_sites.py diff --git a/plugins/sentinelone/.CHECKSUM b/plugins/sentinelone/.CHECKSUM index 23f92ed3d5..35525054c5 100644 --- a/plugins/sentinelone/.CHECKSUM +++ b/plugins/sentinelone/.CHECKSUM @@ -1,7 +1,7 @@ { - "spec": "13e105b48bad02633d278585e2eaa1ad", - "manifest": "b1b091ebaf5b638a9ba11717ac781941", - "setup": "7d1b8e3c1277b10ecbb42346e6c39fa8", + "spec": "dea9bcebb2466c647f32cff50ff770a2", + "manifest": "d22df2cb908420ee272ffd40aa540d14", + "setup": "3d39e9768c2fda42e7f413e0d7681642", "schemas": [ { "identifier": "activities_list/schema.py", @@ -91,6 +91,10 @@ "identifier": "mitigate_threat/schema.py", "hash": "7455705e503928685319d34cd1d732bd" }, + { + "identifier": "move_between_sites/schema.py", + "hash": "da998f50504a8bd40a9696bd6612ebe2" + }, { "identifier": "name_available/schema.py", "hash": "84bd100d0e08e0252dd92c65cb193d1f" diff --git a/plugins/sentinelone/Dockerfile b/plugins/sentinelone/Dockerfile index 5cd8ac3d8e..9b3fe60fb4 100755 --- a/plugins/sentinelone/Dockerfile +++ b/plugins/sentinelone/Dockerfile @@ -12,7 +12,7 @@ RUN if [ -f requirements.txt ]; then pip install -r requirements.txt; fi ADD . /python/src -RUN python setup.py build && python setup.py install +RUN python setup.py build && python setup.py install # User to run plugin code. The two supported users are: root, nobody USER nobody diff --git a/plugins/sentinelone/bin/komand_sentinelone b/plugins/sentinelone/bin/komand_sentinelone index 9101cf1981..e3c5c40ddc 100644 --- a/plugins/sentinelone/bin/komand_sentinelone +++ b/plugins/sentinelone/bin/komand_sentinelone @@ -6,7 +6,7 @@ from sys import argv Name = "SentinelOne" Vendor = "rapid7" -Version = "9.0.0" +Version = "9.1.0" Description = "The SentinelOne plugin allows you to manage and mitigate all your security operations through SentinelOne" @@ -94,6 +94,8 @@ def main(): self.add_action(actions.RunRemoteScript()) + self.add_action(actions.MoveBetweenSites()) + """Run plugin""" cli = insightconnect_plugin_runtime.CLI(ICONSentinelone()) diff --git a/plugins/sentinelone/help.md b/plugins/sentinelone/help.md index d0b96fa0a9..e221247a46 100644 --- a/plugins/sentinelone/help.md +++ b/plugins/sentinelone/help.md @@ -1647,6 +1647,44 @@ Example output: } ``` +#### Move Agent to Another Site + +This action is used to move an agent to another site. This action requires Account or Global level access for your user role. + +##### Input + +|Name|Type|Default|Required|Description|Enum|Example| +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +|filter|object|None|False|Applied filter - only matched agents will be affected by the requested action. Leave empty to apply the action on all applicable agents|None|{"ids": ["1000000000000000000"]}| +|targetSiteId|string|None|True|The ID of the new site to move the agents to|None|1000000000000000000| + +Example input: + +``` +{ + "filter": { + "ids": [ + "1000000000000000000" + ] + }, + "targetSiteId": 1000000000000000000 +} +``` + +##### Output + +|Name|Type|Required|Description|Example| +| :--- | :--- | :--- | :--- | :--- | +|affected|integer|False|Number of entities affected by the requested operation|1| + +Example output: + +``` +{ + "affected": 1 +} +``` + ### Triggers #### Get Threats @@ -2097,6 +2135,7 @@ Example output: # Version History +* 9.1.0 - `Move Agent to Another Site`: Action added * 9.0.0 - Update plugin to allow cloud connections to be configured | Rename URL input to Instance in connection | Code refactor * 8.1.0 - Added New actions: Fetch file for agent ID and Run remote script. Updated description for Trigger resolved field * 8.0.1 - Search Agents: Remove duplicate results when Case Sensitive is false diff --git a/plugins/sentinelone/komand_sentinelone/actions/__init__.py b/plugins/sentinelone/komand_sentinelone/actions/__init__.py index 753b854a74..d8ac5c42f9 100755 --- a/plugins/sentinelone/komand_sentinelone/actions/__init__.py +++ b/plugins/sentinelone/komand_sentinelone/actions/__init__.py @@ -58,3 +58,5 @@ from .run_remote_script.action import RunRemoteScript +from .move_between_sites.action import MoveBetweenSites + diff --git a/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/__init__.py b/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/__init__.py new file mode 100644 index 0000000000..edf1128979 --- /dev/null +++ b/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/__init__.py @@ -0,0 +1,2 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +from .action import MoveBetweenSites diff --git a/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/action.py b/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/action.py new file mode 100644 index 0000000000..154f04302a --- /dev/null +++ b/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/action.py @@ -0,0 +1,26 @@ +import insightconnect_plugin_runtime +from .schema import MoveBetweenSitesInput, MoveBetweenSitesOutput, Input, Output, Component + +# Custom imports below + + +class MoveBetweenSites(insightconnect_plugin_runtime.Action): + def __init__(self): + super(self.__class__, self).__init__( + name="move_between_sites", + description=Component.DESCRIPTION, + input=MoveBetweenSitesInput(), + output=MoveBetweenSitesOutput(), + ) + + def run(self, params={}): + agents_filter = params.get(Input.FILTER, {}) + data = {"targetSiteId": (params.get(Input.TARGETSITEID, ""))} + + return { + Output.AFFECTED: self.connection.client.agents_action_move_agent_to_new_site( + agents_filter, data, "move-to-site" + ) + .get("data", {}) + .get("affected", 0) + } diff --git a/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/schema.py b/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/schema.py new file mode 100644 index 0000000000..5b9dea86a2 --- /dev/null +++ b/plugins/sentinelone/komand_sentinelone/actions/move_between_sites/schema.py @@ -0,0 +1,67 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +import insightconnect_plugin_runtime +import json + + +class Component: + DESCRIPTION = "Move an agent to another site. This action requires Account or Global level access for your user role" + + +class Input: + FILTER = "filter" + TARGETSITEID = "targetSiteId" + + +class Output: + AFFECTED = "affected" + + +class MoveBetweenSitesInput(insightconnect_plugin_runtime.Input): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "filter": { + "type": "object", + "title": "Filter JSON", + "description": "Applied filter - only matched agents will be affected by the requested action. Leave empty to apply the action on all applicable agents", + "order": 2 + }, + "targetSiteId": { + "type": "string", + "title": "Target Site ID", + "description": "The ID of the new site to move the agents to", + "order": 1 + } + }, + "required": [ + "targetSiteId" + ], + "definitions": {} +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) + + +class MoveBetweenSitesOutput(insightconnect_plugin_runtime.Output): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "affected": { + "type": "integer", + "title": "Affected", + "description": "Number of entities affected by the requested operation", + "order": 1 + } + }, + "definitions": {} +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) diff --git a/plugins/sentinelone/komand_sentinelone/util/api.py b/plugins/sentinelone/komand_sentinelone/util/api.py index 597b30c6cc..252ad50f8a 100644 --- a/plugins/sentinelone/komand_sentinelone/util/api.py +++ b/plugins/sentinelone/komand_sentinelone/util/api.py @@ -67,6 +67,11 @@ def apps_by_agent_ids(self, identifiers: str) -> list: def agents_action(self, action: str, agents_filter: str) -> dict: return self._call_api("POST", AGENTS_ACTION_ENDPOINT.format(action=action), json={"filter": agents_filter}) + def agents_action_move_agent_to_new_site(self, agents_filter: dict, data: dict, action: str) -> dict: + return self._call_api( + "POST", AGENTS_ACTION_ENDPOINT.format(action=action), json={"filter": agents_filter, "data": data} + ) + def agents_support_action(self, action: str, json_data: dict) -> dict: return self._call_api("POST", AGENTS_SUPPORT_ACTION_ENDPOINT.format(action=action), json=json_data) diff --git a/plugins/sentinelone/plugin.spec.yaml b/plugins/sentinelone/plugin.spec.yaml index 8c447505d5..98318ced31 100644 --- a/plugins/sentinelone/plugin.spec.yaml +++ b/plugins/sentinelone/plugin.spec.yaml @@ -3,7 +3,7 @@ extension: plugin products: [insightconnect] name: sentinelone title: SentinelOne -version: 9.0.0 +version: 9.1.0 connection_version: 9 cloud_ready: true sdk: @@ -3044,6 +3044,29 @@ actions: type: integer required: true example: 1 + move_between_sites: + title: Move Agent to Another Site + description: Move an agent to another site, This action requires Account or Global level access for your user role + input: + targetSiteId: + title: Target Site ID + description: The ID of the new site to move the agents to + type: string + example: "1000000000000000000" + required: true + filter: + title: Filter JSON + description: Applied filter - only matched agents will be affected by the requested action. Leave empty to apply the action on all applicable agents + type: object + required: false + example: '{"ids": ["1000000000000000000"]}' + output: + affected: + title: Affected + description: Number of entities affected by the requested operation + type: integer + required: false + example: 1 triggers: get_threats: title: Get Threats diff --git a/plugins/sentinelone/setup.py b/plugins/sentinelone/setup.py index b2b8fba1fa..a9fe023d03 100644 --- a/plugins/sentinelone/setup.py +++ b/plugins/sentinelone/setup.py @@ -3,7 +3,7 @@ setup(name="sentinelone-rapid7-plugin", - version="9.0.0", + version="9.1.0", description="The SentinelOne plugin allows you to manage and mitigate all your security operations through SentinelOne", author="rapid7", author_email="", diff --git a/plugins/sentinelone/unit_test/expected/move_between_sites_data.json.exp b/plugins/sentinelone/unit_test/expected/move_between_sites_data.json.exp new file mode 100644 index 0000000000..41f56fd1af --- /dev/null +++ b/plugins/sentinelone/unit_test/expected/move_between_sites_data.json.exp @@ -0,0 +1,3 @@ +{ + "affected": 1 +} \ No newline at end of file diff --git a/plugins/sentinelone/unit_test/expected/move_between_sites_minimum.json.exp b/plugins/sentinelone/unit_test/expected/move_between_sites_minimum.json.exp new file mode 100644 index 0000000000..ac01db44f1 --- /dev/null +++ b/plugins/sentinelone/unit_test/expected/move_between_sites_minimum.json.exp @@ -0,0 +1,3 @@ +{ + "affected": 2 +} \ No newline at end of file diff --git a/plugins/sentinelone/unit_test/inputs/move_between_sites_data.json.inp b/plugins/sentinelone/unit_test/inputs/move_between_sites_data.json.inp new file mode 100644 index 0000000000..0694944bf1 --- /dev/null +++ b/plugins/sentinelone/unit_test/inputs/move_between_sites_data.json.inp @@ -0,0 +1,6 @@ +{ + "targetSiteId" : "1234567891234567891", + "filter": { + "computerName": "WindowsX64" + } +} \ No newline at end of file diff --git a/plugins/sentinelone/unit_test/inputs/move_between_sites_minimum.json.inp b/plugins/sentinelone/unit_test/inputs/move_between_sites_minimum.json.inp new file mode 100644 index 0000000000..2639db8fcb --- /dev/null +++ b/plugins/sentinelone/unit_test/inputs/move_between_sites_minimum.json.inp @@ -0,0 +1,3 @@ +{ + "targetSiteId" : "1234567891234567890" +} \ No newline at end of file diff --git a/plugins/sentinelone/unit_test/responses/move_between_sites_data.json.resp b/plugins/sentinelone/unit_test/responses/move_between_sites_data.json.resp new file mode 100644 index 0000000000..2987310e4b --- /dev/null +++ b/plugins/sentinelone/unit_test/responses/move_between_sites_data.json.resp @@ -0,0 +1,5 @@ +{ + "data": { + "affected": 1 + } +} diff --git a/plugins/sentinelone/unit_test/responses/move_between_sites_minimum.json.resp b/plugins/sentinelone/unit_test/responses/move_between_sites_minimum.json.resp new file mode 100644 index 0000000000..dc4f9606f2 --- /dev/null +++ b/plugins/sentinelone/unit_test/responses/move_between_sites_minimum.json.resp @@ -0,0 +1,5 @@ +{ + "data": { + "affected": 2 + } +} \ No newline at end of file diff --git a/plugins/sentinelone/unit_test/test_move_between_sites.py b/plugins/sentinelone/unit_test/test_move_between_sites.py new file mode 100644 index 0000000000..96e2038cc6 --- /dev/null +++ b/plugins/sentinelone/unit_test/test_move_between_sites.py @@ -0,0 +1,36 @@ +import sys +import os + +sys.path.append(os.path.abspath("../")) + +from unittest import TestCase +from unittest.mock import patch +from komand_sentinelone.actions.move_between_sites import MoveBetweenSites +from util import Util +from parameterized import parameterized + + +@patch("requests.request", side_effect=Util.mocked_requests_get) +class TestMoveBetweenSites(TestCase): + @classmethod + @patch("requests.post", side_effect=Util.mocked_requests_get) + def setUpClass(cls, mock_request) -> None: + cls.action = Util.default_connector(MoveBetweenSites()) + + @parameterized.expand( + [ + [ + "move_between_sites_minimum", + Util.read_file_to_dict("inputs/move_between_sites_minimum.json.inp"), + Util.read_file_to_dict("expected/move_between_sites_minimum.json.exp"), + ], + [ + "move_between_sites_data", + Util.read_file_to_dict("inputs/move_between_sites_data.json.inp"), + Util.read_file_to_dict("expected/move_between_sites_data.json.exp"), + ], + ] + ) + def test_agents_action(self, mock_request, test_name, input_params, expected): + actual = self.action.run(input_params) + self.assertEqual(expected, actual) diff --git a/plugins/sentinelone/unit_test/util.py b/plugins/sentinelone/unit_test/util.py index b0a8328636..2e1017a909 100644 --- a/plugins/sentinelone/unit_test/util.py +++ b/plugins/sentinelone/unit_test/util.py @@ -357,4 +357,9 @@ def raise_for_status(self): return MockResponse(200, "get_events_success_page_2") else: return MockResponse(200, "get_events_success_page_1") + elif args[1] == "https://rapid7.sentinelone.net/web/api/v2.1/agents/actions/move-to-site": + if json_data.get("data", {}).get("targetSiteId", "") == "1234567891234567890": + return MockResponse(200, "move_between_sites_minimum") + elif json_data.get("data", {}).get("targetSiteId", "") == "1234567891234567891": + return MockResponse(200, "move_between_sites_data") return MockResponse(404)