Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare Tuency bot for new version #2561

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
- `intelmq.bots.experts.securitytxt`:
- Added new bot (PR#2538 by Frank Westers and Sebastian Wagner)
- `intelmq.bots.experts.misp`: Use `PyMISP` class instead of deprecated `ExpandedPyMISP` (PR#2532 by Radek Vyhnal)
- `intelmq.bots.experts.tuency`: (PR# by Kamil Mańkowski)
- Support for querying using `feed.code` and `classification.identifier` (requires Tuency 2.6+),
- Support for customizing fields and the TTL value for suspended sending.

#### Outputs
- `intelmq.bots.outputs.cif3.output`:
Expand Down
45 changes: 39 additions & 6 deletions docs/user/bots.md
Original file line number Diff line number Diff line change
Expand Up @@ -4021,8 +4021,9 @@ addresses and delivery settings for IP objects (addresses, netblocks), Autonomou

- `classification.taxonomy`
- `classification.type`
- `classification.identifier`
- `feed.provider`
- `feed.name`
- `feed.name` or `feed.code`

These fields therefore need to exist, otherwise the message is skipped.

Expand All @@ -4031,17 +4032,20 @@ The API parameter "feed_status" is currently set to "production" constantly, unt
The API answer is processed as following. For the notification interval:

- If *suppress* is true, then `extra.notify` is set to false.
If explicitly configured, a special TTL value can be set.
- Otherwise:
- If the interval is *immediate*, then `extra.ttl` is set to 0.
- Otherwise the interval is converted into seconds and saved in
`extra.ttl`.
- If the interval is *immediate*, then `extra.ttl` is set to 0.
- Otherwise the interval is converted into seconds and saved in
`extra.ttl`.

For the contact lookup: For both fields *ip* and *domain*, the
*destinations* objects are iterated and its *email* fields concatenated to a comma-separated list
in `source.abuse_contact`.

The IntelMQ fields used by this bot may change in the next IntelMQ release, as soon as better suited fields are
available.
For constituency: if provided from Tuency, the list of relvant consitituencies will
be saved comma-separated in the `extra.constituency` field.

The IntelMQ fields used by this bot may be customized by the parameters.

**Module:** `intelmq.bots.experts.tuency.expert`

Expand All @@ -4059,6 +4063,35 @@ available.

(optional, boolean) Whether the existing data in `source.abuse_contact` should be overwritten. Defaults to true.

**`notify_field`**

(optional, string) Name of the field to save information if the message should not be send
(suspension in Tuency). By default `extra.notify`

**`ttl_field`**

(optional, string) Name of the field to save the TTL value (in seconds). By default `extra.ttl`.

**`constituency_field`**

(optional, string) Name of the gield to save information about the consitutuency. By default
`extra.constituency`. If set to empty value, this information won't be saved.

**`ttl_on_suspended`**

(optional, integer) Custom value to set as TTL when the sending is suspended. By default
not set - no value will be set at all.

**`query_classification_identifier`**

(optional, boolean) Whether to add `classification.identifier` to the query. Requires
at least Tuency 2.6. By default `False`.

**`query_feed_code`**

(optional, boolean) Whether to query using `feed.code` instead of `feed.name`. Requires
at least Tuency 2.6. By default `False`.

---

### Truncate By Delimiter <div id="intelmq.bots.experts.truncate_by_delimiter.expert" />
Expand Down
60 changes: 47 additions & 13 deletions intelmq/bots/experts/tuency/expert.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
© 2021 Sebastian Wagner <[email protected]>

SPDX-FileCopyrightText: 2021 Sebastian Wagner <[email protected]>
SPDX-FileCopyrightText: 2025 CERT.at GmbH <https://cert.at/>
SPDX-License-Identifier: AGPL-3.0-or-later

https://gitlab.com/intevation/tuency/tuency/-/blob/master/backend/docs/IntelMQ-API.md
Expand All @@ -26,6 +26,17 @@ class TuencyExpertBot(ExpertBot):
authentication_token: str
overwrite: bool = True

notify_field = "extra.notify"
ttl_field = "extra.ttl"
constituency_field = "extra.constituency"

# Allows setting custom TTL for suspended sending
ttl_on_suspended = None

# Non-default values require Tuency v2.6+
query_classification_identifier = False
query_feed_code = False

def init(self):
self.set_request_parameters()
self.session = create_request_session(self)
Expand All @@ -44,11 +55,17 @@ def process(self):
"classification_taxonomy": event["classification.taxonomy"],
"classification_type": event["classification.type"],
"feed_provider": event["feed.provider"],
"feed_name": event["feed.name"],
"feed_status": "production",
}
if self.query_feed_code:
params["feed_code"] = event["feed.code"]
else:
params["feed_name"] = event["feed.name"]

if self.query_classification_identifier:
params["classification_identifier"] = event["classification.identifier"]
except KeyError as exc:
self.logger.debug('Skipping event because of missing field: %s.', exc)
self.logger.debug("Skipping event because of missing field: %s.", exc)
self.send_message(event)
self.acknowledge_message()
return
Expand All @@ -62,24 +79,41 @@ def process(self):
pass

response = self.session.get(self.url, params=params).json()
self.logger.debug('Received response %r.', response)
self.logger.debug("Received response %r.", response)

if response.get("suppress", False):
event["extra.notify"] = False
event.add(self.notify_field, False)
if self.ttl_on_suspended:
event.add(self.ttl_field, self.ttl_on_suspended)
else:
if 'interval' not in response:
if "interval" not in response:
# empty response
self.send_message(event)
self.acknowledge_message()
return
elif response['interval']['unit'] == 'immediate':
event["extra.ttl"] = 0
elif response["interval"]["unit"] == "immediate":
event.add(self.ttl_field, 0)
else:
event["extra.ttl"] = parse_relative(f"{response['interval']['length']} {response['interval']['unit']}") * 60
event.add(
self.ttl_field,
(
parse_relative(
f"{response['interval']['length']} {response['interval']['unit']}"
) * 60
),
)
contacts = []
for destination in response.get('ip', {'destinations': []})['destinations'] + response.get('domain', {'destinations': []})['destinations']:
contacts.extend(contact['email'] for contact in destination["contacts"])
event.add('source.abuse_contact', ','.join(contacts), overwrite=self.overwrite)
for destination in (
response.get("ip", {"destinations": []})["destinations"] +
response.get("domain", {"destinations": []})["destinations"]
):
contacts.extend(contact["email"] for contact in destination["contacts"])
event.add("source.abuse_contact", ",".join(contacts), overwrite=self.overwrite)

if self.constituency_field and (
constituencies := response.get("constituencies", [])
):
event.add(self.constituency_field, ",".join(constituencies))

self.send_message(event)
self.acknowledge_message()
Expand Down
Loading
Loading