UBIRCH

The UBIRCH solution can be seen as an external data certification provider, as a data notary service, giving data receivers the capability to verify data they have received with regard to its authenticity and integrity and correctness of sequence.

Data Enrichment & Threat Intelligence · UBIRCH

Details

IDUBIRCH
ProviderUBIRCH GmbH
CategoryData Enrichment & Threat Intelligence
From Version5.5.0
Docker Imagedemisto/py3-tools:1.0.0.114656
Supported ModulesAgentix XSIAM

README

The UBIRCH solution can be seen as an external data certification provider, as a data notary service, giving data receivers the capability to verify data they have received with regard to its authenticity and integrity and correctness of sequence.
This integration was integrated and tested with version v1.0.0 of UBIRCH

Configure UBIRCH in Cortex

Parameter Description Required
url Your MQTT host name True
port port True
credentials Username True
longRunning Long running instance False
tenantId Tenant Id True
stage Stage True

Usage

The UBIRCH integration is a long-running implementation. Whenever a verification of ubirched data fails, the incident is created with the following fields:

Field Type Description
name string Name of the incident to be created.
type string Type of the incident to be created. If not provided, the value of the integration parameter Incident type will be used.
labels object RequestId and deviceId of the incident to be created. For example, [{"type":"requestId", "value":"94a55743-d285-487a-839f-3005f3f8854a"}, {"type":"hwDeviceId", "value":"ba70ad8b-a564-4e58-9a3b-224ac0f0153f"}]
severity string Severity level of the incident. levels are Critical, High, Medium, Low, Unknown.
occurred string Date the incident occurred in ISO-8601 format.
details object Details of the incident to be created. For example, {"field1":"value1","field2":"value2"}
raw_json object Details of the incident to be created. For example, {"field1":"value1","field2":"value2"}

The incidents are based on these errors written in this page.

  • The error codes, NA401 - 4000, have a severity type HIGH because it may indicate that someone is trying to get authorization in an invalid manner OR that our ThingAPI is acting up.
  • The error codes, ND403 - 1200, have a severity type HIGH because this error would likely mean that the UPP is corrected in some way, which can be a red flag.
  • The error codes, ND400 - 2300, have a severity type MEDIUM as if the payload of a UPP is null, then no Hash can be processed. This is of course controlled, but it is very strange if the UPP is empty.
  • The rest of the errors have a severity type UNKNOWN.

create-sample-incidents


Create a list of sample incidents.

Base Command

create-sample-incidents

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Configuration parameters

  • url — Your MQTT host name (required)
  • port — Port (required)
  • credentials — Username (required)
  • longRunning — Long running instance
  • tenant_id — Tenant Id (required)
  • stage — Stage (required)

Commands (1)

  • create-sample-incidents

    Create a list of sample incidents.

import json
import traceback
from collections.abc import Callable

import demistomock as demisto
import paho
import paho.mqtt.client as mqtt
from CommonServerPython import *  # noqa # pylint: disable=unused-wildcard-import

from CommonServerUserPython import *  # noqa

""" CONSTANTS """

QOS_AT_LEAST_ONCE = 1

# SEVERITY
CRITICAL_SEVERITY = 4
HIGH_SEVERITY = 3
MEDIUM_SEVERITY = 2
LOW_SEVERITY = 1
UNKNOWN_SEVERITY = 0

# Types
AUTHENTICATION_TYPE = "Authentication"
AUTHENTICITY_TYPE = "UBIRCH Authenticity"
INTEGRITY_TYPE = "UBIRCH Integrity"
PRIVACY_TYPE = "UBIRCH Privacy"
SEQUENCE_TYPE = "UBIRCH Sequence"

# Fields
SEVERITY_FIELD = "severity"
TYPE_FIELD = "type"
MEANING_FIELD = "meaning"

