Skip to content

Commit a662c84

Browse files
Birger SchachtBirger Schacht
authored andcommitted
JinjaExpert - modify message fields using jinja2
This commit adds a simple jinja expert bot, that lets you modify message data using jinja2 templates. It add the relevant documentation to the bots file and also a couple of unit tests.
1 parent f3d3df0 commit a662c84

File tree

5 files changed

+167
-0
lines changed

5 files changed

+167
-0
lines changed

docs/user/bots.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,6 +2387,33 @@ Documentation about IDEA: https://idea.cesnet.cz/en/index
23872387
* `test_mode`: add `Test` category to mark all outgoing IDEA events as informal (meant to simplify setting up and debugging new IDEA producers) (default: `true`)
23882388

23892389

2390+
.. _intelmq.bots.experts.jinja.expert:
2391+
2392+
Jinja2 Template Expert
2393+
^^^^^^^^^^^^^^^^^^^^^^
2394+
2395+
This bot lets you modify the content of your IntelMQ message fields using Jinja2 templates.
2396+
2397+
Documentation about Jinja2 templating language: https://jinja.palletsprojects.com/
2398+
2399+
**Information**
2400+
2401+
* `name:` intelmq.bots.experts.jinja.expert
2402+
* `description:` Modify the content of IntelMQ messages using jinja2 templates
2403+
2404+
**Configuration Parameters**
2405+
2406+
* `fields`: a dict containing as key the name of the field where the result of the Jinja2 template should be written to and as value either a Jinja2 template or a filepath to a Jinja2 template file (starting with ``file:///``). Because the experts decides if it is a filepath based on the value starting with ``file:///`` it is not possible to simply write values starting with ``file:///`` to fields.
2407+
The object containing the existing message will be passed to the Jinja2 template with the name ``msg``.
2408+
2409+
.. code-block:: yaml
2410+
2411+
fields:
2412+
output: The provider is {{ msg['feed.provider'] }}!
2413+
feed.url: "{{ msg['feed.url'] | upper }}"
2414+
extra.somejinjaoutput: file:///etc/intelmq/somejinjatemplate.j2
2415+
2416+
23902417
.. _intelmq.bots.experts.lookyloo.expert:
23912418

23922419
Lookyloo

intelmq/bots/experts/jinja/expert.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# SPDX-FileCopyrightText: 2021 Birger Schacht
2+
#
3+
# SPDX-License-Identifier: AGPL-3.0-or-later
4+
5+
from intelmq.lib.bot import Bot
6+
from intelmq.lib.exceptions import MissingDependencyError
7+
8+
import pathlib
9+
import os
10+
from typing import Union, Dict
11+
12+
try:
13+
from jinja2 import Template, TemplateError
14+
except ImportError:
15+
Template = None
16+
17+
18+
class JinjaExpertBot(Bot):
19+
"""
20+
Modify the message using the Jinja templating engine
21+
Example:
22+
fields:
23+
output: The provider is {{ msg['feed.provider'] }}!
24+
feed.url: "{{ msg['feed.url'] | upper }}"
25+
extra.somejinjaoutput: file:///etc/intelmq/somejinjatemplate.j2
26+
"""
27+
28+
fields: Dict[str, Union[str, Template]] = {}
29+
overwrite: bool = False
30+
31+
def init(self):
32+
if not Template:
33+
raise MissingDependencyError("Library 'jinja2' is required, please install it.")
34+
35+
for field, template in self.fields.items():
36+
if template.startswith("file:///"):
37+
templatefile = pathlib.Path(template[7:])
38+
if templatefile.exists() and os.access(templatefile, os.R_OK):
39+
self.fields[field] = templatefile.read_text()
40+
else:
41+
raise ValueError(f"Jinja Template {templatefile} does not exist or is not readable.")
42+
43+
for field, template in self.fields.items():
44+
try:
45+
self.fields[field] = Template(template)
46+
except TemplateError as msg:
47+
raise ValueError(f"Error parsing Jinja Template for '{field}': {msg}")
48+
49+
def process(self):
50+
msg = self.receive_message()
51+
52+
for field, template in self.fields.items():
53+
msg.add(field, template.render(msg=msg), overwrite=self.overwrite)
54+
55+
self.send_message(msg)
56+
self.acknowledge_message()
57+
58+
59+
BOT = JinjaExpertBot
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"@timestamp": {% if msg['time.source'] %}{{ msg['time.source'] }}{% else %}{{ msg['time.observation'] }}{% endif %},
3+
{%- set fields = { 'event.provider': 'feed.provider', 'server.ip': 'source.ip', 'server.domain': 'source.fqdn', 'event.dataset': 'feed.name' } -%}
4+
{% for key, value in fields.items() %}
5+
{% if msg[value] %} "{{ key }}": {{ msg[value] }}{% if not loop.last %},{% endif %}{% endif %}
6+
{%- endfor %}
7+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SPDX-FileCopyrightText: 2021 Birger Schacht
2+
SPDX-License-Identifier: AGPL-3.0-or-later
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# SPDX-FileCopyrightText: 2021 Birger Schacht
2+
#
3+
# SPDX-License-Identifier: AGPL-3.0-or-later
4+
"""
5+
Testing jinja expert
6+
"""
7+
8+
import unittest
9+
import os
10+
11+
import pkg_resources
12+
13+
import intelmq.lib.test as test
14+
from intelmq.bots.experts.jinja.expert import JinjaExpertBot
15+
16+
EXAMPLE_INPUT = {"__type": "Event",
17+
"source.ip": "192.168.0.1",
18+
"destination.ip": "192.0.43.8",
19+
"time.observation": "2015-01-01T00:00:00+00:00",
20+
"feed.url": "https://cert.at",
21+
}
22+
EXAMPLE_OUTPUT1 = {"__type": "Event",
23+
"source.ip": "192.168.0.1",
24+
"destination.ip": "192.0.43.8",
25+
"time.observation": "2015-01-01T00:00:00+00:00",
26+
"feed.url": "HTTPS://CERT.AT",
27+
}
28+
EXAMPLE_OUTPUT2 = {"__type": "Event",
29+
"source.ip": "192.168.0.1",
30+
"destination.ip": "192.0.43.8",
31+
"time.observation": "2015-01-01T00:00:00+00:00",
32+
"extra.some_text": "Hello World, this is the destination ip: 192.0.43.8! And this is the source ip: 192.168.0.1!",
33+
"feed.url": "https://cert.at",
34+
}
35+
EXAMPLE_OUTPUT3 = {"__type": "Event",
36+
"source.ip": "192.168.0.1",
37+
"destination.ip": "192.0.43.8",
38+
"time.observation": "2015-01-01T00:00:00+00:00",
39+
"extra.some_text": '{\n "@timestamp": 2015-01-01T00:00:00+00:00,\n\n "server.ip": 192.168.0.1,\n\n\n}',
40+
"feed.url": "https://cert.at",
41+
}
42+
43+
44+
class TestJinjaExpertBot(test.BotTestCase, unittest.TestCase):
45+
"""
46+
A TestCase for JinjaExpertBot.
47+
"""
48+
49+
@classmethod
50+
def set_bot(cls):
51+
cls.bot_reference = JinjaExpertBot
52+
53+
def test_jinja1(self):
54+
self.sysconfig = {'fields': { 'feed.url': "{{ msg['feed.url'] | upper }}" } }
55+
self.input_message = EXAMPLE_INPUT
56+
self.run_bot()
57+
self.assertMessageEqual(0, EXAMPLE_OUTPUT1)
58+
59+
def test_jinja2(self):
60+
self.sysconfig = {'fields': { 'extra.some_text': "Hello World, this is the destination ip: {{ msg['destination.ip'] }}! And this is the source ip: {{ msg['source.ip'] }}!" } }
61+
self.input_message = EXAMPLE_INPUT
62+
self.run_bot()
63+
self.assertMessageEqual(0, EXAMPLE_OUTPUT2)
64+
65+
def test_jinja_file1(self):
66+
self.sysconfig = {'fields': { 'extra.some_text': "file:///" + os.path.join(os.path.dirname(__file__)) + "/ecs.j2" } }
67+
self.input_message = EXAMPLE_INPUT
68+
self.run_bot()
69+
self.assertMessageEqual(0, EXAMPLE_OUTPUT3)
70+
71+
if __name__ == '__main__': # pragma: no cover
72+
unittest.main()

0 commit comments

Comments
 (0)