Syslog Sender

Use the Syslog Sender integration to send messages and mirror incident War Room entries to Syslog.

Analytics & SIEM · Syslog

Details

IDSyslog Sender
ProviderOpen Source
CategoryAnalytics & SIEM
From Version5.5.0
Docker Imagedemisto/syslog:1.0.0.10133006
Supported ModulesAgentix XSIAM

README

Overview


Use the Syslog Sender integration to send messages in RFC 5424 message format and mirror incident War Room entries to Syslog.

Use Cases


  • Send messages to Syslog via TCP or UDP or TLS.
  • Mirror incident war room entries to Syslog.
  • Track any activity from the Playground and War Room to your SIEM for improved visibility. This activity should be logged and attributed to the specific analyst.

Configure Syslog Sender on Cortex XSOAR


Usage example for rsyslog

To allow sending messages to rsyslog via Cortex XSOAR, the following lines have to be in the rsyslog configuration:

For TCP:

module(load="imtcp")
input(type="imtcp" port="<port>")

For UDP:

module(load="imudp")
input(type="imudp" port="<port>")

Usage example for sending War Room/Playground actions to Syslog

  1. From the Incidents page, click an incident.
  2. Run the !mirror-investigation type=”all” command.
    If you receive an Investication mirrored to Syslog successfully response, any action in the War Room will be sent to Syslog.

For example:
Run the command !Print value=”test msg”

<14>1 2023-09-19T13:49:38.140870+00:00 a1427a8493b5 SysLogLogger 1 - - 29, 105@29, admin:   !Print value=" incident owner is ${incident.owner}"
<14>1 2023-09-19T13:49:38.162145+00:00 a4140f2eb707 SysLogLogger 1 - - 29, 106@29, DBot:    incident owner is admin

Syslog already contains the analyst name - admin (the user who performed the action).
The action is: !Print value=”test msg”
The action result appears on the second line.

If you run the same command with a different user on the same Cortex XSOAR instance, the output will be:

<14>1 2023-09-19T13:56:02.152486+00:00 a1427a8493b5 SysLogLogger 1 - - 8069, 86@8069, jsmith:   !Print value=" incident owner is ${incident.owner}"
<14>1 2023-09-19T13:56:02.180858+00:00 a4140f2eb707 SysLogLogger 1 - - 8069, 87@8069, DBot:    incident owner is admin

Username (analyst) is present and located before each command. In this case, jsmith.
The timestamp is present and loacated at the beginning of each string.
To determine the execution time (duration), calculate the difference between the second timestamp and the first.

Integration configuration

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Syslog Sender.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • IP Address (e.g. 127.0.0.1)
    • Port
    • Protocol (TCP / UDP)
    • Minimum severity of incidents to send messages on
    • Log level to send
    • Facility
    • Long running instance. Required for investigation mirroring.
    • Incident type
  4. Click Test to validate the URLs, token, and connection.

Commands


You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

  1. mirror-investigation
  2. send-notification

1. mirror-investigation


Mirrors the investigation’s War Room to syslog.

Base Command

mirror-investigation

Input
Argument Name Description Required
type The mirroring type. Can be “all”, which mirrors everything, “chat”, which mirrors only chats (not commands), or “none”, which stops all mirroring. Optional
Context Output

There is no context output for this command.

Command Example

!mirror-investigation

Human Readable Output

Investigation mirrored successfully.

2. send-notification


Sends a message to syslog.

Base Command

send-notification

Input
Argument Name Description Required
message The message content. Optional
entry An entry ID to send as a link. Optional
ignoreAddURL Whether to include a URL to the relevant component in Cortex XSOAR. Can be “true” or “false”. The default value is “false’. Optional
level Log level to send. Can be “DEBUG”, “INFO”, “WARNING”, “ERROR”, or “CRITICAL”. Optional
Context Output

There is no context output for this command.

Command Example

!send-notification message=Test ignoreAddURL=true

Human Readable Output

