Skip to content

Commit

Permalink
Python 3 Script - 414 - Updated the SDK | Updated Python version to 3…
Browse files Browse the repository at this point in the history
….9.18 | Added handler to run function separately (#2125)
  • Loading branch information
igorski-r7 committed Nov 22, 2023
1 parent 40bf213 commit 68695c9
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 34 deletions.
6 changes: 3 additions & 3 deletions plugins/python_3_script/.CHECKSUM
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"spec": "29d983b124964524c684549fd1ceb355",
"manifest": "c40652105e23b83e0975e0991a587371",
"setup": "2909819c387092e88d56e1079094e6a0",
"spec": "35a664c2015a27eb5a5026d746f7213c",
"manifest": "50ec47def361c8d3e6ecfd7503f1964d",
"setup": "f02f843d20f1de6d92372cdf5a8656e5",
"schemas": [
{
"identifier": "run/schema.py",
Expand Down
21 changes: 10 additions & 11 deletions plugins/python_3_script/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM rapid7/insightconnect-python-3-38-plugin:5
FROM rapid7/insightconnect-python-3-slim-plugin:5

LABEL organization=rapid7
LABEL sdk=python
Expand All @@ -7,21 +7,20 @@ LABEL type=plugin
ENV SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_DIR /etc/ssl/certs
ENV REQUESTS_CA_BUNDLE /etc/ssl/certs/ca-certificates.crt
ENV PYTHONUSERBASE=/var/cache/python_dependencies
ENV PYTHONPATH=/var/cache/python_dependencies

ADD ./plugin.spec.yaml /plugin.spec.yaml
ADD . /python/src
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests libxslt-dev libxml2-dev gcc g++ -y

WORKDIR /python/src
# Add any package dependencies here
RUN apt-get update && \
apt-get install --no-install-recommends --no-install-suggests -y libxslt-dev libxml2-dev gcc g++ && \
apt-get clean -y

ENV PYTHONUSERBASE=/var/cache/python_dependencies \
PYTHONPATH=/var/cache/python_dependencies
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

# End package dependencies
RUN if [ -f requirements.txt ]; then pip install --no-cache-dir -r requirements.txt; fi
RUN python setup.py build && python setup.py install

# User to run plugin code. The two supported users are: root, nobody
Expand Down
2 changes: 1 addition & 1 deletion plugins/python_3_script/bin/icon_python_3_script
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ from sys import argv

Name = "Python 3 Script"
Vendor = "rapid7"
Version = "4.0.6"
Version = "4.0.7"
Description = "Python is a programming language that lets you work quickly and integrate systems more effectively. This plugin allows you to run Python 3 code. It includes Python 3.8.1 and its standard library as well as the following 3rd party libraries"


Expand Down
5 changes: 3 additions & 2 deletions plugins/python_3_script/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* [maya 0.6.1](https://pypi.python.org/pypi/maya)
* [lxml 4.9.2](http://lxml.de/)
* [beautifulsoup 4.12.2](https://www.crummy.com/software/BeautifulSoup/)
* [pyyaml 6.0.0](http://pyyaml.org/)
* [pyyaml 6.0.1](http://pyyaml.org/)
* [records 0.5.3](https://github.com/kennethreitz/records)

The Python 3 Script plugin also allows you to load custom modules via its connection parameters.
Expand All @@ -23,7 +23,7 @@ Also, this plugin allows you to provide additional credentials in the connection

# Supported Product Versions

* Python 3.8.1
* Python 3.9.18

# Documentation

Expand Down Expand Up @@ -134,6 +134,7 @@ If installation fails, try increasing the `Timeout` connection input to `900` (1

# Version History

* 4.0.7 - Updated the SDK | Updated Python version to `3.9.18` | Added handler to run function separately
* 4.0.6 - Added empty `__init__.py` file to `unit_test` folder | Refreshed with new tooling
* 4.0.5 - Updated the SDK version to include output masking | Updated all dependencies to the newest versions
* 4.0.4 - Update Pyyaml to version 6.0.0
Expand Down
63 changes: 52 additions & 11 deletions plugins/python_3_script/icon_python_3_script/actions/run/action.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import subprocess # nosec B404
import sys
from typing import Any, Dict
from typing import Any, Dict, Union
from uuid import uuid4

import insightconnect_plugin_runtime
from insightconnect_plugin_runtime.exceptions import PluginException
from insightconnect_plugin_runtime.helper import clean

from icon_python_3_script.util.constants import DEFAULT_ENCODING, DEFAULT_PROCESS_TIMEOUT, INDENTATION_CHARACTER
from icon_python_3_script.util.util import extract_output_from_stdout

from .schema import Component, Input, RunInput, RunOutput

sys.path.append("/var/cache/python_dependencies/lib/python3.8/site-packages")

INDENTATION_CHARACTER = " " * 4


class Run(insightconnect_plugin_runtime.Action):
def __init__(self):
Expand All @@ -28,15 +31,15 @@ def run(self, params={}):
self.logger.info(f"Function: (below)\n\n{params.get(Input.FUNCTION)}\n")

try:
out = self._exec_python_function(function_=function_, params=params)
output = self._execute_function_as_process(function_, params.get(Input.INPUT, {}))
except Exception as error:
raise PluginException(cause="Could not run supplied script", data=str(error))
try:
if out is None:
if output is None:
raise PluginException(
cause="Output type was None", assistance="Ensure that output has a non-None data type"
)
return out
return output
except UnboundLocalError:
raise PluginException(
cause="No output was returned.", assistance="Check supplied script to ensure that it returns output"
Expand All @@ -45,32 +48,69 @@ def run(self, params={}):
@staticmethod
def _exec_python_function(function_: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Executes python function and returning it's data
Executes python function and returning its data
:param function_: Python script function
:type: str
:param params: Parameters to be added to the function
:type: Dict[str, Any]
:return: Output of the functions response
:rtype: Dict[str, Any]
"""

exec(function_) # noqa: B102
function_name = function_.split(" ")[1].split("(")[0]
out = locals()[function_name](params.get(Input.INPUT))
return out
return locals()[function_name](params.get(Input.INPUT))

@staticmethod
def _execute_function_as_process(function_: str, parameters: Dict[str, Any]) -> Union[Dict[str, Any], None]:
"""
Execute a function as a separate process and return its data.
:param function_: The declaration of the run function to execute.
:type: str
:param parameters: The input parameters to pass to the function.
:type: Dict[str, Any]
:return: The result of the function execution.
:rtype: Union[Dict[str, Any], None]
"""

execution_id = f"Python3Script-ActionRun-{uuid4()}:"
function_name = function_.split(" ")[1].split("(")[0]
try:
output = subprocess.check_output( # nosec B603, B607
[
"python",
"-c",
f'import sys\n\n{function_}\nsys.stdout.write("{execution_id}" + str({function_name}({parameters})))',
],
shell=False,
stderr=subprocess.PIPE,
timeout=DEFAULT_PROCESS_TIMEOUT,
)
return extract_output_from_stdout(output.decode(DEFAULT_ENCODING), execution_id)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as error:
raise PluginException(error.stderr.decode(DEFAULT_ENCODING))

@staticmethod
def _add_credentials_to_function(
self, function_: str, credentials: Dict[str, str], indentation_character: str = INDENTATION_CHARACTER
function_: str, credentials: Dict[str, str], indentation_character: str = INDENTATION_CHARACTER
) -> str:
"""
This function adds credentials to the function entered by the user. It looks for connection script
credentials: username, password, secret_key, and adds all of them to variables inside of the function.
credentials: username, password, secret_key, and adds all of them to variables inside the function.
:param function_: Python script function
:type: str
:param credentials: Credentials to be added
:type: Dict[str, str]
:param indentation_character: Indentation character used in function
:type: str
:return: Function string appended by the credential variables
:rtype: str
"""
Expand All @@ -90,6 +130,7 @@ def _check_indentation_character(function_: str) -> str:
This function returns the indentation character that is used in the input python's function
:param function_: Python script function
:type: str
:return: Indentation character that is used
:rtype: str
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
INDENTATION_CHARACTER = " " * 4
DEFAULT_ENCODING = "utf-8"
DEFAULT_PROCESS_TIMEOUT = 5 * 60
29 changes: 29 additions & 0 deletions plugins/python_3_script/icon_python_3_script/util/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import Any, Dict, Union

import yaml


def extract_output_from_stdout(input_stdout: str, output_prefix: str) -> Union[Dict[str, Any], None]:
"""
Extract output from a string representing standard output.
This function parses the provided `input_stdout` string and extracts any output data
that start with the specified `output_prefix`. The extracted output is returned as a
dictionary where the keys are the extracted output lines without the prefix.
:param input_stdout: The string representing the standard output to extract from.
:type: str
:param output_prefix: The prefix indicating the lines to extract from `input_stdout`.
:type: str
:return: A dictionary containing the extracted output.
:rtype: Union[Dict[str, Any], None]
"""

if output_prefix in input_stdout:
function_output = yaml.safe_load(input_stdout[input_stdout.index(output_prefix) + len(output_prefix) :])
if isinstance(function_output, str) and function_output.lower().strip() == "none":
return None
return function_output
return None
11 changes: 8 additions & 3 deletions plugins/python_3_script/plugin.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ vendor: rapid7
support: rapid7
status: []
description: Python is a programming language that lets you work quickly and integrate systems more effectively. This plugin allows you to run Python 3 code. It includes Python 3.8.1 and its standard library as well as the following 3rd party libraries
version: 4.0.6
supported_versions: ["Python 3.8.1"]
version: 4.0.7
supported_versions: ["Python 3.9.18"]
sdk:
type: full
version: 5
user: root
packages:
- libxslt-dev
- libxml2-dev
- gcc
- g++
enable_cache: true
resources:
source_url: https://github.com/rapid7/insightconnect-plugins/tree/master/plugins/python_3_script
Expand Down Expand Up @@ -42,7 +47,7 @@ connection:
required: true
type: integer
default: 60
example: '120'
example: 120
script_secret_key:
title: Script Secret Key
description: Credential secret key available in script as python variable (`secret_key`)
Expand Down
2 changes: 1 addition & 1 deletion plugins/python_3_script/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ maya==0.6.1
lxml==4.9.2
datetime==5.1
setuptools==65.5.1
pyyaml==6.0.0
PyYAML==6.0.1
beautifulsoup4==4.12.2
parameterized==0.8.1
2 changes: 1 addition & 1 deletion plugins/python_3_script/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


setup(name="python_3_script-rapid7-plugin",
version="4.0.6",
version="4.0.7",
description="Python is a programming language that lets you work quickly and integrate systems more effectively. This plugin allows you to run Python 3 code. It includes Python 3.8.1 and its standard library as well as the following 3rd party libraries",
author="rapid7",
author_email="",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"function": "def run(params={}):\n\treturn {'some_output': params.get('some_input')",
"function": "def run(params={}):\n\treturn {'some_output': params.get('some_input')}",
"input": {
"some_input": "no_credentials"
}
Expand Down

0 comments on commit 68695c9

Please sign in to comment.