diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a6e014..95f9468 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/phantomcyber/dev-cicd-tools - rev: v1.16 + rev: v1.17 hooks: - id: org-hook - id: package-app-dependencies diff --git a/LICENSE b/LICENSE index cb61fdc..85c4b94 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) 2018-2022 Splunk Inc. + Copyright (c) 2018-2023 Splunk Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE index 35cd7ea..7dcb1d0 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Splunk SOAR Windows Remote Management -Copyright (c) 2018-2022 Splunk Inc. +Copyright (c) 2018-2023 Splunk Inc. Third-party Software Attributions: diff --git a/README.md b/README.md index 0b385c6..3c9921a 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,17 @@ # Windows Remote Management Publisher: Splunk -Connector Version: 2.2.5 +Connector Version: 2.2.7 Product Vendor: Microsoft Product Name: Windows Remote Management Product Version Supported (regex): ".\*" -Minimum Product Version: 5.1.0 +Minimum Product Version: 6.1.1 This app integrates with the Windows Remote Management service to execute various actions [comment]: # "" [comment]: # " File: README.md" -[comment]: # " Copyright (c) 2018-2022 Splunk Inc." +[comment]: # " Copyright (c) 2018-2023 Splunk Inc." [comment]: # " " [comment]: # " Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)" [comment]: # "" diff --git a/__init__.py b/__init__.py index 3981118..f8b589a 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ # File: __init__.py # -# Copyright (c) 2018-2022 Splunk Inc. +# Copyright (c) 2018-2023 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/manual_readme_content.md b/manual_readme_content.md index acb7070..b3179e7 100644 --- a/manual_readme_content.md +++ b/manual_readme_content.md @@ -1,6 +1,6 @@ [comment]: # "" [comment]: # " File: README.md" -[comment]: # " Copyright (c) 2018-2022 Splunk Inc." +[comment]: # " Copyright (c) 2018-2023 Splunk Inc." [comment]: # " " [comment]: # " Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt)" [comment]: # "" diff --git a/parse_callbacks.py b/parse_callbacks.py index 511a064..d4cec5b 100644 --- a/parse_callbacks.py +++ b/parse_callbacks.py @@ -1,6 +1,6 @@ # File: parse_callbacks.py # -# Copyright (c) 2018-2022 Splunk Inc. +# Copyright (c) 2018-2023 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ # in any specific manner import base64 import json -import tempfile from builtins import str from collections import OrderedDict @@ -296,11 +295,11 @@ def list_firewall_rules(action_result, response, **kwargs): def create_firewall_rule(action_result, response): if response.status_code: try: - msg = response.std_out.splitlines()[1] + message = response.std_out.splitlines()[1] except: - msg = response.std_out + message = response.std_out return action_result.set_status( - phantom.APP_ERROR, "Error running command: {}".format(msg) + phantom.APP_ERROR, "Error running command: {}".format(message) ) return phantom.APP_SUCCESS @@ -453,13 +452,7 @@ def decodeb64_add_to_vault(action_result, response, container_id, file_name): b64string = response.std_out try: - if hasattr(Vault, 'create_attachment'): - resp = Vault.create_attachment(base64.b64decode(b64string), container_id, file_name=file_name) - else: - tmp_file = tempfile.NamedTemporaryFile(mode='wb', delete=False, dir='/opt/phantom/vault/tmp') - tmp_file.write(base64.b64decode(b64string)) - tmp_file.close() - resp = Vault.add_attachment(tmp_file.name, container_id, file_name=file_name) + resp = Vault.create_attachment(base64.b64decode(b64string), container_id, file_name=file_name) except Exception as e: return action_result.set_status( phantom.APP_ERROR, "Error adding file to vault", e diff --git a/release_notes/2.2.6.md b/release_notes/2.2.6.md new file mode 100644 index 0000000..978a1a9 --- /dev/null +++ b/release_notes/2.2.6.md @@ -0,0 +1,5 @@ +* Use the Vault API to create temporary files, instead of manual filesystem access [PAPP-32449] +* Update `min_phantom_version` to 6.1.1 +* Remove `requests` dependency, using the one built into the platform instead +* Suppress "progress" output from PowerShell, preventing actions from wrongly being marked as failed +* Improve Unicode parsing to prevent errors \ No newline at end of file diff --git a/release_notes/2.2.7.md b/release_notes/2.2.7.md new file mode 100644 index 0000000..0fc4cc1 --- /dev/null +++ b/release_notes/2.2.7.md @@ -0,0 +1,2 @@ +* [PAPP-32933] Kerberos and Certificate authentication support. + * User can now select Certificate and Kerberos as transport methods \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b23840b..e52f8a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,3 @@ beautifulsoup4==4.9.1 -ntlm_auth==1.5.0 -pywinrm==0.4.1 -requests_ntlm==1.1.0 -six==1.15.0 -xmltodict==0.12.0 +pywinrm==0.4.3 +xmltodict==0.13.0 diff --git a/wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl b/wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl deleted file mode 100644 index 080b2f8..0000000 Binary files a/wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/certifi-2023.11.17-py3-none-any.whl b/wheels/py3/certifi-2023.11.17-py3-none-any.whl deleted file mode 100644 index de0787f..0000000 Binary files a/wheels/py3/certifi-2023.11.17-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl b/wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl deleted file mode 100644 index 17a2dfb..0000000 Binary files a/wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py3/cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl deleted file mode 100644 index ca743b2..0000000 Binary files a/wheels/py3/cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl and /dev/null differ diff --git a/wheels/py3/idna-3.6-py3-none-any.whl b/wheels/py3/idna-3.6-py3-none-any.whl deleted file mode 100644 index fdf65ae..0000000 Binary files a/wheels/py3/idna-3.6-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/requests-2.31.0-py3-none-any.whl b/wheels/py3/requests-2.31.0-py3-none-any.whl deleted file mode 100644 index bfd5d2e..0000000 Binary files a/wheels/py3/requests-2.31.0-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl b/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl deleted file mode 100644 index b363a9b..0000000 Binary files a/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/soupsieve-2.5-py3-none-any.whl b/wheels/py3/soupsieve-2.5-py3-none-any.whl deleted file mode 100644 index e1be128..0000000 Binary files a/wheels/py3/soupsieve-2.5-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/urllib3-2.1.0-py3-none-any.whl b/wheels/py3/urllib3-2.1.0-py3-none-any.whl deleted file mode 100644 index 0951ac3..0000000 Binary files a/wheels/py3/urllib3-2.1.0-py3-none-any.whl and /dev/null differ diff --git a/wheels/py36/cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py36/cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl deleted file mode 100644 index e703acc..0000000 Binary files a/wheels/py36/cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl and /dev/null differ diff --git a/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl deleted file mode 100644 index f725331..0000000 Binary files a/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl and /dev/null differ diff --git a/wheels/py39/cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py39/cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl deleted file mode 100644 index dc02197..0000000 Binary files a/wheels/py39/cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl and /dev/null differ diff --git a/wheels/py39/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py39/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl deleted file mode 100644 index 39c7d52..0000000 Binary files a/wheels/py39/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl and /dev/null differ diff --git a/wheels/shared/ntlm_auth-1.5.0-py2.py3-none-any.whl b/wheels/shared/ntlm_auth-1.5.0-py2.py3-none-any.whl deleted file mode 100644 index 6083118..0000000 Binary files a/wheels/shared/ntlm_auth-1.5.0-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/shared/pycparser-2.21-py2.py3-none-any.whl b/wheels/shared/pycparser-2.21-py2.py3-none-any.whl deleted file mode 100644 index fef6735..0000000 Binary files a/wheels/shared/pycparser-2.21-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/shared/pywinrm-0.4.1-py2.py3-none-any.whl b/wheels/shared/pywinrm-0.4.1-py2.py3-none-any.whl deleted file mode 100644 index 4f390da..0000000 Binary files a/wheels/shared/pywinrm-0.4.1-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/shared/requests-2.27.1-py2.py3-none-any.whl b/wheels/shared/requests-2.27.1-py2.py3-none-any.whl deleted file mode 100644 index 807fc61..0000000 Binary files a/wheels/shared/requests-2.27.1-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/shared/requests_ntlm-1.1.0-py2.py3-none-any.whl b/wheels/shared/requests_ntlm-1.1.0-py2.py3-none-any.whl deleted file mode 100644 index 5f97789..0000000 Binary files a/wheels/shared/requests_ntlm-1.1.0-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/shared/six-1.15.0-py2.py3-none-any.whl b/wheels/shared/six-1.15.0-py2.py3-none-any.whl deleted file mode 100644 index 89edace..0000000 Binary files a/wheels/shared/six-1.15.0-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/shared/urllib3-1.26.18-py2.py3-none-any.whl b/wheels/shared/urllib3-1.26.18-py2.py3-none-any.whl deleted file mode 100644 index c7337c7..0000000 Binary files a/wheels/shared/urllib3-1.26.18-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/shared/xmltodict-0.12.0-py2.py3-none-any.whl b/wheels/shared/xmltodict-0.12.0-py2.py3-none-any.whl deleted file mode 100644 index 540936b..0000000 Binary files a/wheels/shared/xmltodict-0.12.0-py2.py3-none-any.whl and /dev/null differ diff --git a/winrm.json b/winrm.json index 6054811..aea3e60 100644 --- a/winrm.json +++ b/winrm.json @@ -9,82 +9,18 @@ "product_name": "Windows Remote Management", "product_version_regex": ".*", "publisher": "Splunk", - "license": "Copyright (c) 2018-2022 Splunk Inc.", - "app_version": "2.2.5", - "utctime_updated": "2022-03-03T19:04:41.000000Z", + "license": "Copyright (c) 2018-2023 Splunk Inc.", + "app_version": "2.2.7", + "utctime_updated": "2023-12-05T12:42:47.000000Z", "package_name": "phantom_winrm", "main_module": "winrm_connector.py", - "min_phantom_version": "5.1.0", + "min_phantom_version": "6.1.1", "fips_compliant": true, "python_version": "3", "latest_tested_versions": [ "On-premise, Windows Server 2012 R2 Standard" ], "app_wizard_version": "1.0.0", - "pip_dependencies": { - "wheel": [ - { - "module": "beautifulsoup4", - "input_file": "wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl" - }, - { - "module": "certifi", - "input_file": "wheels/py3/certifi-2023.11.17-py3-none-any.whl" - }, - { - "module": "cffi", - "input_file": "wheels/py36/cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, - { - "module": "charset_normalizer", - "input_file": "wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl" - }, - { - "module": "cryptography", - "input_file": "wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, - { - "module": "idna", - "input_file": "wheels/py3/idna-3.6-py3-none-any.whl" - }, - { - "module": "ntlm_auth", - "input_file": "wheels/shared/ntlm_auth-1.5.0-py2.py3-none-any.whl" - }, - { - "module": "pycparser", - "input_file": "wheels/shared/pycparser-2.21-py2.py3-none-any.whl" - }, - { - "module": "pywinrm", - "input_file": "wheels/shared/pywinrm-0.4.1-py2.py3-none-any.whl" - }, - { - "module": "requests", - "input_file": "wheels/shared/requests-2.27.1-py2.py3-none-any.whl" - }, - { - "module": "requests_ntlm", - "input_file": "wheels/shared/requests_ntlm-1.1.0-py2.py3-none-any.whl" - }, - { - "module": "six", - "input_file": "wheels/shared/six-1.15.0-py2.py3-none-any.whl" - }, - { - "module": "soupsieve", - "input_file": "wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl" - }, - { - "module": "urllib3", - "input_file": "wheels/shared/urllib3-1.26.18-py2.py3-none-any.whl" - }, - { - "module": "xmltodict", - "input_file": "wheels/shared/xmltodict-0.12.0-py2.py3-none-any.whl" - } - ] - }, "configuration": { "endpoint": { "description": "IP/Hostname (For TEST CONNECTIVITY and default, if not provided in an action)", @@ -3373,7 +3309,7 @@ }, { "module": "certifi", - "input_file": "wheels/py3/certifi-2023.11.17-py3-none-any.whl" + "input_file": "wheels/py3/certifi-2023.7.22-py3-none-any.whl" }, { "module": "cffi", @@ -3381,39 +3317,35 @@ }, { "module": "charset_normalizer", - "input_file": "wheels/py39/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "input_file": "wheels/py39/charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "module": "cryptography", - "input_file": "wheels/py3/cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "input_file": "wheels/py3/cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "module": "idna", - "input_file": "wheels/py3/idna-3.6-py3-none-any.whl" - }, - { - "module": "ntlm_auth", - "input_file": "wheels/shared/ntlm_auth-1.5.0-py2.py3-none-any.whl" + "input_file": "wheels/py3/idna-3.4-py3-none-any.whl" }, { "module": "pycparser", "input_file": "wheels/shared/pycparser-2.21-py2.py3-none-any.whl" }, { - "module": "pywinrm", - "input_file": "wheels/shared/pywinrm-0.4.1-py2.py3-none-any.whl" + "module": "pyspnego", + "input_file": "wheels/py3/pyspnego-0.10.2-py3-none-any.whl" }, { - "module": "requests", - "input_file": "wheels/py3/requests-2.31.0-py3-none-any.whl" + "module": "pywinrm", + "input_file": "wheels/shared/pywinrm-0.4.3-py2.py3-none-any.whl" }, { "module": "requests_ntlm", - "input_file": "wheels/shared/requests_ntlm-1.1.0-py2.py3-none-any.whl" + "input_file": "wheels/py3/requests_ntlm-1.2.0-py3-none-any.whl" }, { "module": "six", - "input_file": "wheels/shared/six-1.15.0-py2.py3-none-any.whl" + "input_file": "wheels/shared/six-1.16.0-py2.py3-none-any.whl" }, { "module": "soupsieve", @@ -3421,11 +3353,11 @@ }, { "module": "urllib3", - "input_file": "wheels/py3/urllib3-2.1.0-py3-none-any.whl" + "input_file": "wheels/py3/urllib3-2.0.7-py3-none-any.whl" }, { "module": "xmltodict", - "input_file": "wheels/shared/xmltodict-0.12.0-py2.py3-none-any.whl" + "input_file": "wheels/shared/xmltodict-0.13.0-py2.py3-none-any.whl" } ] } diff --git a/winrm_connector.py b/winrm_connector.py index 75e0c2e..6bbf806 100644 --- a/winrm_connector.py +++ b/winrm_connector.py @@ -1,6 +1,6 @@ # File: winrm_connector.py # -# Copyright (c) 2018-2022 Splunk Inc. +# Copyright (c) 2018-2023 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -81,32 +81,32 @@ def _get_error_message_from_exception(self, e): if e.args: if len(e.args) > 1: error_code = e.args[0] - error_msg = e.args[1] + error_message = e.args[1] elif len(e.args) == 1: - error_code = consts.WINRM_ERR_CODE_MSG - error_msg = e.args[0] + error_code = consts.WINRM_ERROR_CODE_MESSAGE + error_message = e.args[0] else: - error_code = consts.WINRM_ERR_CODE_MSG - error_msg = consts.WINRM_ERR_MSG_UNAVAILABLE + error_code = consts.WINRM_ERROR_CODE_MESSAGE + error_message = consts.WINRM_ERROR_MESSAGE_UNAVAILABLE except: - error_code = consts.WINRM_ERR_CODE_MSG - error_msg = consts.WINRM_ERR_MSG_UNAVAILABLE + error_code = consts.WINRM_ERROR_CODE_MESSAGE + error_message = consts.WINRM_ERROR_MESSAGE_UNAVAILABLE try: - error_msg = self._handle_py_ver_compat_for_input_str(error_msg) + error_message = self._handle_py_ver_compat_for_input_str(error_message) except TypeError: - error_msg = consts.WINRM_TYPE_ERR_MSG + error_message = consts.WINRM_TYPE_ERROR_MESSAGE except: - error_msg = consts.WINRM_ERR_MSG_UNAVAILABLE + error_message = consts.WINRM_ERROR_MESSAGE_UNAVAILABLE try: - if error_code in consts.WINRM_ERR_CODE_MSG: - error_text = "Error Message: {0}".format(error_msg) + if error_code in consts.WINRM_ERROR_CODE_MESSAGE: + error_text = "Error Message: {0}".format(error_message) else: - error_text = "Error Code: {0}. Error Message: {1}".format(error_code, error_msg) + error_text = "Error Code: {0}. Error Message: {1}".format(error_code, error_message) except: - self.debug_print(consts.WINRM_PARSE_ERR_MSG) - error_text = consts.WINRM_PARSE_ERR_MSG + self.debug_print(consts.WINRM_PARSE_ERROR_MESSAGE) + error_text = consts.WINRM_PARSE_ERROR_MESSAGE return error_text @@ -114,18 +114,18 @@ def _validate_integer(self, action_result, parameter, key, allow_zero=False): if parameter is not None: try: if not float(parameter).is_integer(): - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_INVALID_INT.format(msg="", param=key)), None + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_INVALID_INT.format(msg="", param=key)), None parameter = int(parameter) except: - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_INVALID_INT.format(msg="", param=key)), None + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_INVALID_INT.format(msg="", param=key)), None if parameter < 0: return action_result.set_status(phantom.APP_ERROR, - consts.WINRM_ERR_INVALID_INT.format(msg="non-negative", param=key)), None + consts.WINRM_ERROR_INVALID_INT.format(msg="non-negative", param=key)), None if not allow_zero and parameter == 0: return action_result.set_status(phantom.APP_ERROR, - consts.WINRM_ERR_INVALID_INT.format(msg="non-zero positive", param=key)), None + consts.WINRM_ERROR_INVALID_INT.format(msg="non-zero positive", param=key)), None return phantom.APP_SUCCESS, parameter @@ -145,10 +145,10 @@ def _get_vault_file_text(self, action_result, vault_id): try: success, message, file_info = phantom_rules.vault_info(vault_id=vault_id) if not file_info: - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_INVALID_VAULT_ID), None + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_INVALID_VAULT_ID), None file_path = list(file_info)[0].get('path') except: - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_INVALID_VAULT_ID), None + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_INVALID_VAULT_ID), None try: with open(file_path, 'r') as fp: @@ -164,10 +164,10 @@ def _get_custom_parser_method(self, action_result, vault_id): try: success, message, file_info = phantom_rules.vault_info(vault_id=vault_id) if not file_info: - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_INVALID_VAULT_ID), None + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_INVALID_VAULT_ID), None file_path = list(file_info)[0].get('path') except: - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_INVALID_VAULT_ID), None + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_INVALID_VAULT_ID), None try: custom_parser = imp.load_source('custom_parser', file_path) @@ -187,6 +187,19 @@ def _sanitize_string(self, string): # break any double quotes which are found, then we break any $, which is used to declare variables return string.replace('`', '``').replace('"', '`"').replace('$', '`$').replace('&', '`&').replace(')', '`)').replace('(', '`(') + def _get_fips_enabled(self): + try: + from phantom_common.install_info import is_fips_enabled + except ImportError: + return False + + fips_enabled = is_fips_enabled() + if fips_enabled: + self.debug_print('FIPS is enabled') + else: + self.debug_print('FIPS is not enabled') + return fips_enabled + def _create_ps_script(self, action_result, args, whitelist_args=set(), cmd_prefix="", cmd_suffix=""): # Here, you can pass it something like {"val1": "value"} which will generate a string for "-val1 value" # "For your convenience" you can also pass it a list of strings and dicts, something like [val1, {"val2": "asdf"}, foo], @@ -224,19 +237,19 @@ def _create_ps_script(self, action_result, args, whitelist_args=set(), cmd_prefi def _init_session(self, action_result, param=None): config = self.get_config() - default_protocol = config.get('default_protocol', 'http') + default_protocol = config.get(consts.WINRM_CONFIG_PROTOCOL, 'http') ret_val, default_port = self._validate_integer( action_result, - config.get('default_port', 5985 if default_protocol == 'http' else 5986), + config.get(consts.WINRM_CONFIG_PORT, 5985 if default_protocol == 'http' else 5986), "Default port", True) if phantom.is_fail(ret_val): return action_result.get_status() + endpoint = self._handle_py_ver_compat_for_input_str(config.get(consts.WINRM_CONFIG_ENDPOINT)) if param: - endpoint = self._handle_py_ver_compat_for_input_str(param.get('ip_hostname', config.get('endpoint'))) - else: - endpoint = self._handle_py_ver_compat_for_input_str(config.get('endpoint')) + endpoint = self._handle_py_ver_compat_for_input_str(param.get('ip_hostname', endpoint)) + if endpoint is None: return action_result.set_status( phantom.APP_ERROR, "No Endpoint Configured" @@ -245,12 +258,16 @@ def _init_session(self, action_result, param=None): endpoint = '{0}://{1}'.format(default_protocol, endpoint) if re.search(r':\d+$', endpoint, re.UNICODE | re.IGNORECASE) is None: endpoint = '{0}:{1}'.format(endpoint, default_port) - username = config['username'] - password = config['password'] - transport = config.get('transport') - domain = self._handle_py_ver_compat_for_input_str(config.get('domain')) + username = config[consts.WINRM_CONFIG_USERNAME] + password = config[consts.WINRM_CONFIG_PASSWORD] + transport = config.get(consts.WINRM_CONFIG_TRANSPORT) + domain = self._handle_py_ver_compat_for_input_str(config.get(consts.WINRM_CONFIG_DOMAIN)) verify_bool = config.get(phantom.APP_JSON_VERIFY, False) + cert_pem_path = None + cert_key_pem_path = None + cert_ca_trust_path = config.get(consts.WINRM_CONFIG_CA_TRUST, "legacy_requests") + if verify_bool: verify = 'validate' else: @@ -262,12 +279,18 @@ def _init_session(self, action_result, param=None): "Warning: Domain is set but transport type is set to 'basic'" ) elif transport == 'ntlm': + if self._get_fips_enabled(): + return action_result.set_status( + phantom.APP_ERROR, "This transport type is not supported when FIPS is enabled" + ) if domain: username = r'{}\{}'.format(domain, username) elif transport == 'kerberos': - return action_result.set_status( - phantom.APP_ERROR, "This transport type is not yet implemented" - ) + username = r'{}\{}'.format(domain, username) + elif transport == 'certificate': + username = r'{}\{}'.format(domain, username) + cert_pem_path = config.get(consts.WINRM_CONFIG_CERT_PEM) + cert_key_pem_path = config.get(consts.WINRM_CONFIG_CERT_KEY_PEM) elif transport == 'credssp': return action_result.set_status( phantom.APP_ERROR, "This transport type is not yet implemented" @@ -281,7 +304,10 @@ def _init_session(self, action_result, param=None): endpoint, auth=(username, password), server_cert_validation=verify, - transport=transport + transport=transport, + cert_pem=cert_pem_path, + cert_key_pem=cert_key_pem_path, + ca_trust_path=cert_ca_trust_path ) self._protocol = self._session.protocol @@ -319,7 +345,7 @@ def _run_cmd(self, action_result, cmd, args=None, parse_callback=pc.basic, else: resp = self._session.run_cmd(cmd, args) except UnicodeDecodeError: - return action_result.set_status(phantom.APP_ERROR, "Error running command: {}".format(consts.WINRM_UNICODE_ERR_MESSAGE)) + return action_result.set_status(phantom.APP_ERROR, "Error running command: {}".format(consts.WINRM_UNICODE_ERROR_MESSAGE)) except Exception as e: return action_result.set_status(phantom.APP_ERROR, "Error running command: {}".format(unquote(self._get_error_message_from_exception(e)))) @@ -346,6 +372,11 @@ def _run_ps(self, action_result, script, parse_callback=pc.basic, additional_dat if additional_data is None: additional_data = {} resp = None + + if script is not None: + # Suppress the "progress" output that PowerShell sends to Standard Error + script = "$ProgressPreference = 'SilentlyContinue'; \n " + script + try: if command_id: if shell_id is None: @@ -358,7 +389,7 @@ def _run_ps(self, action_result, script, parse_callback=pc.basic, additional_dat if len(resp.std_err): resp.std_err = self._session._clean_error_msg(resp.std_err) if isinstance(resp.std_err, bytes): - resp.std_err = resp.std_err.decode('UTF-8') + resp.std_err = resp.std_err.decode('UTF-8', errors='backslashreplace') elif async_: encoded_ps = b64encode(script.encode('utf_16_le')).decode('ascii') shell_id = self._protocol.open_shell() @@ -372,7 +403,7 @@ def _run_ps(self, action_result, script, parse_callback=pc.basic, additional_dat script = UnicodeDammit(script).unicode_markup resp = self._session.run_ps(script) except UnicodeDecodeError: - return action_result.set_status(phantom.APP_ERROR, "Error running PowerShell script: {}".format(consts.WINRM_UNICODE_ERR_MESSAGE)) + return action_result.set_status(phantom.APP_ERROR, "Error running PowerShell script: {}".format(consts.WINRM_UNICODE_ERROR_MESSAGE)) except Exception as e: return action_result.set_status(phantom.APP_ERROR, "Error running PowerShell script: {}".format(self._get_error_message_from_exception(e))) @@ -493,7 +524,7 @@ def _handle_list_firewall_rules(self, param): action_result = self.add_action_result(ActionResult(dict(param))) direction = param.get('direction') if direction and direction not in consts.DIRECTION_VALUE_LIST: - return action_result.set_status(phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MSG.format( + return action_result.set_status(phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MESSAGE.format( consts.DIRECTION_VALUE_LIST, "direction")) if not self._init_session(action_result, param): @@ -528,7 +559,7 @@ def _handle_delete_firewall_rule(self, param): param.update(other_dict) dir = param.get('dir') if dir and dir not in consts.DIR_VALUE_LIST: - return action_result.set_status(phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MSG.format(consts.DIR_VALUE_LIST, 'dir')) + return action_result.set_status(phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MESSAGE.format(consts.DIR_VALUE_LIST, 'dir')) val_map = { "local_ip": "localip", @@ -597,11 +628,11 @@ def _handle_create_firewall_rule(self, param): param.update(other_dict) dir = param.get('dir') if dir and dir not in consts.DIR_VALUE_LIST: - return action_result.set_status(phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MSG.format(consts.DIR_VALUE_LIST, 'dir')) + return action_result.set_status(phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MESSAGE.format(consts.DIR_VALUE_LIST, 'dir')) action = param.get('action') if action and action not in consts.ACTION_VALUE_LIST: - return action_result.set_status(phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MSG.format(consts.ACTION_VALUE_LIST, 'action')) + return action_result.set_status(phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MESSAGE.format(consts.ACTION_VALUE_LIST, 'action')) val_map = { "local_ip": "localip", @@ -711,7 +742,7 @@ def _handle_deactivate_partition(self, param): "inactive" ) if phantom.is_fail(ret_val): - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_PARTITION) + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_PARTITION) return action_result.set_status(phantom.APP_SUCCESS, "Successfully deactivated partition") @@ -725,7 +756,7 @@ def _handle_activate_partition(self, param): "active" ) if phantom.is_fail(ret_val): - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_PARTITION) + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_PARTITION) return action_result.set_status(phantom.APP_SUCCESS, "Successfully activated partition") @@ -770,7 +801,7 @@ def _format_list_applocker_script(self, action_result, location, ldap, xml=True, suffix = "-XML" if xml else "" if location.lower() not in consts.LOCATION_VALUE_LIST: return action_result.set_status( - phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MSG.format(consts.LOCATION_VALUE_LIST, "location") + phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MESSAGE.format(consts.LOCATION_VALUE_LIST, "location") ), None if location.lower() == "domain": if not ldap: @@ -822,7 +853,7 @@ def _handle_create_applocker_policy(self, param): deny_allow = param['deny_allow'].lower() if deny_allow not in consts.DENY_ALLOW_VALUE_LIST: return action_result.set_status( - phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MSG.format(consts.DENY_ALLOW_VALUE_LIST, "deny_allow") + phantom.APP_ERROR, consts.VALUE_LIST_VALIDATION_MESSAGE.format(consts.DENY_ALLOW_VALUE_LIST, "deny_allow") ) file_path = self._handle_py_ver_compat_for_input_str(param['file_path']) @@ -850,6 +881,8 @@ def _handle_create_applocker_policy(self, param): self._sanitize_string(file_path), new_policy_str, set_policy_str )) + self.debug_print(ps_script) + ret_val = self._run_ps(action_result, ps_script, parse_callback=pc.check_exit_no_data2) if phantom.is_fail(ret_val): return ret_val @@ -922,10 +955,10 @@ def _handle_send_file(self, param): vault_id = self._handle_py_ver_compat_for_input_str(param['vault_id']) success, message, file_info = phantom_rules.vault_info(vault_id=vault_id) if not file_info: - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_INVALID_VAULT_ID) + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_INVALID_VAULT_ID) path = list(file_info)[0].get('path') except: - return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERR_INVALID_VAULT_ID) + return action_result.set_status(phantom.APP_ERROR, consts.WINRM_ERROR_INVALID_VAULT_ID) destination = self._handle_py_ver_compat_for_input_str(param['destination']) diff --git a/winrm_consts.py b/winrm_consts.py index 5ccb50e..da20f5e 100644 --- a/winrm_consts.py +++ b/winrm_consts.py @@ -1,6 +1,6 @@ # File: winrm_consts.py # -# Copyright (c) 2018-2022 Splunk Inc. +# Copyright (c) 2018-2023 Splunk Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -87,19 +87,19 @@ [Convert]::ToBase64String([IO.File]::ReadAllBytes($d)) """ -WINRM_UNICODE_ERR_MESSAGE = "Invalid unicode detected" +WINRM_UNICODE_ERROR_MESSAGE = "Invalid unicode detected" # Constants relating to '_validate_integer' -WINRM_ERR_INVALID_INT = 'Please provide a valid {msg} integer value in the "{param}"' -WINRM_ERR_PARTITION = "Failed to fetch system volume, Please check the asset configuration and|or \"ip hostname\" parameter" -WINRM_ERR_INVALID_VAULT_ID = "Could not retrieve vault file" +WINRM_ERROR_INVALID_INT = 'Please provide a valid {msg} integer value in the "{param}"' +WINRM_ERROR_PARTITION = "Failed to fetch system volume, Please check the asset configuration and|or \"ip hostname\" parameter" +WINRM_ERROR_INVALID_VAULT_ID = "Could not retrieve vault file" # Constants relating to '_get_error_message_from_exception' -WINRM_ERR_CODE_MSG = "Error code unavailable" -WINRM_ERR_MSG_UNAVAILABLE = "Error message unavailable. Please check the asset configuration and|or action parameters" -WINRM_PARSE_ERR_MSG = "Unable to parse the error message. Please check the asset configuration and|or action parameters" -WINRM_TYPE_ERR_MSG = "Error occurred while connecting to the Winrm Server. Please check the asset configuration and|or " \ - "the action parameters" +WINRM_ERROR_CODE_MESSAGE = "Error code unavailable" +WINRM_ERROR_MESSAGE_UNAVAILABLE = "Error message unavailable. Please check the asset configuration and|or action parameters" +WINRM_PARSE_ERROR_MESSAGE = "Unable to parse the error message. Please check the asset configuration and|or action parameters" +WINRM_TYPE_ERROR_MESSAGE = ("Error occurred while connecting to the Winrm Server. " + "Please check the asset configuration and|or the action parameters") # Constants relating to value_list check DIRECTION_VALUE_LIST = ["in", "out"] @@ -107,4 +107,17 @@ ACTION_VALUE_LIST = ["allow", "block", "bypass"] LOCATION_VALUE_LIST = ["local", "domain", "effective"] DENY_ALLOW_VALUE_LIST = ["deny", "allow"] -VALUE_LIST_VALIDATION_MSG = "Please provide valid input from {} in '{}' action parameter" +VALUE_LIST_VALIDATION_MESSAGE = "Please provide valid input from {} in '{}' action parameter" + +# Config keys + +WINRM_CONFIG_ENDPOINT = "endpoint" +WINRM_CONFIG_PROTOCOL = "default_protocol" +WINRM_CONFIG_PORT = "default_port" +WINRM_CONFIG_USERNAME = "username" +WINRM_CONFIG_PASSWORD = "password" +WINRM_CONFIG_TRANSPORT = "transport" +WINRM_CONFIG_DOMAIN = "domain" +WINRM_CONFIG_CERT_PEM = "cert_pem_path" +WINRM_CONFIG_CERT_KEY_PEM = "cert_key_pem_path" +WINRM_CONFIG_CA_TRUST = "ca_trust_path"