Skip to content

Commit

Permalink
Merge pull request #1301 from malinkinsa/dfir-iris
Browse files Browse the repository at this point in the history
Add alerter for IRIS IRP system
  • Loading branch information
jertel authored Oct 27, 2023
2 parents 8e8fe69 + ee93942 commit b0824fd
Show file tree
Hide file tree
Showing 8 changed files with 787 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- TBD

## New features
- TBD
- [Iris] Alerter added - [#1301](https://github.com/jertel/elastalert2/pull/1301) - @malinkinsa

## Other changes
- Refactored FlatlineRule to make it more extensible - [#1291](https://github.com/jertel/elastalert2/pull/1291) - @rundef
Expand Down
1 change: 1 addition & 0 deletions docs/source/elastalert.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Currently, we have support built in for these alert types:
- Graylog GELF
- HTTP POST
- HTTP POST 2
- Iris
- Jira
- Lark
- Line Notify
Expand Down
79 changes: 79 additions & 0 deletions docs/source/ruletypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2579,6 +2579,85 @@ Example usage with json string formatting::
"X-custom-{{key}}": "{{type}}"
}

IRIS
~~~~~~~~~
The Iris alerter can be used to create a new alert or case in `Iris IRP System <https://dfir-iris.org>`_. The alerter supports adding tags, IOCs, and context from the alert matches and rule data.

The alerter requires the following option:

``iris_host``: Address of the Iris host. Exclude https:// For example: ``iris.example.com``.

``iris_api_token``: The API key of the user you created, which will be used to initiate alerts and cases on behalf of this user.

``iris_customer_id``: The user ID associated with the API key mentioned above. You can find it on the same page where the API key is located.

Optional:

``iris_ca_cert``: Path to custom CA certificate.

``iris_ignore_ssl_errors``: Ignore ssl error. The default value is: ``False``.

``iris_description``: Description of the alert or case.

``iris_overwrite_timestamp``: Should the timestamp be overridden when creating an alert. By default, the alert's creation time will be the trigger time. If you want to use the event's timestamp as the ticket creation time, set this value to ``True``. Default value is ``False``.

``iris_type``: The type of object being created. It can be either ``alert`` or ``case``. The default value is ``alert``.

``iris_case_template_id``: Case template ID, if you want to apply a pre-prepared template.

``iris_alert_note``: Note for the alert.

``iris_alert_tags``: List of tags.

``iris_alert_status_id``: The alert status of the alert, default value is ``2``. This parameter requires an integer input.

Possible values:

- ``1`` - Unspecified
- ``2`` - New
- ``3`` - Assigned
- ``4`` - In progress
- ``5`` - Pending
- ``6`` - Closed
- ``7`` - Merged.

``iris_alert_source_link``: Your custom link, if needed.

``iris_alert_severity_id``: The severity level of the alert, default value is ``1``. This parameter requires an integer input.

Possible values:

- ``1`` - Unspecified
- ``2`` - Informational
- ``3`` - Low
- ``4`` - Medium
- ``5`` - High
- ``6`` - Critical.

``iris_alert_context``: Include information from the match into the alert context. Working as key-value, where the key is your custom name and value - data from elasticsearch message.

``iris_iocs``: Description of the IOC to be added.

Example usage ``iris_iocs``:

.. code-block:: yaml
iris_iocs:
- ioc_value: ip
ioc_description: Suspicious IP address
ioc_tlp_id: 2
ioc_type_id: 76
ioc_tags: ipv4, ip, suspicious
- ioc_value: username
ioc_description: Suspicious username
ioc_tlp_id: 1
ioc_type_id: 3
ioc_tags: username
A few words about ``ioc_tlp_id`` and ``ioc_type_id``. ``ioc_tlp_id`` can be of three types: ``1 - red``, ``2 - amber``, ``3 - green``. There are numerous values for ``ioc_type_id``, and you can also add your custom ones. To find the ID for the type you are interested in, refer to your Iris instance's API at 'https://example.com/manage/ioc-types/list'.

You can find complete examples of rules in the repository under the 'examples' folder.

Jira
~~~~

Expand Down
187 changes: 187 additions & 0 deletions elastalert/alerters/iris.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import requests
import uuid

from datetime import datetime
from requests import RequestException

from elastalert.alerts import Alerter
from elastalert.util import EAException, elastalert_logger, lookup_es_key


class IrisAlerter(Alerter):
required_options = set(['iris_host', 'iris_api_token', 'iris_customer_id'])

def __init__(self, rule):
super(IrisAlerter, self).__init__(rule)
self.url = f"https://{self.rule.get('iris_host')}"
self.api_token = self.rule.get('iris_api_token')
self.customer_id = self.rule.get('iris_customer_id')
self.ca_cert = self.rule.get('iris_ca_cert', False)
self.ignore_ssl_errors = self.rule.get('iris_ignore_ssl_errors', False)
self.description = self.rule.get('iris_description', None)
self.overwrite_timestamp = self.rule.get('iris_overwrite_timestamp', False)
self.type = self.rule.get('iris_type', 'alert')
self.case_template_id = self.rule.get('iris_case_template_id', None)
self.headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.rule.get("iris_api_token")}'
}
self.alert_note = self.rule.get('iris_alert_note', None)
self.alert_tags = self.rule.get('iris_alert_tags', None)
self.alert_status_id = self.rule.get('iris_alert_status_id', 2)
self.alert_source_link = self.rule.get('iris_alert_source_link', None)
self.alert_severity_id = self.rule.get('iris_alert_severity_id', 1)
self.alert_context = self.rule.get('iris_alert_context', None)
self.iocs = self.rule.get('iris_iocs', None)

