Skip to content

Commit 425fe78

Browse files
committed
Prepare Tuency bot for new version
In currently developed Tuency version, the quering capabilities are being extended to support identifier and feed.code, and the information about consituency is returned. In addition, the bot gots more customisation capabilities.
1 parent 9bfe9bf commit 425fe78

File tree

4 files changed

+345
-60
lines changed

4 files changed

+345
-60
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
- `intelmq.bots.experts.securitytxt`:
3535
- Added new bot (PR#2538 by Frank Westers and Sebastian Wagner)
3636
- `intelmq.bots.experts.misp`: Use `PyMISP` class instead of deprecated `ExpandedPyMISP` (PR#2532 by Radek Vyhnal)
37+
- `intelmq.bots.experts.tuency`: (PR# by Kamil Mańkowski)
38+
- Support for querying using `feed.code` and `classification.identifier` (requires Tuency 2.6+),
39+
- Support for customizing fields and the TTL value for suspended sending.
3740

3841
#### Outputs
3942
- `intelmq.bots.outputs.cif3.output`:

docs/user/bots.md

+39-6
Original file line numberDiff line numberDiff line change
@@ -4073,8 +4073,9 @@ addresses and delivery settings for IP objects (addresses, netblocks), Autonomou
40734073

40744074
- `classification.taxonomy`
40754075
- `classification.type`
4076+
- `classification.identifier`
40764077
- `feed.provider`
4077-
- `feed.name`
4078+
- `feed.name` or `feed.code`
40784079

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

@@ -4083,17 +4084,20 @@ The API parameter "feed_status" is currently set to "production" constantly, unt
40834084
The API answer is processed as following. For the notification interval:
40844085

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

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

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

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

@@ -4111,6 +4115,35 @@ available.
41114115

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

4118+
**`notify_field`**
4119+
4120+
(optional, string) Name of the field to save information if the message should not be send
4121+
(suspention in Tuency). By default `extra.notify`
4122+
4123+
**`ttl_field`**
4124+
4125+
(optional, string) Name of the field to save the TTL value (in seconds). By default `extra.ttl`.
4126+
4127+
**`constituency_field`**
4128+
4129+
(optional, string) Name of the gield to save information about the consitutuency. By default
4130+
`extra.constituency`. If set to empty value, this information won't be saved.
4131+
4132+
**`ttl_on_suspended`**
4133+
4134+
(optional, integer) Custom value to set as TTL when the sending is suspended. By default
4135+
not set - no value will be set at all.
4136+
4137+
**`query_classification_identifier`**
4138+
4139+
(optional, boolean) Whether to add `classification.identifier` to the query. Requires
4140+
at least Tuency 2.6. By default `False`.
4141+
4142+
**`query_feed_code`**
4143+
4144+
(optional, boolean) Whether to query using `feed.code` instead of `feed.name`. Requires
4145+
at least Tuency 2.6. By default `False`.
4146+
41144147
---
41154148

41164149
### Truncate By Delimiter <div id="intelmq.bots.experts.truncate_by_delimiter.expert" />

intelmq/bots/experts/tuency/expert.py

+48-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
2-
© 2021 Sebastian Wagner <wagner@cert.at>
3-
2+
SPDX-FileCopyrightText: 2021 Sebastian Wagner <wagner@cert.at>
3+
SPDX-FileCopyrightText: 2025 CERT.at GmbH <https://cert.at/>
44
SPDX-License-Identifier: AGPL-3.0-or-later
55
66
https://gitlab.com/intevation/tuency/tuency/-/blob/master/backend/docs/IntelMQ-API.md
@@ -26,6 +26,17 @@ class TuencyExpertBot(ExpertBot):
2626
authentication_token: str
2727
overwrite: bool = True
2828

29+
notify_field = "extra.notify"
30+
ttl_field = "extra.ttl"
31+
constituency_field = "extra.constituency"
32+
33+
# Allows setting custom TTL for suspended sending
34+
ttl_on_suspended = None
35+
36+
# Non-default values require Tuency v2.6+
37+
query_classification_identifier = False
38+
query_feed_code = False
39+
2940
def init(self):
3041
self.set_request_parameters()
3142
self.session = create_request_session(self)
@@ -44,11 +55,17 @@ def process(self):
4455
"classification_taxonomy": event["classification.taxonomy"],
4556
"classification_type": event["classification.type"],
4657
"feed_provider": event["feed.provider"],
47-
"feed_name": event["feed.name"],
4858
"feed_status": "production",
4959
}
60+
if self.query_feed_code:
61+
params["feed_code"] = event["feed.code"]
62+
else:
63+
params["feed_name"] = event["feed.name"]
64+
65+
if self.query_classification_identifier:
66+
params["classification_identifier"] = event["classification.identifier"]
5067
except KeyError as exc:
51-
self.logger.debug('Skipping event because of missing field: %s.', exc)
68+
self.logger.debug("Skipping event because of missing field: %s.", exc)
5269
self.send_message(event)
5370
self.acknowledge_message()
5471
return
@@ -62,24 +79,42 @@ def process(self):
6279
pass
6380

6481
response = self.session.get(self.url, params=params).json()
65-
self.logger.debug('Received response %r.', response)
82+
self.logger.debug("Received response %r.", response)
6683

6784
if response.get("suppress", False):
68-
event["extra.notify"] = False
85+
event.add(self.notify_field, False)
86+
if self.ttl_on_suspended:
87+
event.add(self.ttl_field, self.ttl_on_suspended)
6988
else:
70-
if 'interval' not in response:
89+
if "interval" not in response:
7190
# empty response
7291
self.send_message(event)
7392
self.acknowledge_message()
7493
return
75-
elif response['interval']['unit'] == 'immediate':
76-
event["extra.ttl"] = 0
94+
elif response["interval"]["unit"] == "immediate":
95+
event.add(self.ttl_field, 0)
7796
else:
78-
event["extra.ttl"] = parse_relative(f"{response['interval']['length']} {response['interval']['unit']}") * 60
97+
event.add(
98+
self.ttl_field,
99+
(
100+
parse_relative(
101+
f"{response['interval']['length']} {response['interval']['unit']}"
102+
)
103+
* 60
104+
),
105+
)
79106
contacts = []
80-
for destination in response.get('ip', {'destinations': []})['destinations'] + response.get('domain', {'destinations': []})['destinations']:
81-
contacts.extend(contact['email'] for contact in destination["contacts"])
82-
event.add('source.abuse_contact', ','.join(contacts), overwrite=self.overwrite)
107+
for destination in (
108+
response.get("ip", {"destinations": []})["destinations"]
109+
+ response.get("domain", {"destinations": []})["destinations"]
110+
):
111+
contacts.extend(contact["email"] for contact in destination["contacts"])
112+
event.add("source.abuse_contact", ",".join(contacts), overwrite=self.overwrite)
113+
114+
if self.constituency_field and (
115+
constituencies := response.get("constituencies", [])
116+
):
117+
event.add(self.constituency_field, ",".join(constituencies))
83118

84119
self.send_message(event)
85120
self.acknowledge_message()

0 commit comments

Comments
 (0)