summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cyberark/pas/plugins/event_source
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/cyberark/pas/plugins/event_source
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cyberark/pas/plugins/event_source')
-rw-r--r--ansible_collections/cyberark/pas/plugins/event_source/cyberark-eda-json-v1.0.xsl175
-rw-r--r--ansible_collections/cyberark/pas/plugins/event_source/syslog.py173
2 files changed, 348 insertions, 0 deletions
diff --git a/ansible_collections/cyberark/pas/plugins/event_source/cyberark-eda-json-v1.0.xsl b/ansible_collections/cyberark/pas/plugins/event_source/cyberark-eda-json-v1.0.xsl
new file mode 100644
index 000000000..d9ee36467
--- /dev/null
+++ b/ansible_collections/cyberark/pas/plugins/event_source/cyberark-eda-json-v1.0.xsl
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:import href="./Syslog/RFC5424Changes.xsl"/>
+<xsl:output method='text' version='1.0' encoding='UTF-8' indent='no'/>
+
+<!-- version control variables -->
+<xsl:variable name="format" select="'cyberark-eda-json'"/>
+<xsl:variable name="version" select="'1.0'"/>
+
+<!-- configuration -->
+<xsl:variable name="include_raw" select="0"/> <!-- save a "raw" key with the original XML -->
+
+<!-- main object with header info -->
+<xsl:template match="/">
+ <xsl:apply-imports/>
+ <xsl:text>{"format":"</xsl:text><xsl:value-of select="$format"/>
+ <xsl:text>","version":"</xsl:text><xsl:value-of select="$version"/>
+ <xsl:text>"</xsl:text>
+ <xsl:choose>
+ <xsl:when test="$include_raw=1">
+ <xsl:text>,"raw":</xsl:text>
+ <xsl:call-template name="json-string">
+ <xsl:with-param name="text">
+ <xsl:apply-templates select="*" mode="raw"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+ <xsl:text>,</xsl:text>
+ <xsl:apply-templates select="*" mode="object"/>
+ <!-- this text below includes the terminating newline -->
+ <xsl:text>}&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="*" mode="raw">
+ <xsl:value-of select="concat('&lt;', name())" />
+ <xsl:for-each select="@*">
+ <xsl:value-of select="concat(' ', name(), '=&quot;', ., '&quot;')"/>
+ </xsl:for-each>
+ <xsl:text>&gt;</xsl:text>
+ <xsl:apply-templates mode="raw"/>
+ <xsl:value-of select="concat('&lt;/', name(), '&gt;')" />
+ </xsl:template>
+
+<!-- serialize objects -->
+<xsl:template match="*" mode="object">
+ <xsl:text>&quot;</xsl:text>
+ <xsl:value-of select="name()"/><xsl:text>&quot;:</xsl:text><xsl:call-template name="value">
+ <xsl:with-param name="parent" select="1"/>
+ </xsl:call-template>
+</xsl:template>
+
+<!-- serialize array elements -->
+<xsl:template match="*" mode="array">
+ <xsl:call-template name="value"/>
+</xsl:template>
+
+<!-- value of node serializer -->
+<xsl:template name="value">
+ <xsl:param name="parent"/>
+ <xsl:variable name="childName" select="name(*[1])"/>
+ <xsl:choose>
+ <xsl:when test="not(*|@*)">
+ <xsl:choose>
+ <xsl:when test="$parent=1">
+ <xsl:call-template name="json-string">
+ <xsl:with-param name="text" select="."/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="json-string">
+ <xsl:with-param name="text" select="name()"/>
+ </xsl:call-template>
+ <xsl:text>:</xsl:text>
+ <xsl:call-template name="json-string">
+ <xsl:with-param name="text" select="."/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="count(*[name()=$childName]) > 1">
+ <xsl:text>{</xsl:text>
+ <xsl:call-template name="json-string">
+ <xsl:with-param name="text" select="$childName"/>
+ </xsl:call-template>
+ <xsl:text>:[</xsl:text>
+ <xsl:apply-templates select="*" mode="array"/>
+ <xsl:text>]}</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>{</xsl:text>
+ <xsl:apply-templates select="@*" mode="attrs"/>
+ <xsl:if test='count(@*)>0 and count(*)>0'>,</xsl:if>
+ <xsl:apply-templates select="*" mode="object"/>
+ <xsl:text>}</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="following-sibling::*"><xsl:text>,</xsl:text></xsl:if>
+</xsl:template>
+
+<!-- serialize attributes -->
+<xsl:template match="@*" mode="attrs">
+ <xsl:call-template name="json-string">
+ <xsl:with-param name="text" select="name()"/>
+ </xsl:call-template>
+ <xsl:text>:</xsl:text>
+ <xsl:call-template name="json-string">
+ <xsl:with-param name="text" select="."/>
+ </xsl:call-template>
+ <xsl:if test="position()!=last()"><xsl:text>,</xsl:text></xsl:if>
+</xsl:template>
+
+<!-- json-string converts a text to a quoted and escaped JSON string -->
+<xsl:template name="json-string">
+ <xsl:param name="text"/>
+ <xsl:variable name="tmp">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="string" select="$text"/>
+ <xsl:with-param name="from" select="'\'"/>
+ <xsl:with-param name="to" select="'\\'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="tmp2">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="string" select="$tmp"/>
+ <xsl:with-param name="from" select="'&#xa;'"/>
+ <xsl:with-param name="to" select="'\n'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="tmp3">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="string" select="$tmp2"/>
+ <xsl:with-param name="from" select="'&#xd;'"/>
+ <xsl:with-param name="to" select="'\r'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:variable name="tmp4">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="string" select="$tmp3"/>
+ <xsl:with-param name="from" select="'&#x09;'"/>
+ <xsl:with-param name="to" select="'\t'"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:text>&quot;</xsl:text>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="string" select="$tmp4"/>
+ <xsl:with-param name="from" select="'&quot;'"/>
+ <xsl:with-param name="to" select="'\&quot;'"/>
+ </xsl:call-template>
+ <xsl:text>&quot;</xsl:text>
+</xsl:template>
+
+<!-- replace all occurences of the character(s) `from'
+ by the string `to' in the string `string'.-->
+<xsl:template name="string-replace">
+ <xsl:param name="string"/>
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+ <xsl:choose>
+ <xsl:when test="contains($string,$from)">
+ <xsl:value-of select="substring-before($string,$from)"/>
+ <xsl:value-of select="$to"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="string" select="substring-after($string,$from)"/>
+ <xsl:with-param name="from" select="$from"/>
+ <xsl:with-param name="to" select="$to"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$string"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/ansible_collections/cyberark/pas/plugins/event_source/syslog.py b/ansible_collections/cyberark/pas/plugins/event_source/syslog.py
new file mode 100644
index 000000000..2fbe1916c
--- /dev/null
+++ b/ansible_collections/cyberark/pas/plugins/event_source/syslog.py
@@ -0,0 +1,173 @@
+#!/usr/bin/python
+# Copyright: (c) 2017, Ansible Project
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import (absolute_import, division, print_function)
+
+
+__metaclass__ = type
+
+"""
+syslog.py
+
+An ansible-rulebook event source module for receiving events via a syslog.
+
+Arguments:
+ host: The hostname to listen to. Set to 0.0.0.0 to listen on all
+ interfaces. Defaults to 127.0.0.1
+ port: The TCP port to listen to. Defaults to 1514
+
+"""
+
+import asyncio
+import json
+import logging
+import socketserver
+from typing import Any, Dict
+import re
+
+def parse(str_input):
+ """
+ Parse a string in CEF format and return a dict with the header values
+ and the extension data.
+ """
+
+ logger = logging.getLogger()
+ # Create the empty dict we'll return later
+ values = dict()
+
+ # This regex separates the string into the CEF header and the extension
+ # data. Once we do this, it's easier to use other regexes to parse each
+ # part.
+ header_re = r'((CEF:\d+)([^=\\]+\|){,7})(.*)'
+
+ res = re.search(header_re, str_input)
+
+ if res:
+ header = res.group(1)
+ extension = res.group(4)
+
+ # Split the header on the "|" char. Uses a negative lookbehind
+ # assertion to ensure we don't accidentally split on escaped chars,
+ # though.
+ spl = re.split(r'(?<!\\)\|', header)
+
+ # If the input entry had any blanks in the required headers, that's wrong
+ # and we should return. Note we explicitly don't check the last item in the
+ # split list becuase the header ends in a '|' which means the last item
+ # will always be an empty string (it doesn't exist, but the delimiter does).
+ if "" in spl[0:-1]:
+ logger.warning(f'Blank field(s) in CEF header. Is it valid CEF format?')
+ return None
+
+ # Since these values are set by their position in the header, it's
+ # easy to know which is which.
+ values["DeviceVendor"] = spl[1]
+ values["DeviceProduct"] = spl[2]
+ values["DeviceVersion"] = spl[3]
+ values["DeviceEventClassID"] = spl[4]
+ values["Name"] = spl[5]
+ values["DeviceName"] = spl[5]
+ if len(spl) > 6:
+ values["Severity"] = spl[6]
+ values["DeviceSeverity"] = spl[6]
+
+ # The first value is actually the CEF version, formatted like
+ # "CEF:#". Ignore anything before that (like a date from a syslog message).
+ # We then split on the colon and use the second value as the
+ # version number.
+ cef_start = spl[0].find('CEF')
+ if cef_start == -1:
+ return None
+ (cef, version) = spl[0][cef_start:].split(':')
+ values["CEFVersion"] = version
+
+ # The ugly, gnarly regex here finds a single key=value pair,
+ # taking into account multiple whitespaces, escaped '=' and '|'
+ # chars. It returns an iterator of tuples.
+ spl = re.findall(r'([^=\s]+)=((?:[\\]=|[^=])+)(?:\s|$)', extension)
+ for i in spl:
+ # Split the tuples and put them into the dictionary
+ values[i[0]] = i[1]
+
+ # Process custom field labels
+ for key in list(values.keys()):
+ # If the key string ends with Label, replace it in the appropriate
+ # custom field
+ if key[-5:] == "Label":
+ customlabel = key[:-5]
+ # Find the corresponding customfield and replace with the label
+ for customfield in list(values.keys()):
+ if customfield == customlabel:
+ values[values[key]] = values[customfield]
+ del values[customfield]
+ del values[key]
+ else:
+ # return None if our regex had now output
+ # logger.warning('Could not parse record. Is it valid CEF format?')
+ return None
+
+ # Now we're done!
+ logger.debug('Returning values: ' + str(values))
+ return values
+
+
+class SyslogProtocol(asyncio.DatagramProtocol):
+ def __init__(self, edaQueue):
+ super().__init__()
+ self.edaQueue = edaQueue
+ def connection_made(self, transport) -> "Used by asyncio":
+ self.transport = transport
+
+ def datagram_received(self, data, addr):
+ asyncio.get_event_loop().create_task(self.datagram_received_async( data, addr))
+
+ async def datagram_received_async(self, indata, addr) -> "Main entrypoint for processing message":
+ # Syslog event data received, and processed for EDA
+ logger = logging.getLogger()
+ rcvdata = indata.decode()
+ logger.info(f"Received Syslog message: {rcvdata}")
+ data = parse(rcvdata)
+
+ if data is None:
+ # if not CEF, we will try JSON load of the text from first curly brace
+ try:
+ value = rcvdata[rcvdata.index("{"):len(rcvdata)]
+ #logger.info("value after encoding:%s", value1)
+ data = json.loads(value)
+ #logger.info("json:%s", data)
+ except json.decoder.JSONDecodeError as jerror:
+ logger.error(jerror)
+ data = rcvdata
+ except UnicodeError as e:
+ logger.error(e)
+
+ if data:
+ #logger.info("json data:%s", data)
+ queue = self.edaQueue
+ await queue.put({"cyberark": data})
+
+async def main(queue: asyncio.Queue, args: Dict[str, Any]):
+ logger = logging.getLogger()
+
+ loop = asyncio.get_event_loop()
+ host = args.get("host") or '0.0.0.0'
+ port = args.get("port") or 1514
+ transport, protocol = await asyncio.get_running_loop().create_datagram_endpoint(
+ lambda: SyslogProtocol(queue),
+ local_addr=((host, port)))
+ logger.info(f"Starting cyberark.pas.syslog [Host={host}, port={port}]")
+ try:
+ while True:
+ await asyncio.sleep(3600) # Serve for 1 hour.
+ finally:
+ transport.close()
+
+
+if __name__ == "__main__":
+
+ class MockQueue:
+ async def put(self, event):
+ pass #print(event)
+
+ asyncio.run(main(MockQueue(), {}))