INCIDENT_SEVERITY_MAP = {
    "niomon-auth": {
        "1000": {
            MEANING_FIELD: "Authentication error: request malformed. Possible missing header and parameters.",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: AUTHENTICATION_TYPE,
        },
        "2000": {
            MEANING_FIELD: "Authentication error: processing authentication response/Failed Request",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: AUTHENTICATION_TYPE,
        },
        "3000": {
            MEANING_FIELD: "Authentication error (3rd party): error processing authentication request",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: AUTHENTICATION_TYPE,
        },
        "4000": {
            MEANING_FIELD: "Authentication error: Failed Request",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: AUTHENTICATION_TYPE,
        },
    },
    "niomon-decoder": {
        "1100": {
            MEANING_FIELD: "Invalid verification: request malformed. Possible missing headers or parameters.",
            SEVERITY_FIELD: MEDIUM_SEVERITY,
            TYPE_FIELD: AUTHENTICITY_TYPE,
        },
        "1200": {
            MEANING_FIELD: "Invalid verification: invalid parts",
            SEVERITY_FIELD: HIGH_SEVERITY,
            TYPE_FIELD: AUTHENTICITY_TYPE,
        },
        "1300": {
            MEANING_FIELD: "Invalid verification: signature verification failed. No public key or integrity is compromised.",
            SEVERITY_FIELD: HIGH_SEVERITY,
            TYPE_FIELD: AUTHENTICITY_TYPE,
        },
        "2100": {
            MEANING_FIELD: "Decoding error: request malformed. Possible missing headers or parameters.",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: INTEGRITY_TYPE,
        },
        "2200": {MEANING_FIELD: "Decoding Error: Invalid Match", SEVERITY_FIELD: MEDIUM_SEVERITY, TYPE_FIELD: INTEGRITY_TYPE},
        "2300": {
            MEANING_FIELD: "Decoding Error: Decoding Error/Null Payload",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: INTEGRITY_TYPE,
        },
    },
    "niomon-enricher": {
        "1000": {
            MEANING_FIELD: "Tenant error: the owner of the device cannot be determined. Possible missing headers or "
            "parameters or body.",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: AUTHENTICITY_TYPE,
        },
        "2000": {
            MEANING_FIELD: "Tenant error: the owner of the device does not exist or cannot be acquired.",
            SEVERITY_FIELD: HIGH_SEVERITY,
            TYPE_FIELD: AUTHENTICITY_TYPE,
        },
        "0000": {
            MEANING_FIELD: "Tenant error: the owner of the device does not exist or cannot be acquired (3rd Party).",
            SEVERITY_FIELD: HIGH_SEVERITY,
            TYPE_FIELD: AUTHENTICITY_TYPE,
        },
    },
    "filter-service": {
        "0000": {
            MEANING_FIELD: "Integrity violation: duplicate hash detected. Possible injection, reply attack, "
            "or hash collision. ",
            SEVERITY_FIELD: HIGH_SEVERITY,
            TYPE_FIELD: SEQUENCE_TYPE,
        },
        "0010": {
            MEANING_FIELD: "Privacy violation: hash is already disabled or non-existent",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: PRIVACY_TYPE,
        },
        "0020": {
            MEANING_FIELD: "Privacy violation: hash is not disabled or non-existent",
            SEVERITY_FIELD: LOW_SEVERITY,
            TYPE_FIELD: PRIVACY_TYPE,
        },
        "0030": {
            MEANING_FIELD: "Privacy violation: hash does not exist. Possible DDOS attack",
            SEVERITY_FIELD: HIGH_SEVERITY,
            TYPE_FIELD: PRIVACY_TYPE,
        },
    },
}

""" CLIENT CLASS """


class Client:
    """Client class to subscribe the error from MQTT server

    This Client class is a wrapper class of the paho mqtt Client.

    Args:
        mqtt_host(str): MQTT server's host name
        mqtt_port(int): MQTT server's port number
        username(str): MQTT server's user name
        password(str): MQTT server's password
        stage(str): MQTT server's environment
        tenant_id(str): tenant id related with errors subscribed

    Return:
        None
    """

    def __init__(self, mqtt_host: str, mqtt_port: int, username: str, password: str, stage: str, tenant_id: str):
        self.mqtt_client = mqtt.Client()
        self.mqtt_client.username_pw_set(username, password)
        self.mqtt_host = mqtt_host
        self.mqtt_port = mqtt_port
        self.topic = f"com/ubirch/{stage}/incident/tenant/{tenant_id}"

    def connect(self, on_connect_callback: Callable[[mqtt.Client, dict, dict, int], None] = None) -> None:
        if on_connect_callback is not None:
            self.mqtt_client.on_connect = on_connect_callback
        self.mqtt_client.connect(self.mqtt_host, self.mqtt_port)

    def subscribe(self, on_message_callback: Callable[[mqtt.Client, dict, mqtt.MQTTMessage], None] = None) -> None:
        if on_message_callback is not None:
            self.mqtt_client.on_message = on_message_callback
        self.mqtt_client.subscribe(self.topic, QOS_AT_LEAST_ONCE)

    def loop_forever(self) -> None:
        self.mqtt_client.loop_forever()

    def loop_stop(self) -> None:
        self.mqtt_client.loop_stop()


""" HELPER FUNCTIONS """


def get_error_definition(incident: Dict) -> Dict:
    """Return the error definition from the incident

    Ex. { "meaning": "xxx", "severity": 1, "type": "AUTHENTICATION" }

    Args:
        incident(Dict): an incident

    Return:
        Dict: error definition
    """
    microservice: str = incident.get("microservice", "")
    error_code: str = incident.get("errorCode", "")
    # ex. { "1000": { ... }, "1100": { ... } }
    error_codes: Dict = INCIDENT_SEVERITY_MAP.get(microservice, {})
    # ex. { "meaning": "xxx", "severity": 1, "type": "AUTHENTICATION" }
    return error_codes.get(error_code, {})