def make_alert_context_records(self, matches):
alert_context = {}

for key, value in self.alert_context.items():
alert_context.update(
{
key: matches[0].get(value)
}
)

return alert_context

def make_iocs_records(self, matches):
iocs = []
for record in self.iocs:
record['ioc_value'] = lookup_es_key(matches[0], record['ioc_value'])
iocs.append(record)
return iocs

def make_alert(self, matches):
if self.overwrite_timestamp:
event_timestamp = matches[0].get('@timestamp')
else:
event_timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")

alert_data = {
"alert_title": self.rule.get('name'),
"alert_description": self.description,
"alert_source": "ElastAlert2",
"alert_severity_id": self.alert_severity_id,
"alert_status_id": self.alert_status_id,
"alert_source_event_time": event_timestamp,
"alert_note": self.alert_note,
"alert_tags": self.alert_tags,
"alert_customer_id": self.customer_id,
}

if self.alert_source_link:
alert_data.update(
{"alert_source_link": self.alert_source_link}
)

if self.iocs:
iocs = self.make_iocs_records(matches)
alert_data.update(
{"alert_iocs": iocs}
)

if self.alert_context:
alert_context = self.make_alert_context_records(matches)
alert_data.update(
{"alert_context": alert_context}
)

return alert_data

def make_case(self, matches):
iocs = []
case_data = {
"case_soc_id": f"SOC_{str(uuid.uuid4())[0:6]}",
"case_customer": self.customer_id,
"case_name": self.rule.get('name'),
"case_description": self.description
}

if self.iocs:
iocs = self.make_iocs_records(matches)

if self.case_template_id:
case_data.update(
{"case_template_id": self.case_template_id}
)

return case_data, iocs

def alert(self, matches):
if self.ca_cert:
verify = self.ca_cert
else:
verify = False

if self.ignore_ssl_errors:
requests.packages.urllib3.disable_warnings()

if 'alert' in self.type:
alert_data = self.make_alert(matches)

try:
alert_response = requests.post(
url=f'{self.url}/alerts/add',
headers=self.headers,
json=alert_data,
verify=verify,
)

if alert_response.status_code != 200:
raise EAException(f"Cannot create a new alert: {alert_response.status_code}")

