Skip to content

Commit

Permalink
Merge pull request #1115 from SEKOIA-IO/fix/sekoia_add_comment_info
Browse files Browse the repository at this point in the history
Fix: Sekoia add comment info to result ( 191 )
  • Loading branch information
squioc authored Oct 8, 2024
2 parents be39d53 + c1fdb33 commit 07fa866
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 11 deletions.
6 changes: 6 additions & 0 deletions Sekoia.io/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## 2024-10-08 - 2.64.4

### Changed

- Return more information in Sekoia Alert comment trigger

## 2024-10-04 - 2.64.3

### Changed
Expand Down
2 changes: 1 addition & 1 deletion Sekoia.io/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"name": "Sekoia.io",
"uuid": "92d8bb47-7c51-445d-81de-ae04edbb6f0a",
"slug": "sekoia.io",
"version": "2.64.3",
"version": "2.64.4",
"categories": [
"Generic"
]
Expand Down
131 changes: 123 additions & 8 deletions Sekoia.io/sekoiaio/triggers/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,15 @@ def handle_event(self, message):
"""
alert_attrs = message.get("attributes", {})
event_type: str | None = message.get("type")
event_action: str | None = message.get("action")
event_type: str = message.get("type", "")
event_action: str = message.get("action", "")

# Ignore alert “sub event” types that we can’t (yet) handle.
if (event_type, event_action) not in self.HANDLED_EVENT_SUB_TYPES:
return

alert_uuid: str | None = None
if event_type == "alert":
alert_uuid = alert_attrs.get("uuid")
elif event_type == "alert-comment":
alert_uuid = alert_attrs.get("alert_uuid")

# Is the notification in a format we can understand?
alert_uuid: str = alert_attrs.get("uuid", "")
if not alert_uuid:
return

Expand Down Expand Up @@ -161,3 +156,123 @@ def _filter_notifications(self, message) -> bool:
class AlertCommentCreatedTrigger(SecurityAlertsTrigger):
# List of alert types we can handle.
HANDLED_EVENT_SUB_TYPES = [("alert-comment", "created")]

def handle_event(self, message):
"""Handle alert messages.
Only a few event are considered (`alert:created`,
`alert:updated`, `alert-comment:created`). If a valid evnet is
handled, then enrich event from `sicalertapi` to retrieve its
status, its short id, etc. Finally, send message to the
Symphony workflow.
"""
alert_attrs = message.get("attributes", {})
event_type: str = message.get("type", "")
event_action: str = message.get("action", "")

# Ignore alert “sub event” types that we can’t (yet) handle.
if (event_type, event_action) not in self.HANDLED_EVENT_SUB_TYPES:
return

# Is the notification in a format we can understand?
alert_uuid: str = alert_attrs.get("alert_uuid", "")
if not alert_uuid:
return

comment_uuid: str = alert_attrs.get("uuid", "")
if not comment_uuid:
return

if not self._filter_notifications(message):
return

try:
alert = self._retrieve_alert_from_alertapi(alert_uuid)
comment = self._retrieve_comment_from_alertapi(alert_uuid, comment_uuid)
except Exception as exp:
self.log_exception(exp, message="Failed to fetch alert from Alert API")
return

if rule_filter := self.configuration.get("rule_filter"):
if alert["rule"]["name"] != rule_filter and alert["rule"]["uuid"] != rule_filter:
return

work_dir = self._data_path.joinpath("sekoiaio_securityalerts").joinpath(str(uuid.uuid4()))
alert_path = work_dir.joinpath("alert.json")
work_dir.mkdir(parents=True, exist_ok=True)

with alert_path.open("w") as fp:
fp.write(orjson.dumps(alert).decode("utf-8"))

directory = str(work_dir.relative_to(self._data_path))
file_path = str(alert_path.relative_to(work_dir))

alert_short_id = alert.get("short_id")
event = {
"comment": {
"uuid": comment.get("uuid"),
"content": comment.get("content"),
"author": comment.get("created_by"),
"date": comment.get("date"),
},
"file_path": file_path,
"event_type": event_type,
"alert_uuid": alert_uuid,
"short_id": alert_short_id,
"status": {
"name": alert.get("status", {}).get("name"),
"uuid": alert.get("status", {}).get("uuid"),
},
"created_at": alert.get("created_at"),
"urgency": alert.get("urgency", {}).get("current_value"),
"entity": alert.get("entity", {}),
"alert_type": alert.get("alert_type", {}),
"rule": {"name": alert["rule"]["name"], "uuid": alert["rule"]["uuid"]},
"last_seen_at": alert.get("last_seen_at"),
"first_seen_at": alert.get("first_seen_at"),
}

self.send_event(
event_name=f"Sekoia.io Alert: {alert_short_id}",
event=event,
directory=directory,
remove_directory=True,
)

@retry(
reraise=True,
wait=wait_exponential(max=10),
stop=stop_after_attempt(10),
)
def _retrieve_comment_from_alertapi(self, alert_uuid: str, comment_uuid: str):
api_url = urljoin(
self.module.configuration["base_url"], f"api/v1/sic/alerts/{alert_uuid}/comments/{comment_uuid}"
)

api_url = api_url.replace("/api/api", "/api") # In case base_url ends with /api

api_key = self.module.configuration["api_key"]
headers = {"Authorization": f"Bearer {api_key}", "User-Agent": user_agent()}

response = requests.get(api_url, headers=headers)

if not response.ok:
try:
content = response.json()
except Exception:
content = response.text
self.log(
"Error while fetching alert comment from Alert API",
level="error",
status_code=response.status_code,
content=content,
)

# raise an exception if the http request failed
response.raise_for_status()
try:
return response.json()
except Exception as exp:
self.log("Failed to parse JSON response from Alert Comment API", level="error", content=response.text)
raise exp
21 changes: 19 additions & 2 deletions Sekoia.io/tests/ic_oc_triggers/test_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def test_single_event_triggers_status_changed(

def test_single_event_triggers_comments_added(
alert_created_trigger,
sample_sicalertapi_mock,
sample_sicalertapi,
module_configuration,
symphony_storage,
samplenotif_alert_comment_created,
Expand All @@ -214,7 +214,24 @@ def test_single_event_triggers_comments_added(
trigger.module._community_uuid = "cc93fe3f-c26b-4eb1-82f7-082209cf1892"
trigger.send_event = MagicMock()

with sample_sicalertapi_mock:
alert_uuid = samplenotif_alert_comment_created.get("attributes").get("alert_uuid")
comment_uuid = samplenotif_alert_comment_created.get("attributes").get("uuid")

with requests_mock.Mocker() as mock:
mock.get(f"http://fake.url/api/v1/sic/alerts/{alert_uuid}", json=sample_sicalertapi)

mock.get(
f"http://fake.url/api/v1/sic/alerts/{alert_uuid}/comments/{comment_uuid}",
json={
"uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f",
"content": "string",
"author": "string",
"date": 0,
"created_by": "string",
"created_by_type": "string",
"unseen": True,
},
)
# Calling the trigger with an alert commentadded notification should create an event
trigger.handle_event(samplenotif_alert_comment_created)
trigger.send_event.assert_called_once()
Expand Down
19 changes: 19 additions & 0 deletions Sekoia.io/trigger_sekoiaio_alert_comment_created.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@
"results": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"comment": {
"description": "Alert comment (object containing comment uuid, content, author, date).",
"type": "object",
"properties": {
"uuid": {
"type": "string"
},
"content": {
"type": "string"
},
"author": {
"type": "string"
},
"date": {
"type": "string"
}
}
},
"file_path": {
"description": "File path to the alert on disk.",
"type": "string"
Expand Down Expand Up @@ -76,6 +94,7 @@
"alert_type",
"alert_uuid",
"created_at",
"comment",
"entity",
"short_id",
"status",
Expand Down

0 comments on commit 07fa866

Please sign in to comment.