ibmresilient / resilient-python-api

Python Library for the IBM SOAR REST API, a Python SDK for developing Apps for IBM SOAR and more...
https://ibm.biz/soar-python-docs
MIT License
39 stars 28 forks source link

Feature: Support Client Certificate Authentication for upstream SOAR security components #20

Closed svetterIO closed 3 years ago

svetterIO commented 3 years ago

Description

Adding optional parameter for client certificate authentication to all major functions on a co3base.py Baseclient and the SimpleClient in co3.py for Resilient/SOAR REST API.

Two new optional keys can be added to the app.config to leverage this functionality:

[...]
cert_auth_cert=<certificate>.pem
cert_auth_key=<certificate-key>.pem
[...]

:warning: <certificate-key>.pem must be password unprotected as python request package doesn't support this.

Motivation and Context

When using a reverse proxy or other security components to additionally secure the REST API of IBM Security SOAR with client certificate authentication the resilient-python-api (specifically the resilient package) can't be used anymore as it doesn't support this feature, yet.

The python request module does support client certificate authentication (https://docs.python-requests.org/en/latest/user/advanced/#client-side-certificates) and therefore the resilient python library needs to be extended to allow adding the optional cert parameter through the function calling chain.

How Has This Been Tested?

Following setup with IBM Security Verify Access (ISVA) as reverse proxy and client certificate validation has been setup: Flow Diagram Client Cert Authentication

A self-signed certificate was created with following commands:

$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

$ openssl rsa -in key.pem -out key.open.pem

And following Script was used to test resilient library:

import logging
import resilient
from resilient_lib import write_file_attachment
import configparser
import mimetypes
from io import BytesIO

SOAR_LOGGER = logging.getLogger("resilient.co3")
SOAR_FORMAT = logging.Formatter('%(asctime)s - [SOAR]: %(message)s')
SOAR_HANDLER = logging.StreamHandler()
SOAR_HANDLER.setFormatter(SOAR_FORMAT)
SOAR_LOGGER.addHandler(SOAR_HANDLER)
SOAR_LOGGER.setLevel(logging.DEBUG)

configParser = configparser.ConfigParser()
configParser.read_file(open("app.config"))

soar_client = resilient.get_client(configParser._sections['resilient'])

# change ticket ID for an existing ticket in SOAR
soar_incident_id="0815" # CHANGE-ME
certificate="cert.pem"
certificate_key="key.open.pem"

#================================================== resilient package testing

SOAR_LOGGER.info("Testing get function")
uri=f"/incidents/{soar_incident_id}"
retObj = soar_client.get(uri)
SOAR_LOGGER.info(retObj)

SOAR_LOGGER.info("Testing get_content function")
uri=f"/incidents/{soar_incident_id}"
retObj = soar_client.get_content(uri)
SOAR_LOGGER.info(retObj)

SOAR_LOGGER.info("Testing post function")
uri=f"/incidents"
payload={
    "name": "test",
    "discovered_date": 0
}
retObj = soar_client.post(uri, payload)
test_incident_id=retObj['id']
SOAR_LOGGER.info(retObj)

SOAR_LOGGER.info("Testing post_attachment function")
uri=f"/incidents/{soar_incident_id}/attachments"
filepath="test.txt"

retObj = soar_client.post_attachment(uri, filepath)
SOAR_LOGGER.info(retObj)

SOAR_LOGGER.info("Testing post_artifact_file function")
uri=f"/incidents/{soar_incident_id}/artifacts/files"
artifact_type=16
artifact_filepath="test.txt"

retObj = soar_client.post_artifact_file(uri, artifact_type, artifact_filepath)
SOAR_LOGGER.info(retObj)

SOAR_LOGGER.info("Testing put function (PATCH)")
uri=f"/incidents/{soar_incident_id}/patch"
old_to_new_payload={
  "changes": [
    {
      "field": {
        "name": "name"
      },
      "old_value": {
        "text": "Existing Incident"
      },
      "new_value": {
        "text": "New Name"
      }
    }
  ]
}
new_to_old_payload={
  "changes": [
    {
      "field": {
        "name": "name"
      },
      "old_value": {
        "text": "New Name"
      },
      "new_value": {
        "text": "Existing Incident"
      }
    }
  ]
}
retObj = soar_client.put(uri, old_to_new_payload)
retObj = soar_client.put(uri, new_to_old_payload)
SOAR_LOGGER.info(retObj)

SOAR_LOGGER.info("Testing delete function")
uri=f"/incidents/{test_incident_id}"
retObj = soar_client.delete(uri)
SOAR_LOGGER.info(retObj)

Checklist:

Signed-off-by: Sebastian Vetter sebastian.vetter@de.ibm.com