Message sent to Syslog successfully.

3. syslog-send


Send a message to Syslog

Base Command

syslog-send

Input
Argument Name Description Required
message The message content. Optional
level The log level to send. Can be “DEBUG”, “INFO”, “WARNING”, “ERROR”, or “CRITICAL”. Optional
address The Syslog server address. Optional
protocol The protocol to use Optional
port The Syslog server port (required for TCP or UDP protocols). Optional
facility The Syslog facility. Optional
Context Output

There is no context output for this command.

Command Example

!syslog-send address=127.0.0.1 port=514 protocol=TCP message=yo level=ERROR

Human Readable Output

Message sent to Syslog successfully.

Troubleshooting


Make sure you can access the Syslog server on the provided IP address and the port is open.

Demo Video


Sorry, your browser doesn't support embedded videos. You can download the video at: https://github.com/demisto/content-assets/blob/7982404664dc68c2035b7c701d093ec026628802/Assets/Syslog/syslog-sender-demo.mp4

Configuration parameters

  • address — IP Address (e.g., 127.0.0.1)
  • port — Port
  • protocol — Protocol
  • certificate
  • self_signed_certificate — Self Signed Certificate
  • facility — Facility
  • priority — Log level to send
  • severity — Minimum severity of incidents to send messages on

Commands (3)

  • mirror-investigation

    Mirrors the investigation's War Room to Syslog.

  • send-notification

    Sends a message to Syslog. Used by the mirroring functionality.

  • syslog-send

    Sends a message to Syslog.

import demistomock as demisto
from CommonServerPython import *
from CommonServerUserPython import *

""" IMPORTS """

from contextlib import contextmanager
from logging.handlers import SysLogHandler
from logging import Logger, getLogger, INFO, DEBUG, WARNING, ERROR, CRITICAL
from socket import SOCK_STREAM
from collections.abc import Generator
from tempfile import NamedTemporaryFile
from rfc5424logging import Rfc5424SysLogHandler
import socket
import ssl

""" CONSTANTS """

PLAYGROUND_INVESTIGATION_TYPE = 9
INCIDENT_OPENED = "incidentOpened"
LOGGING_LEVEL_DICT = {"LOG_INFO": INFO, "LOG_DEBUG": DEBUG, "LOG_WARNING": WARNING, "LOG_ERR": ERROR, "LOG_CRIT": CRITICAL}
FACILITY_DICT = {
    "LOG_AUTH": SysLogHandler.LOG_AUTH,
    "LOG_AUTHPRIV": SysLogHandler.LOG_AUTHPRIV,
    "LOG_CRON": SysLogHandler.LOG_CRON,
    "LOG_DAEMON": SysLogHandler.LOG_DAEMON,
    "LOG_FTP": SysLogHandler.LOG_FTP,
    "LOG_KERN": SysLogHandler.LOG_KERN,
    "LOG_LPR": SysLogHandler.LOG_LPR,
    "LOG_MAIL": SysLogHandler.LOG_MAIL,
    "LOG_NEWS": SysLogHandler.LOG_NEWS,
    "LOG_SYSLOG": SysLogHandler.LOG_SYSLOG,
    "LOG_USER": SysLogHandler.LOG_USER,
    "LOG_UUCP": SysLogHandler.LOG_UUCP,
    "LOG_LOCAL0": SysLogHandler.LOG_LOCAL0,
    "LOG_LOCAL1": SysLogHandler.LOG_LOCAL1,
    "LOG_LOCAL2": SysLogHandler.LOG_LOCAL2,
    "LOG_LOCAL3": SysLogHandler.LOG_LOCAL3,
    "LOG_LOCAL4": SysLogHandler.LOG_LOCAL4,
    "LOG_LOCAL5": SysLogHandler.LOG_LOCAL5,
    "LOG_LOCAL6": SysLogHandler.LOG_LOCAL6,
    "LOG_LOCAL7": SysLogHandler.LOG_LOCAL7,
}
SEVERITY_DICT = {"Unknown": 0, "Low": 1, "Medium": 2, "High": 3, "Critical": 4}

