Files
blue-team-tools/tools/sigma/backends/datadog.py
T

124 lines
4.4 KiB
Python
Raw Normal View History

2021-12-02 10:26:51 -05:00
# Output backends for sigmac
# Copyright 2021 Datadog, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2021-11-29 15:11:29 +01:00
import re
2021-11-23 11:08:27 -05:00
from sigma.backends.base import SingleTextQueryBackend
2021-11-29 16:13:23 +01:00
from sigma.parser.condition import NodeSubexpression
2021-11-23 11:08:27 -05:00
2021-12-01 16:08:00 -05:00
class DatadogLogsBackend(SingleTextQueryBackend):
2021-12-03 12:41:49 -05:00
"""Converts Sigma rule into Datadog log search query."""
2021-11-29 11:55:50 +01:00
2021-12-01 15:11:51 -05:00
identifier = "datadog-logs"
2021-11-23 11:08:27 -05:00
active = True
config_required = False
andToken = " AND "
orToken = " OR "
notToken = "-"
subExpression = "(%s)"
2021-11-23 17:51:03 +01:00
listExpression = "(%s)"
2021-12-21 12:17:13 +01:00
# List selection items are linked with a logical 'OR' per the Sigma specification:
# https://github.com/SigmaHQ/sigma/wiki/Specification#lists.
2021-11-23 17:51:03 +01:00
listSeparator = " OR "
2021-11-29 15:11:29 +01:00
valueExpression = "%s"
2021-11-23 11:08:27 -05:00
mapExpression = "%s:%s"
2021-11-29 16:13:23 +01:00
nullExpression = "-%s:*"
notNullExpression = "%s:*"
2021-11-23 17:51:03 +01:00
2021-11-29 15:11:29 +01:00
# The escaped characters list comes from https://docs.datadoghq.com/logs/explorer/search_syntax/#escaping-of-special-characters.
2021-12-02 17:28:17 +01:00
specialCharactersRegexp = re.compile(r'([+\-=&|><!(){}\[\]^"~?:\\/]+)')
2021-12-15 17:14:58 +01:00
whitespacesRegexp = re.compile(r"\s")
2021-11-29 15:11:29 +01:00
2021-12-15 17:26:45 +01:00
# Default tags taken from https://docs.datadoghq.com/getting_started/tagging/#introduction.
tags = ["index", "service", "source", "host", "device", "env", "version"]
2021-11-29 17:23:23 +01:00
2021-12-21 12:02:31 +01:00
def __init__(self, sigmaconfig, backend_options=None):
if backend_options is None:
backend_options = {}
2021-11-23 18:11:46 +01:00
if "index" in backend_options:
self.dd_index = backend_options["index"]
2021-12-02 10:21:40 -05:00
if "service" in backend_options:
self.dd_service = backend_options["service"]
2021-12-02 17:28:17 +01:00
if "source" in backend_options:
self.dd_source = backend_options["source"]
2021-12-15 17:26:45 +01:00
if "env" in backend_options:
self.dd_env = backend_options["env"]
2021-11-29 18:08:34 +01:00
if sigmaconfig.config:
2021-12-15 17:26:45 +01:00
self.tags += sigmaconfig.config.get("tags", [])
2021-11-29 17:23:23 +01:00
2021-11-23 18:11:46 +01:00
super().__init__(sigmaconfig)
2021-11-23 17:51:03 +01:00
def generateQuery(self, parsed):
2021-11-29 11:55:50 +01:00
nodes = []
2021-11-23 18:11:46 +01:00
2021-11-25 16:14:02 +01:00
if hasattr(self, "dd_index"):
2021-11-29 15:11:29 +01:00
nodes.append(("index", self.dd_index))
2021-11-29 11:55:50 +01:00
if hasattr(self, "dd_service"):
2021-11-29 15:11:29 +01:00
nodes.append(("service", self.dd_service))
2021-11-29 11:55:50 +01:00
2021-12-02 17:28:17 +01:00
if hasattr(self, "dd_source"):
nodes.append(("source", self.dd_source))
2021-12-15 17:26:45 +01:00
if hasattr(self, "dd_env"):
nodes.append(("env", self.dd_env))
2021-11-29 16:13:23 +01:00
if type(parsed.parsedSearch) == NodeSubexpression:
nodes.append(parsed.parsedSearch.items)
else:
nodes.append(parsed.parsedSearch)
2021-11-23 18:11:46 +01:00
return self.generateANDNode(nodes)
2021-11-29 15:11:29 +01:00
def cleanValue(self, val):
if type(val) == int:
return val
else:
2021-12-21 12:17:13 +01:00
# Whitespaces characters are replaced with a `?`.
# Datadog also supports escaping whitespaces by double quoting
# expression, but at the cost of losing the `*` pattern matching
# syntax that we wanted to preserve.
# Note that technically, `?` matches **any** single character.
2021-11-29 15:11:29 +01:00
return self.whitespacesRegexp.sub(
2021-12-21 12:17:13 +01:00
# Special characters are escaped with a `\` which requires to be escaped
# in Python as well (see https://docs.python.org/3/library/re.html).
# This explains the unusual number of `\` in the following regex definition.
2021-11-29 15:11:29 +01:00
"?", self.specialCharactersRegexp.sub("\\\\\g<1>", val)
)
2021-11-29 17:23:23 +01:00
def generateMapItemNode(self, node):
key, value = node
return super().generateMapItemNode(((self.wrap_key(key)), value))
def generateNULLValueNode(self, node):
return super().generateNULLValueNode((self.wrap_key(node)))
def generateNotNULLValueNode(self, node):
return super().generateNotNULLValueNode(self.wrap_key(node))
def wrap_key(self, key):
2021-12-15 17:26:45 +01:00
if key not in self.tags:
2021-11-29 17:23:23 +01:00
return "@%s" % key
else:
return key