Skip to content

Commit

Permalink
Add alerter for IRIS IRP system
Browse files Browse the repository at this point in the history
  • Loading branch information
malinkinsa committed Oct 23, 2023
1 parent f7b9a54 commit 28cb0ba
Show file tree
Hide file tree
Showing 7 changed files with 763 additions and 3 deletions.
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
60 changes: 60 additions & 0 deletions docs/source/ruletypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2579,6 +2579,66 @@ 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``: Alert status. Can be: ``1 - Unspecified``, ``2 - New``, ``3 - Assigned``, ``4 - In progress``, ``5 - Pending``, ``6 - Closed``, ``7 - Merged``. The default value is: `2`.

``iris_alert_source_link``: Link, if needed.

``iris_alert_severity_id``: Alert severity. Can be: ``1 - Unspecified``, ``2 - Informational``, ``3 - Low``, ``4 - Medium``, ``5 - High``, ``6 - Critical``. The default value is: `1`.

``iris_alert_context``:

``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', '')
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', '')
self.headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.rule.get("iris_api_token")}'
}
self.alert_note = self.rule.get('iris_alert_note', '')
self.alert_tags = self.rule.get('iris_alert_tags', '')
self.alert_status_id = self.rule.get('iris_alert_status_id', 2)
self.alert_source_link = self.rule.get('iris_alert_source_link', '')
self.alert_severity_id = self.rule.get('iris_alert_severity_id', 1)
self.alert_context = self.rule.get('iris_alert_context', '')
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
}
8 changes: 5 additions & 3 deletions elastalert/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
import elastalert.alerters.googlechat
import elastalert.alerters.httppost
import elastalert.alerters.httppost2
import elastalert.alerters.lark
import elastalert.alerters.iris
# import elastalert.alerters.lark
import elastalert.alerters.line
import elastalert.alerters.pagertree
import elastalert.alerters.rocketchat
Expand Down Expand Up @@ -127,12 +128,13 @@ class RulesLoader(object):
'zabbix': ZabbixAlerter,
'discord': elastalert.alerters.discord.DiscordAlerter,
'dingtalk': elastalert.alerters.dingtalk.DingTalkAlerter,
'lark': elastalert.alerters.lark.LarkAlerter,
# 'lark': elastalert.alerters.lark.LarkAlerter,
'chatwork': elastalert.alerters.chatwork.ChatworkAlerter,
'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
36 changes: 36 additions & 0 deletions examples/rules/example_iris_alert_any.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: "Example Iris alert"
type: any
index: index_*
use_strftime_index: true

filter:
- query:
query_string:
query: "session_status: opened"

realert:
minutes: 0

alert:
- iris

iris_host: 127.0.0.1
iris_api_token: token123456789
iris_customer_id: 1
iris_description: 'Test alert from ElastAlert2'
iris_alert_note: 'Alert triggered by opened session'
iris_alert_tags: 'test, login, ssh'
iris_alert_context:
username: username
ip: src_ip
iris_iocs:
- ioc_value: src_ip
ioc_description: source ip address
ioc_tlp_id: 1
ioc_type_id: 42
ioc_tags: ipv4
- ioc_value: username
ioc_description: who was connecting
ioc_tlp_id: 3
ioc_type_id: 3
ioc_tags: username
Loading

0 comments on commit 28cb0ba

Please sign in to comment.