TCP = "tcp"
UDP = "udp"
TLS = "tls"
PROTOCOLS = {TCP, UDP, TLS}
MAX_PORT = 65535
DEFAULT_TCP_SYSLOG_PORT = 514
DEFAULT_TLS_SYSLOG_PORT = 6514

"""SyslogHandlerTLS"""


class SyslogHandlerTLS(logging.Handler):
    def __init__(self, address: str, port: int, log_level: int, facility: int, cert_path: str, if_self_sign_cert: bool):
        """
        Initialize a handler.
        """
        logging.Handler.__init__(self)
        self.address = address
        self.port = port
        self.certfile = cert_path
        self.facility = facility
        self.level = log_level
        # Create a TCP socket
        ssl_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Wrap the socket with SSL
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
        # In order to allow self signed certificate:
        if if_self_sign_cert:
            ssl_context.check_hostname = False
            ssl_context.verify_mode = ssl.CERT_NONE
        else:
            # Only load certificate for verification if not using self-signed cert
            ssl_context.load_verify_locations(self.certfile)
        ssl_sock = ssl_context.wrap_socket(ssl_sock, server_hostname=self.address)
        self.socket = ssl_sock
        try:
            self.socket.connect((self.address, self.port))
        except OSError as exc:
            if ssl_sock:
                ssl_sock.close()
            raise DemistoException(str(exc))

    def emit(self, record):
        """
        Emit a record.

        The record is formatted, and then sent to the syslog server. If
        exception information is present, it is NOT sent to the server.
        """
        ident = ""  # prepended to all messages
        try:
            msg = self.format(record)
            if ident:
                msg = ident + msg

            # Calculate the priority value
            priority = (self.facility << 3) | self.level
            # Construct the syslog message in RFC 5424 format
            syslog_message = "<{priority}>1 {timestamp} {hostname} {appname} {procid} {msgid} - {message}\n".format(
                priority=priority,
                timestamp=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
                hostname=socket.gethostname(),
                appname=record.name,
                procid=os.getpid(),
                msgid="-",
                message=self.format(record),
            )
            # Connect to the syslog server
            self.socket.send(syslog_message.encode("utf-8"))

        except Exception as e:
            if self.socket:
                self.socket.close()
            demisto.error(str(e))


""" Syslog Manager """


class SyslogManager:
    def __init__(
        self,
        address: str,
        port: int,
        protocol: str,
        logging_level: int,
        facility: int,
        cert_path: str | None,
        self_signed_certificate: bool,
    ):
        """
        Class for managing instances of a syslog logger.
        :param address: The IP address of the syslog server.
        :param port: The port of the syslog server.
        :param protocol: The messaging protocol (TCP / UDP / TLS).
        :param logging_level: The logging level.
        """
        self.address = address
        self.port = port
        self.protocol = protocol
        self.logging_level = logging_level
        self.facility = facility
        self.syslog_cert_path = cert_path
        self.self_signed_cert = self_signed_certificate

    @contextmanager  # type: ignore[misc, arg-type]
    def get_logger(self) -> Generator:
        """
        Get a new instance of a syslog logger.
        :return: syslog logger
        """
        if self.protocol == TLS and self.syslog_cert_path:
            demisto.debug("creating tls logger handler")
            handler = self.init_handler_tls(self.syslog_cert_path)
        else:
            demisto.debug("creating tcp/udp logger handler")
            handler = self._get_handler()
        syslog_logger = self._init_logger(handler)
        demisto.debug("logger was created ")
        try:
            yield syslog_logger
        finally:
            syslog_logger.removeHandler(handler)
            handler.close()

    def _get_handler(self) -> Rfc5424SysLogHandler:
        sock_kind = SOCK_STREAM if self.protocol == TCP else socket.SOCK_DGRAM
        return Rfc5424SysLogHandler(
            address=(self.address, self.port), facility=self.facility, socktype=sock_kind, utc_timestamp=True
        )

    def init_handler_tls(self, certfile: str):
        return SyslogHandlerTLS(
            address=self.address,
            port=self.port,
            cert_path=certfile,
            facility=self.facility,
            log_level=self.logging_level,
            if_self_sign_cert=self.self_signed_cert,
        )

    def _init_logger(self, handler: Rfc5424SysLogHandler | SyslogHandlerTLS) -> Logger:
        """
        Initialize a logger with a syslog handler.
        :param handler: A syslog handler
        :return: A syslog logger
        """
        syslog_logger = getLogger("SysLogLogger")
        syslog_logger.setLevel(self.logging_level)
        syslog_logger.addHandler(handler)
        return syslog_logger


