diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a6e014..544174e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,10 @@ repos: - repo: https://github.com/phantomcyber/dev-cicd-tools - rev: v1.16 + rev: v1.17 hooks: - id: org-hook - id: package-app-dependencies + args: ["-d", "./Dockerfile.wheels"] - repo: https://github.com/Yelp/detect-secrets rev: v1.4.0 hooks: diff --git a/Dockerfile.wheels b/Dockerfile.wheels new file mode 100644 index 0000000..7649a6c --- /dev/null +++ b/Dockerfile.wheels @@ -0,0 +1,2 @@ +FROM quay.io/pypa/manylinux2014_x86_64 +RUN yum install krb5-devel krb5-workstation -y diff --git a/README.md b/README.md index fe00c35..1fbd0a7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Windows Remote Management Publisher: Splunk -Connector Version: 2.2.9 +Connector Version: 2.3.0 Product Vendor: Microsoft Product Name: Windows Remote Management Product Version Supported (regex): ".\*" @@ -130,6 +130,33 @@ or some other command which you want to start but don't care for the output, the **async** parameter. After the command starts, it will return a **command_id** and **shell_id** , which you can optionally use to retrieve the output of that command at a later time. +### Certificate Authentication + +To authenticate using SSL certificates, select `certificate` authentication in asset configuration method and pass following configuration parameters. + +* cert_pem_path - A path to signed certificate file that is trusted by the Windows instance, in PEM format + +* cert_key_pem_path - A filepath to key used to generate cert_pem file + +* ca_trust_path - The certificate of the certificate authority that signed cert_file. It's needed only when you set up your own certificate authority. + +It is recommended that these files be placed under the /etc/ssl/ directory. These files must be readable by the phantom-worker user. + +### Kerberos Authentication + +To authenticate using Kerberos, select `kerberos` authentication in asset configuration and provide hostname and username used for authorization. +You'll also need to setup your instance to support Kerberos: + +- Kerberos packages have to be installed: + - for Debian/Ubuntu/etc: `sudo apt-get install krb5-user` + - for RHEL/CentOS/etc: `sudo yum install krb5-workstation krb5-libs` + +- `/etc/krb5.conf` needs to be properly configured for your realm and kdc +- If there is no DNS configuration, `hosts` file will need to have mappings for server with mssccm under same domain as on Windows server +- `kinit` must be run for principal that will be used to connect to msccm +- It should be noted that Kerberos tickets will expire, so it is recommended to use a script to + run `kinit` periodically to refresh the ticket for the user, alternatively `keytab` file can be created on server and used on client for connectivity. + ### Configuration Variables The below configuration variables are required for this Connector to operate. These variables are specified when configuring a Windows Remote Management asset in SOAR. @@ -144,6 +171,9 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION **username** | required | string | Username **password** | required | password | Password **transport** | required | string | Type of transport to use +**cert_pem_path** | optional | string | Path to SSL certificate PEM file +**cert_key_pem_path** | optional | string | Path to SSL key file +**ca_trust_path** | optional | string | Path to trusted CRT file ### Supported Actions [test connectivity](#action-test-connectivity) - Validate the asset configuration for connectivity using supplied configuration diff --git a/manual_readme_content.md b/manual_readme_content.md index 33117a0..49a5187 100644 --- a/manual_readme_content.md +++ b/manual_readme_content.md @@ -117,3 +117,30 @@ default, the app will wait for these actions to finish. In the case of starting or some other command which you want to start but don't care for the output, then you can check the **async** parameter. After the command starts, it will return a **command_id** and **shell_id** , which you can optionally use to retrieve the output of that command at a later time. + +### Certificate Authentication + +To authenticate using SSL certificates, select `certificate` authentication in asset configuration method and pass following configuration parameters. + +* cert_pem_path - A path to signed certificate file that is trusted by the Windows instance, in PEM format + +* cert_key_pem_path - A filepath to key used to generate cert_pem file + +* ca_trust_path - The certificate of the certificate authority that signed cert_file. It's needed only when you set up your own certificate authority. + +It is recommended that these files be placed under the /etc/ssl/ directory. These files must be readable by the phantom-worker user. + +### Kerberos Authentication + +To authenticate using Kerberos, select `kerberos` authentication in asset configuration and provide hostname and username used for authorization. +You'll also need to setup your instance to support Kerberos: + +- Kerberos packages have to be installed: + - for Debian/Ubuntu/etc: `sudo apt-get install krb5-user` + - for RHEL/CentOS/etc: `sudo yum install krb5-workstation krb5-libs` + +- `/etc/krb5.conf` needs to be properly configured for your realm and kdc +- If there is no DNS configuration, `hosts` file will need to have mappings for server with mssccm under same domain as on Windows server +- `kinit` must be run for principal that will be used to connect to msccm +- It should be noted that Kerberos tickets will expire, so it is recommended to use a script to + run `kinit` periodically to refresh the ticket for the user, alternatively `keytab` file can be created on server and used on client for connectivity. diff --git a/release_notes/2.3.0.md b/release_notes/2.3.0.md new file mode 100644 index 0000000..0fc4cc1 --- /dev/null +++ b/release_notes/2.3.0.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 f05e3a8..ebba285 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ beautifulsoup4==4.9.1 +pykerberos==1.2.4 pywinrm==0.4.3 # Pinned to the current platform version requests==2.31.0 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 new file mode 100644 index 0000000..17a2dfb Binary files /dev/null and b/wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl 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 new file mode 100644 index 0000000..b363a9b Binary files /dev/null and b/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl differ diff --git a/wheels/py3/urllib3-2.2.1-py3-none-any.whl b/wheels/py3/urllib3-2.2.1-py3-none-any.whl new file mode 100644 index 0000000..d7cca6a Binary files /dev/null and b/wheels/py3/urllib3-2.2.1-py3-none-any.whl differ diff --git a/wheels/py36/cffi-1.15.1-cp36-cp36m-manylinux1_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py36/cffi-1.15.1-cp36-cp36m-manylinux1_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..cd555e4 Binary files /dev/null and b/wheels/py36/cffi-1.15.1-cp36-cp36m-manylinux1_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..a0d4277 Binary files /dev/null and b/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/wheels/py36/pykerberos-1.2.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py36/pykerberos-1.2.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..9072496 Binary files /dev/null and b/wheels/py36/pykerberos-1.2.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/wheels/py39/charset_normalizer-3.3.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/wheels/py39/charset_normalizer-3.3.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl index b11229e..3559f21 100644 Binary files a/wheels/py39/charset_normalizer-3.3.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl and b/wheels/py39/charset_normalizer-3.3.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/wheels/py39/cryptography-42.0.4-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/wheels/py39/cryptography-42.0.4-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..4f3952b Binary files /dev/null and b/wheels/py39/cryptography-42.0.4-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/wheels/py39/pykerberos-1.2.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py39/pykerberos-1.2.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..3799d1d Binary files /dev/null and b/wheels/py39/pykerberos-1.2.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl 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 new file mode 100644 index 0000000..6083118 Binary files /dev/null and b/wheels/shared/ntlm_auth-1.5.0-py2.py3-none-any.whl 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 new file mode 100644 index 0000000..807fc61 Binary files /dev/null and b/wheels/shared/requests-2.27.1-py2.py3-none-any.whl 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 new file mode 100644 index 0000000..5f97789 Binary files /dev/null and b/wheels/shared/requests_ntlm-1.1.0-py2.py3-none-any.whl differ diff --git a/winrm.json b/winrm.json index 589c061..83546a2 100644 --- a/winrm.json +++ b/winrm.json @@ -10,8 +10,8 @@ "product_version_regex": ".*", "publisher": "Splunk", "license": "Copyright (c) 2018-2024 Splunk Inc.", - "app_version": "2.2.9", - "utctime_updated": "2024-02-16T10:23:24.000000Z", + "app_version": "2.3.0", + "utctime_updated": "2024-02-28T09:53:35.000000Z", "package_name": "phantom_winrm", "main_module": "winrm_connector.py", "min_phantom_version": "6.1.1", @@ -72,10 +72,30 @@ "required": true, "value_list": [ "basic", - "ntlm" + "ntlm", + "certificate", + "kerberos" ], "default": "basic", "order": 7 + }, + "cert_pem_path": { + "description": "Path to SSL certificate PEM file", + "data_type": "string", + "required": false, + "order": 8 + }, + "cert_key_pem_path": { + "description": "Path to SSL key file", + "data_type": "string", + "required": false, + "order": 9 + }, + "ca_trust_path": { + "description": "Path to trusted CRT file", + "data_type": "string", + "required": false, + "order": 10 } }, "actions": [ @@ -3321,7 +3341,7 @@ }, { "module": "cryptography", - "input_file": "wheels/py39/cryptography-42.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" + "input_file": "wheels/py39/cryptography-42.0.4-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" }, { "module": "idna", @@ -3331,6 +3351,10 @@ "module": "pycparser", "input_file": "wheels/shared/pycparser-2.21-py2.py3-none-any.whl" }, + { + "module": "pykerberos", + "input_file": "wheels/py39/pykerberos-1.2.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, { "module": "pyspnego", "input_file": "wheels/py3/pyspnego-0.10.2-py3-none-any.whl" @@ -3355,6 +3379,74 @@ "module": "soupsieve", "input_file": "wheels/py3/soupsieve-2.5-py3-none-any.whl" }, + { + "module": "urllib3", + "input_file": "wheels/py3/urllib3-2.2.1-py3-none-any.whl" + }, + { + "module": "xmltodict", + "input_file": "wheels/shared/xmltodict-0.13.0-py2.py3-none-any.whl" + } + ] + }, + "pip_dependencies": { + "wheel": [ + { + "module": "beautifulsoup4", + "input_file": "wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl" + }, + { + "module": "certifi", + "input_file": "wheels/py3/certifi-2024.2.2-py3-none-any.whl" + }, + { + "module": "cffi", + "input_file": "wheels/py36/cffi-1.15.1-cp36-cp36m-manylinux1_x86_64.manylinux_2_5_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-manylinux2014_x86_64.manylinux_2_17_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": "pykerberos", + "input_file": "wheels/py36/pykerberos-1.2.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "module": "pywinrm", + "input_file": "wheels/shared/pywinrm-0.4.3-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.16.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" @@ -3365,4 +3457,4 @@ } ] } -} \ No newline at end of file +} diff --git a/winrm_connector.py b/winrm_connector.py index e1b99a5..c592963 100644 --- a/winrm_connector.py +++ b/winrm_connector.py @@ -37,6 +37,7 @@ from bs4 import UnicodeDammit from phantom.action_result import ActionResult from phantom.base_connector import BaseConnector +from phantom_common.install_info import is_fips_enabled # Local imports import parse_callbacks as pc @@ -187,6 +188,11 @@ 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): + fips_enabled = is_fips_enabled() + self.debug_print(f'FIPS is enabled: {fips_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 +230,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 +251,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 +272,19 @@ 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" - ) + if domain: + username = '{}@{}'.format(username, domain) + 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 +298,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 diff --git a/winrm_consts.py b/winrm_consts.py index d4a34f5..6987a6b 100644 --- a/winrm_consts.py +++ b/winrm_consts.py @@ -108,3 +108,16 @@ LOCATION_VALUE_LIST = ["local", "domain", "effective"] DENY_ALLOW_VALUE_LIST = ["deny", "allow"] 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"