diff --git a/plugins/rapid7_surface_command/.CHECKSUM b/plugins/rapid7_surface_command/.CHECKSUM new file mode 100644 index 0000000000..0b36eb10bd --- /dev/null +++ b/plugins/rapid7_surface_command/.CHECKSUM @@ -0,0 +1,15 @@ +{ + "spec": "a4b099d042b1b84d6c0239f8784f1f5c", + "manifest": "2d22e59a97bcaaa3e9bd7cbb0810face", + "setup": "6aa89dde78114d142acbe7705a84fbc9", + "schemas": [ + { + "identifier": "run_query/schema.py", + "hash": "0f7bd73a9fe89c3311d451d5805e4dd8" + }, + { + "identifier": "connection/schema.py", + "hash": "134cb982f22fe0c4969721b954e838ef" + } + ] +} \ No newline at end of file diff --git a/plugins/rapid7_surface_command/.dockerignore b/plugins/rapid7_surface_command/.dockerignore new file mode 100644 index 0000000000..6da49864f5 --- /dev/null +++ b/plugins/rapid7_surface_command/.dockerignore @@ -0,0 +1,9 @@ +unit_test/**/* +unit_test +examples/**/* +examples +tests +tests/**/* +**/*.json +**/*.tar +**/*.gz diff --git a/plugins/rapid7_surface_command/Dockerfile b/plugins/rapid7_surface_command/Dockerfile new file mode 100644 index 0000000000..ee4de73042 --- /dev/null +++ b/plugins/rapid7_surface_command/Dockerfile @@ -0,0 +1,20 @@ +FROM --platform=linux/amd64 rapid7/insightconnect-python-3-plugin:latest + +LABEL organization=rapid7 +LABEL sdk=python + +WORKDIR /python/src + +ADD ./plugin.spec.yaml /plugin.spec.yaml +ADD ./requirements.txt /python/src/requirements.txt + +RUN if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + +ADD . /python/src + +RUN python setup.py build && python setup.py install + +# User to run plugin code. The two supported users are: root, nobody +USER nobody + +ENTRYPOINT ["/usr/local/bin/icon_rapid7_surface_command"] diff --git a/plugins/rapid7_surface_command/Makefile b/plugins/rapid7_surface_command/Makefile new file mode 100644 index 0000000000..cdbcdb1721 --- /dev/null +++ b/plugins/rapid7_surface_command/Makefile @@ -0,0 +1,53 @@ +# Include other Makefiles for improved functionality +INCLUDE_DIR = ../../tools/Makefiles +MAKEFILES := $(wildcard $(INCLUDE_DIR)/*.mk) +# We can't guarantee customers will have the include files +# - prefix to ignore Makefiles when not present +# https://www.gnu.org/software/make/manual/html_node/Include.html +-include $(MAKEFILES) + +ifneq ($(MAKEFILES),) + $(info [$(YELLOW)*$(NORMAL)] Use ``make menu`` for available targets) + $(info [$(YELLOW)*$(NORMAL)] Including available Makefiles: $(MAKEFILES)) + $(info --) +else + $(warning Makefile includes directory not present: $(INCLUDE_DIR)) +endif + +VERSION?=$(shell grep '^version: ' plugin.spec.yaml | sed 's/version: //') +NAME?=$(shell grep '^name: ' plugin.spec.yaml | sed 's/name: //') +VENDOR?=$(shell grep '^vendor: ' plugin.spec.yaml | sed 's/vendor: //') +CWD?=$(shell basename $(PWD)) +_NAME?=$(shell echo $(NAME) | awk '{ print toupper(substr($$0,1,1)) tolower(substr($$0,2)) }') +PKG=$(VENDOR)-$(NAME)-$(VERSION).tar.gz + +# Set default target explicitly. Make's default behavior is the first target in the Makefile. +# We don't want that behavior due to includes which are read first +.DEFAULT_GOAL := default # Make >= v3.80 (make -version) + + +default: image tarball + +tarball: + $(info [$(YELLOW)*$(NORMAL)] Creating plugin tarball) + rm -rf build + rm -rf $(PKG) + tar -cvzf $(PKG) --exclude=$(PKG) --exclude=tests --exclude=run.sh * + +image: + $(info [$(YELLOW)*$(NORMAL)] Building plugin image) + docker build --pull -t $(VENDOR)/$(NAME):$(VERSION) . + docker tag $(VENDOR)/$(NAME):$(VERSION) $(VENDOR)/$(NAME):latest + +regenerate: + $(info [$(YELLOW)*$(NORMAL)] Refreshing schema from plugin.spec.yaml) + insight-plugin refresh + +export: image + $(info [$(YELLOW)*$(NORMAL)] Exporting docker image) + @printf "\n ---> Exporting Docker image to ./$(VENDOR)_$(NAME)_$(VERSION).tar\n" + @docker save $(VENDOR)/$(NAME):$(VERSION) | gzip > $(VENDOR)_$(NAME)_$(VERSION).tar + +# Make will not run a target if a file of the same name exists unless setting phony targets +# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html +.PHONY: default tarball image regenerate diff --git a/plugins/rapid7_surface_command/bin/icon_rapid7_surface_command b/plugins/rapid7_surface_command/bin/icon_rapid7_surface_command new file mode 100644 index 0000000000..298dd372a8 --- /dev/null +++ b/plugins/rapid7_surface_command/bin/icon_rapid7_surface_command @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +import os +import json +from sys import argv + +Name = "Rapid7 Surface Command" +Vendor = "rapid7" +Version = "1.0.0" +Description = "Surface Command gives you full visibilty over your assets and identies across multiple technology platforms." + + +def main(): + if 'http' in argv: + if os.environ.get("GUNICORN_CONFIG_FILE"): + with open(os.environ.get("GUNICORN_CONFIG_FILE")) as gf: + gunicorn_cfg = json.load(gf) + if gunicorn_cfg.get("worker_class", "sync") == "gevent": + from gevent import monkey + monkey.patch_all() + elif 'gevent' in argv: + from gevent import monkey + monkey.patch_all() + + import insightconnect_plugin_runtime + from icon_rapid7_surface_command import connection, actions, triggers, tasks + + class ICONRapid7SurfaceCommand(insightconnect_plugin_runtime.Plugin): + def __init__(self): + super(self.__class__, self).__init__( + name=Name, + vendor=Vendor, + version=Version, + description=Description, + connection=connection.Connection() + ) + self.add_action(actions.RunQuery()) + + + """Run plugin""" + cli = insightconnect_plugin_runtime.CLI(ICONRapid7SurfaceCommand()) + cli.run() + + +if __name__ == "__main__": + main() diff --git a/plugins/rapid7_surface_command/help.md b/plugins/rapid7_surface_command/help.md new file mode 100644 index 0000000000..51e85b855f --- /dev/null +++ b/plugins/rapid7_surface_command/help.md @@ -0,0 +1,98 @@ +# Description + +Surface Command gives you full visibilty over your assets and identies across multiple technology platforms. + +# Key Features + +*This plugin does not contain any key features.* + +# Requirements + +*This plugin does not contain any requirements.* + +# Supported Product Versions + +* v1.0.790 + +# Documentation + +## Setup + +The connection configuration accepts the following parameters: + +|Name|Type|Default|Required|Description|Enum|Example|Placeholder|Tooltip| +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | +|api_key|credential_secret_key|None|True|User or Organization Key from the Insight Platform|None|a5zy0a6g-504e-46bz-84xx-1b3f5ci36l99|None|None| +|region|string|United States|True|Region|["United States", "United States 2", "United States 3", "Europe", "Canada", "Australia", "Japan"]|United States|None|None| + +Example input: + +``` +{ + "api_key": "a5zy0a6g-504e-46bz-84xx-1b3f5ci36l99", + "region": "United States" +} +``` + +## Technical Details + +### Actions + + +#### Run Surface Command Query + +This action is used to run and execute Surface Command Query + +##### Input + +|Name|Type|Default|Required|Description|Enum|Example|Placeholder|Tooltip| +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | +|query_id|string|None|True|Query ID (UUID) to run from Surface Command|None|12345678-1234-1234-1234-123456789012|None|None| + +Example input: + +``` +{ + "query_id": "12345678-1234-1234-1234-123456789012" +} +``` + +##### Output + +|Name|Type|Required|Description|Example| +| :--- | :--- | :--- | :--- | :--- | +|items|items|False|Array of Items|[]| + +Example output: + +``` +{ + "items": [] +} +``` +### Triggers + +*This plugin does not contain any triggers.* +### Tasks + +*This plugin does not contain any tasks.* + +### Custom Types + +*This plugin does not contain any custom output types.* + +## Troubleshooting + +*This plugin does not contain a troubleshooting.* + +# Version History + +*This plugin does not contain a version history.* + +# Links + +*This plugin does not contain any links.* + +## References + +*This plugin does not contain any references.* \ No newline at end of file diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/__init__.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/__init__.py new file mode 100644 index 0000000000..797e426edf --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/__init__.py @@ -0,0 +1 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/__init__.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/__init__.py new file mode 100644 index 0000000000..685f8aa67a --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/__init__.py @@ -0,0 +1,4 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT + +from .run_query.action import RunQuery + diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/__init__.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/__init__.py new file mode 100644 index 0000000000..d34104e5bc --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/__init__.py @@ -0,0 +1,2 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +from .action import RunQuery diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/action.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/action.py new file mode 100644 index 0000000000..3182e305f8 --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/action.py @@ -0,0 +1,22 @@ +import insightconnect_plugin_runtime +from .schema import RunQueryInput, RunQueryOutput, Input, Output, Component +# Custom imports below + + +class RunQuery(insightconnect_plugin_runtime.Action): + + def __init__(self): + super(self.__class__, self).__init__( + name="run_query", + description=Component.DESCRIPTION, + input=RunQueryInput(), + output=RunQueryOutput()) + + def run(self, params={}): + # START INPUT BINDING - DO NOT REMOVE - ANY INPUTS BELOW WILL UPDATE WITH YOUR PLUGIN SPEC AFTER REGENERATION + query_id = params.get(Input.QUERY_ID) + # END INPUT BINDING - DO NOT REMOVE + + return { + Output.ITEMS: None, + } diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/schema.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/schema.py new file mode 100644 index 0000000000..6c28f876a6 --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/actions/run_query/schema.py @@ -0,0 +1,59 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +import insightconnect_plugin_runtime +import json + + +class Component: + DESCRIPTION = "Run and execute Surface Command Query" + + +class Input: + QUERY_ID = "query_id" + + +class Output: + ITEMS = "items" + + +class RunQueryInput(insightconnect_plugin_runtime.Input): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "query_id": { + "type": "string", + "title": "ID of Query to run", + "description": "Query ID (UUID) to run from Surface Command", + "order": 1 + } + }, + "required": [ + "query_id" + ], + "definitions": {} +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) + + +class RunQueryOutput(insightconnect_plugin_runtime.Output): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "items": { + "title": "Items", + "description": "Array of Items", + "order": 1 + } + }, + "definitions": {} +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/__init__.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/__init__.py new file mode 100644 index 0000000000..c78d3356be --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/__init__.py @@ -0,0 +1,2 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +from .connection import Connection diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/connection.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/connection.py new file mode 100644 index 0000000000..6ed22557d7 --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/connection.py @@ -0,0 +1,20 @@ +import insightconnect_plugin_runtime +from .schema import ConnectionSchema, Input +# Custom imports below + + +class Connection(insightconnect_plugin_runtime.Connection): + + def __init__(self): + super(self.__class__, self).__init__(input=ConnectionSchema()) + + def connect(self, params): + self.logger.info("Connect: Connecting...") + # START INPUT BINDING - DO NOT REMOVE - ANY INPUTS BELOW WILL UPDATE WITH YOUR PLUGIN SPEC AFTER REGENERATION + self.api_key = params.get(Input.API_KEY) + self.region = params.get(Input.REGION) + # END INPUT BINDING - DO NOT REMOVE + + def test(self): + # TODO: Implement connection test + pass diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/schema.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/schema.py new file mode 100644 index 0000000000..5a6c1b2a08 --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/connection/schema.py @@ -0,0 +1,68 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +import insightconnect_plugin_runtime +import json + + +class Input: + API_KEY = "api_key" + REGION = "region" + + +class ConnectionSchema(insightconnect_plugin_runtime.Input): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "api_key": { + "$ref": "#/definitions/credential_secret_key", + "title": "API Key", + "description": "User or Organization Key from the Insight Platform", + "order": 1 + }, + "region": { + "type": "string", + "title": "Region", + "description": "Region", + "default": "United States", + "enum": [ + "United States", + "United States 2", + "United States 3", + "Europe", + "Canada", + "Australia", + "Japan" + ], + "order": 2 + } + }, + "required": [ + "api_key", + "region" + ], + "definitions": { + "credential_secret_key": { + "id": "credential_secret_key", + "type": "object", + "title": "Credential: Secret Key", + "description": "A shared secret key", + "required": [ + "secretKey" + ], + "properties": { + "secretKey": { + "type": "string", + "title": "Secret Key", + "description": "The shared secret key", + "format": "password", + "displayType": "password" + } + } + } + } +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/tasks/__init__.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/tasks/__init__.py new file mode 100644 index 0000000000..7020c9a4ad --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/tasks/__init__.py @@ -0,0 +1,2 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT + diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/triggers/__init__.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/triggers/__init__.py new file mode 100644 index 0000000000..7020c9a4ad --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/triggers/__init__.py @@ -0,0 +1,2 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT + diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/__init__.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/__init__.py new file mode 100644 index 0000000000..797e426edf --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/__init__.py @@ -0,0 +1 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/surface_command/__init__.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/surface_command/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/surface_command/api_connection.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/surface_command/api_connection.py new file mode 100644 index 0000000000..3c839510d5 --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/surface_command/api_connection.py @@ -0,0 +1,41 @@ +import logging + +import furl +import requests + +from constants import REGION_MAP +from insightconnect_plugin_runtime.exceptions import PluginException, ConnectionTestException +from insightconnect_plugin_runtime.helper import clean + + +class ApiConnection: + """ + ApiConnection(api_key, region_string, logger) + + A class to connect to the Surface Command API. This class provides convenience methods to perform actions + on Surface Command. + """ + + def __init__(self, api_key: str, region_string: str, logger: logging.Logger) -> None: + """ + Init the connection and set the region + """ + self.api_key = api_key + self.logger = logger + region = REGION_MAP.get(region_string) + self.url = f'https://{region}.api.insight.rapid7.com/surface/graph-api/objects/table' + + def run_query(self, query_id: str) -> dict: + """ + Execute Surface Command Query + """ + url = furl.furl(self.url).set(args={"format": "json"}) + response = requests.post( + url, + headers={ + "X-Api-Key": f"{self.api_key}" + }, + json={"query_id": query_id}, + ) + response.raise_for_status() + return response.json() diff --git a/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/surface_command/constants.py b/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/surface_command/constants.py new file mode 100644 index 0000000000..957e36fb70 --- /dev/null +++ b/plugins/rapid7_surface_command/icon_rapid7_surface_command/util/surface_command/constants.py @@ -0,0 +1,9 @@ +REGION_MAP = { + "United States": "us", + "United States 2": "us2", + "United States 3": "us3", + "Europe": "eu", + "Canada": "ca", + "Australia": "au", + "Japan": "ap" +} \ No newline at end of file diff --git a/plugins/rapid7_surface_command/plugin.spec.yaml b/plugins/rapid7_surface_command/plugin.spec.yaml new file mode 100644 index 0000000000..504ef9eb2d --- /dev/null +++ b/plugins/rapid7_surface_command/plugin.spec.yaml @@ -0,0 +1,61 @@ +plugin_spec_version: v2 +extension: plugin +products: [insightconnect] +name: rapid7_surface_command +title: Rapid7 Surface Command +description: Surface Command gives you full visibilty over your assets and identies across multiple technology platforms. +version: 1.0.0 +supported_versions: ["v1.0.790"] +vendor: rapid7 +status: [] +tags: [caasm] +hub_tags: + use_cases: [data_enrichment] + keywords: [caasm, rapid7, surface_command] + features: [] +resources: + source_url: https://github.com/rapid7/insightconnect-plugins/tree/master/example + license_url: https://github.com/rapid7/insightconnect-plugins/blob/master/LICENSE + vendor_url: https://www.rapid7.com + +connection: + api_key: + title: API Key + description: User or Organization Key from the Insight Platform + type: credential_secret_key + example: a5zy0a6g-504e-46bz-84xx-1b3f5ci36l99 + required: true + region: + title: Region + description: Region + type: string + default: United States + example: United States + required: true + enum: + - United States + - United States 2 + - United States 3 + - Europe + - Canada + - Australia + - Japan + +actions: + run_query: + title: Run Surface Command Query + description: Run and execute Surface Command Query + input: + query_id: + title: ID of Query to run + description: Query ID (UUID) to run from Surface Command + type: string + required: true + example: 12345678-1234-1234-1234-123456789012 + output: + items: + title: Items + description: Array of Items + type: items + example: "[]" + required: false diff --git a/plugins/rapid7_surface_command/requirements.txt b/plugins/rapid7_surface_command/requirements.txt new file mode 100644 index 0000000000..33c8572218 --- /dev/null +++ b/plugins/rapid7_surface_command/requirements.txt @@ -0,0 +1,3 @@ +# List third-party dependencies here, separated by newlines. +# All dependencies must be version-pinned, eg. requests==1.2.0 +# See: https://pip.pypa.io/en/stable/user_guide/#requirements-files diff --git a/plugins/rapid7_surface_command/setup.py b/plugins/rapid7_surface_command/setup.py new file mode 100644 index 0000000000..d988a9aae6 --- /dev/null +++ b/plugins/rapid7_surface_command/setup.py @@ -0,0 +1,14 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +from setuptools import setup, find_packages + + +setup(name="rapid7_surface_command-rapid7-plugin", + version="1.0.0", + description="Surface Command gives you full visibilty over your assets and identies across multiple technology platforms.", + author="rapid7", + author_email="", + url="", + packages=find_packages(), + install_requires=['insightconnect-plugin-runtime'], # Add third-party dependencies to requirements.txt, not here! + scripts=['bin/icon_rapid7_surface_command'] + ) diff --git a/plugins/rapid7_surface_command/unit_test/__init__.py b/plugins/rapid7_surface_command/unit_test/__init__.py new file mode 100644 index 0000000000..d9ae09fc16 --- /dev/null +++ b/plugins/rapid7_surface_command/unit_test/__init__.py @@ -0,0 +1,4 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +import sys + +sys.path.append("../") \ No newline at end of file diff --git a/plugins/rapid7_surface_command/unit_test/test_run_query.py b/plugins/rapid7_surface_command/unit_test/test_run_query.py new file mode 100644 index 0000000000..37309ca8e3 --- /dev/null +++ b/plugins/rapid7_surface_command/unit_test/test_run_query.py @@ -0,0 +1,20 @@ +import sys +import os +sys.path.append(os.path.abspath('../')) + +from unittest import TestCase +from icon_rapid7_surface_command.connection.connection import Connection +from icon_rapid7_surface_command.actions.run_query import RunQuery +import json +import logging + + +class TestRunQuery(TestCase): + def test_run_query(self): + """ + DO NOT USE PRODUCTION/SENSITIVE DATA FOR UNIT TESTS + + TODO: Implement test cases here + """ + + self.fail("Unimplemented Test Case")