""" HELPER FUNCTIONS """


def prepare_certificate_file(certificate: str) -> str:
    """
    Prepares the certificate file and key for ssl connection.
    Args:
        certificate (str): Certificate. For SSL connection.
    Returns:
        (str, str): certificate_path.
    """
    certificate_file = NamedTemporaryFile(delete=False)
    certificate_path = certificate_file.name
    certificate_file.write(bytes(certificate, "utf-8"))
    certificate_file.close()
    demisto.debug("Successfully preparing the certificate")
    return certificate_path


def init_manager(params: dict) -> SyslogManager:
    """
    Create a syslog manager instance according to provided parameters.
    :param params: Parameters for the syslog manager.
    :return: syslog manager
    """
    address = params.get("address")
    protocol = params.get("protocol", UDP).lower()
    facility = FACILITY_DICT.get(params.get("facility", "LOG_SYSLOG"), SysLogHandler.LOG_SYSLOG)
    logging_level = LOGGING_LEVEL_DICT.get(params.get("priority", "LOG_INFO"), INFO)
    certificate: Optional[str] = replace_spaces_in_credential(params.get("certificate", {}).get("password")) or params.get(
        "certificate", None
    )
    certificate_path: Optional[str] = None
    default_port: int = DEFAULT_TLS_SYSLOG_PORT if protocol == "tls" else DEFAULT_TCP_SYSLOG_PORT
    port = arg_to_number(params.get("port"), required=False) or default_port
    self_signed_certificate = params.get("self_signed_certificate", False)
    if not address:
        raise DemistoException("A address must be provided.")
    if port and (port < 0 or port > MAX_PORT):
        raise DemistoException(f"Given port: {port} is not valid and must be between 0-{MAX_PORT}")
    if protocol == "tls" and not certificate:
        raise DemistoException("A certificate must be provided in TLS protocol.")
    if certificate and protocol == "tls":
        certificate_path = prepare_certificate_file(certificate)
    return SyslogManager(address, port, protocol, logging_level, facility, certificate_path, self_signed_certificate)


def send_log(manager: SyslogManager, message: str, log_level: str):
    """
    Use a syslog manager to get a logger and send a message to syslog.
    :param manager: The syslog manager
    :param message: The message to send
    :param log_level: The logging level
    """
    with manager.get_logger() as syslog_logger:  # type: Logger
        if log_level == "DEBUG":
            syslog_logger.debug(message)
        if log_level == "INFO":
            syslog_logger.info(message)
        if log_level == "WARNING":
            syslog_logger.warning(message)
        if log_level == "ERROR":
            syslog_logger.error(message)
        if log_level == "CRITICAL":
            syslog_logger.critical(message)


def mirror_investigation():
    """
    Update the integration context with a new or existing mirror.
    """
    mirror_type = demisto.args().get("type", "all")

    investigation = demisto.investigation()

    if investigation.get("type") == PLAYGROUND_INVESTIGATION_TYPE:
        return_error("Can not perform this action in the playground.")

    investigation_id = investigation.get("id")

    demisto.mirrorInvestigation(investigation_id, f"{mirror_type}:FromDemisto", False)

    demisto.results("Investigation mirrored to Syslog successfully.")