def create_incidents(error_message: str) -> list:
    """Create the incidents

    Args:
        error_message (str): this is the message payload from MQTT server

    Return:
        list: list of incidents
    """
    incident_dict = json.loads(error_message)
    error_definition = get_error_definition(incident_dict)
    return [
        {
            "name": error_definition.get(MEANING_FIELD, incident_dict.get("error")),
            "type": error_definition.get(TYPE_FIELD, ""),
            "labels": [
                {"type": "requestId", "value": incident_dict.get("requestId")},
                {"type": "hwDeviceId", "value": incident_dict.get("hwDeviceId")},
            ],
            "rawJSON": json.dumps(incident_dict),
            "details": json.dumps(incident_dict),
            "severity": error_definition.get(SEVERITY_FIELD, UNKNOWN_SEVERITY),
        }
    ]


""" COMMAND FUNCTIONS """


def long_running_execution(client: Client) -> None:
    """Connects to a MQTT Server and subscribe the error from it in a loop.

    Args:
        client(Client): client class which is a wrapper of the mqtt.Client class.

    Return:
        None: no data returned
    """

    def on_connect(_client: mqtt.Client, _userdata: dict, _flags: dict, rc: int) -> None:
        """
        Callback function when a MQTT client connects to the server

        Check if the connection is succeeded.
        The rc argument is a connection result.
        """
        if rc != mqtt.MQTT_ERR_SUCCESS:
            demisto.info(mqtt.connack_string(rc))
            raise paho.mqtt.MQTTException(mqtt.connack_string(rc))
        else:
            demisto.info(
                f"connection was succeeded for a long-running container. host: {client.mqtt_host}, port: {client.mqtt_port}"
            )

    def on_message(_client: mqtt.Client, _userdata: dict, message: mqtt.MQTTMessage) -> None:
        """
        Callback function when a MQTT client subscribes to a message from the server

        Create incidents, when the client subscribes to an error from the mqtt server.
        """
        demisto.info(f"on message. {message.topic} {message.qos} {message.payload}")  # type: ignore[str-bytes-safe]
        incidents = create_incidents(message.payload.decode("utf-8", "ignore"))  # the message payload is binary.
        demisto.info(f"catch an incident. {incidents}")
        demisto.createIncidents(incidents)

    try:
        client.connect(on_connect)
        client.subscribe(on_message)
        client.loop_forever()
    except Exception as e:
        demisto.error(f"An error occurred in the long running loop: {e}")
    finally:
        client.loop_stop()


def test_module(client: Client) -> None:
    """Check if the user configuration is correct

    Return:
        None: no data returned
    """

    def on_connect(_client: mqtt.Client, _userdata: dict, _flags: dict, rc: int) -> None:
        """
        Callback function when a MQTT client connects to the server

        Check if the connection is succeeded.
        Stop the loop regardless of whether the connection is succeeded or not.
        The rc argument is a connection result.
        """
        _client.disconnect()
        _client.loop_stop()
        if rc != mqtt.MQTT_ERR_SUCCESS:
            demisto.info(mqtt.connack_string(rc))
            raise paho.mqtt.MQTTException(mqtt.connack_string(rc))
        else:
            demisto.info("connection was succeeded for test")

    try:
        client.connect(on_connect)
        client.subscribe()
        client.loop_forever()
    except Exception as e:
        raise DemistoException(f"Test failed. Please check your parameters. \n {e}")
    demisto.results("ok")


def create_sample_incidents() -> None:
    """Extract sample events stored in the integration context and create them as incidents

    Return:
        None: no data returned
    """
    integration_context = get_integration_context()
    sample_events = integration_context.get("sample_events")
    if sample_events:
        try:
            incidents = [{"name": "sample_event", "rawJSON": json.dumps(event)} for event in json.loads(sample_events)]
            demisto.createIncidents(incidents)
            demisto.info("it was succeeded to create the sample incident.")
        except json.decoder.JSONDecodeError as e:
            raise ValueError(f"Failed deserializing sample events - {e}")
    else:
        incidents = [
            {
                "name": "sample incident.",
            }
        ]
        demisto.createIncidents(incidents)
        demisto.info("it was succeeded to create the sample incident.")


demisto.info("connection was succeeded for test")


def main() -> None:
    """main function, parses params and runs command functions"""

    params = demisto.params()
    username = params["credentials"].get("identifier")
    password = params["credentials"].get("password")
    tenant_id = params["tenant_id"]
    mqtt_host = params["url"]
    mqtt_port = params["port"]
    stage = params["stage"]

    command = demisto.command()
    demisto.debug(f"Command being called is {command}")

    try:
        try:
            mqtt_port = int(mqtt_port)
        except ValueError as e:
            raise ValueError(f"Invalid the mqtt server's port - {e}")

        client = Client(
            mqtt_host=mqtt_host, mqtt_port=mqtt_port, username=username, password=password, stage=stage, tenant_id=tenant_id
        )

        if command == "test-module":
            # This is the call made when pressing the integration Test button.
            test_module(client)

        elif command == "long-running-execution":
            long_running_execution(client)

        elif command == "create-sample-incidents":
            create_sample_incidents()

    # Log exceptions and return errors
    except Exception as e:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}")


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