except RequestException as e:
raise EAException(f"Error posting alert to Iris: {e}")
elastalert_logger.info('Alert sent to Iris')

elif 'case' in self.type:
case_data, iocs = self.make_case(matches)

try:
case_response = requests.post(
url=f'{self.url}/manage/cases/add',
headers=self.headers,
json=case_data,
verify=verify,
)


if case_response.status_code == 200:
case_response_data = case_response.json()
case_id = case_response_data.get('data', '').get('case_id')
for ioc in iocs:
ioc.update(
{
"cid": case_id
}
)

try:
response_ioc = requests.post(
url=f'{self.url}/case/ioc/add',
headers=self.headers,
json=ioc,
verify=verify,
)

if response_ioc.status_code != 200:
raise EAException(f"Unable to add a new IOC to the case {case_id}")

except RequestException as e:
raise EAException(f"Error when adding IOC to the case {case_id}: {e}")
elastalert_logger.info('IOCs successfully added to the case')

else:
raise EAException(f'Cannot create a new case: {case_response.status_code}')

except RequestException as e:
raise EAException(f"Error posting the case to Iris: {e}")
elastalert_logger.info('Case successfully created in Iris')

def get_info(self):
return {
'type': 'IrisAlerter',
'iris_api_endpoint': self.url
}
4 changes: 3 additions & 1 deletion elastalert/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import elastalert.alerters.googlechat
import elastalert.alerters.httppost
import elastalert.alerters.httppost2
import elastalert.alerters.iris
import elastalert.alerters.lark
import elastalert.alerters.line
import elastalert.alerters.pagertree
Expand Down Expand Up @@ -132,7 +133,8 @@ class RulesLoader(object):
'datadog': elastalert.alerters.datadog.DatadogAlerter,
'ses': elastalert.alerters.ses.SesAlerter,
'rocketchat': elastalert.alerters.rocketchat.RocketChatAlerter,
'gelf': elastalert.alerters.gelf.GelfAlerter
'gelf': elastalert.alerters.gelf.GelfAlerter,
'iris': elastalert.alerters.iris.IrisAlerter,
}

# A partial ordering of alert types. Relative order will be preserved in the resulting alerts list
Expand Down
43 changes: 43 additions & 0 deletions elastalert/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ definitions:

filter: &filter {}

irisIocField: &irisIocField
type: object
additionalProperties: false
properties:
ioc_value: {type: string}
ioc_description: {type: string}
ioc_tlp_id: {type: integer, enum: [1, 2, 3]}
ioc_type_id: {type: integer}
ioc_tags: {type: string}

arrayOfIrisIocFields: &arrayOfIrisIocFields
type: array
items: *irisIocField

required: [type, index, alert]
type: object

Expand Down Expand Up @@ -514,6 +528,35 @@ properties:
http_post2_ignore_ssl_errors: {type: boolean}
http_post2_timeout: {type: integer}

### IRIS
iris_url: {type: string}
iris_api_token: {type: string}
iris_type: {type: string, enum: ['alert', 'case']}
iris_customer_id: {type: integer}
iris_ignore_ssl_errors: {type: boolean}
iris_ca_cert: {type: string}
iris_overwrite_timestamp: {type: boolean}
iris_case_template_id: {type: integer}
iris_description: {type: string}
iris_alert_note: {type: string}
iris_alert_tags: {type: string}
iris_alert_status_id: {type: integer, enum: [1, 2, 3, 4, 5, 6, 7]}
iris_alert_source_link: {type: string}
iris_alert_severity_id: {type: integer, enum: [1, 2, 3, 4, 5, 6]}
iris_iocs: *arrayOfIrisIocFields
iris_alert_context:
type: object
minProperties: 1
patternProperties:
"^.+$":
oneOf:
- type: string
- type: object
additionalProperties: false
required: [ field ]
properties:
field: { type: string, minLength: 1 }

### Jira
jira_server: {type: string}
jira_project: {type: string}
Expand Down
Loading

0 comments on commit b0824fd

Please sign in to comment.