""" Syslog send command """


def syslog_send_notification(manager: SyslogManager, min_severity: int):
    """
    Send a message to syslog
    :param manager: Syslog manager
    :param min_severity: Minimum severity of incidents to send messages about
    """
    message = demisto.args().get("message", "")
    entry = demisto.args().get("entry")
    ignore_add_url = demisto.args().get("ignoreAddURL", False)
    log_level = demisto.args().get("level", "INFO")
    severity = demisto.args().get("severity")  # From server
    message_type = demisto.args().get("messageType", "")  # From server

    if severity:
        try:
            severity = int(severity)
        except Exception:
            severity = None

    if message_type == INCIDENT_OPENED and (severity is not None and severity < min_severity):
        return

    if not message:
        message = ""

    message = message.replace("\n", " ").replace("\r", " ").replace("`", "")
    investigation = demisto.investigation()
    if investigation:
        investigation_id = investigation.get("id")
        if entry:
            message = f"{entry}, {message}"
        message = f"{investigation_id}, {message}"

    if ignore_add_url and isinstance(ignore_add_url, str):
        ignore_add_url = argToBoolean(ignore_add_url)
    if not ignore_add_url:
        investigation = demisto.investigation()
        server_links = demisto.demistoUrls()
        if investigation:
            if investigation.get("type") != PLAYGROUND_INVESTIGATION_TYPE:
                link = server_links.get("warRoom")
                if link:
                    if entry:
                        link += "/" + entry
                    message += f" {link}"
            else:
                link = server_links.get("server", "")
                if link:
                    message += f" {link}#/home"

    if not message:
        raise ValueError("No message received")

    send_log(manager, message, log_level)

    demisto.results("Message sent to Syslog successfully.")


def syslog_send(manager):
    message = demisto.args().get("message", "")
    log_level = demisto.args().get("level", "INFO")
    send_log(manager, message, log_level)
    demisto.results("Message sent to Syslog successfully.")


""" MAIN """


def main():
    LOG(f"Command being called is {demisto.command()}")
    try:
        if demisto.command() == "test-module":
            syslog_manager = init_manager(demisto.params())
            with syslog_manager.get_logger() as syslog_logger:  # type: Logger
                syslog_logger.info("The connection was successfully established")
            demisto.results("ok")
        elif demisto.command() == "mirror-investigation":
            mirror_investigation()
        elif demisto.command() == "syslog-send":
            if "address" in demisto.args():
                # params provided in the command args
                syslog_manager = init_manager(demisto.args())
            else:
                syslog_manager = init_manager(demisto.params())
            syslog_send(syslog_manager)
        elif demisto.command() == "send-notification":
            min_severity = SEVERITY_DICT.get(demisto.params().get("severity", "Low"), 1)
            syslog_manager = init_manager(demisto.params())
            syslog_send_notification(syslog_manager, min_severity)
    except Exception as e:
        exception_msg = str(e)
        error_message = f"The following error was thrown: {exception_msg} "
        if "PEM lib (_ssl.c:4123)" in exception_msg:
            error_message += (
                "Potential causes could include: "
                "That the certificate is not in the correct format (e.g. it's not in PEM format)- "
                "Make sure to insert the Certificate was insert correctly. "
                "or, The certificate is expired or otherwise invalid"
            )
        elif "CERTIFICATE_VERIFY_FAILED" in exception_msg:
            error_message += (
                "If the certificate is self sign, make sure to check the Self Signed Certificate button."
                "Otherwise, The certificate is not trusted by the system or by the client trying to"
                " establish the connection"
            )
        elif "UnicodeError: label too long" in exception_msg:
            error_message += (
                "\nPotential cause could be too long URL label, which means  there is a part of the"
                " URL between two dots that is longer than 64 chars."
            )
        raise DemistoException(error_message)


if __name__ in ["__main__", "__builtin__", "builtins"]:
    main()