Esempio n. 1
0
def build_request(request, headers, device):
    """
    Helper method to organized required headers into the CoAP Request.
    :param request: Request being build
    :param headers: Data from AMQP message which contains data to forward OpenC2 Command.
    :param device:  Device specific data from headers sent by O.I.F.
    """
    orc_host, orc_port = headers["transport"]["socket"].split(
        ":", 1)  # location of orchestrator-side CoAP server
    request.source = (orc_host, safe_cast(orc_port, int, 5683))

    dev_host, dev_port = device["socket"].split(
        ":", 1)  # location of device-side CoAP server
    request.destination = (dev_host, safe_cast(dev_port, int, 5683))

    encoding = f"application/{device['encoding']}"  # Content Serialization
    request.content_type = defines.Content_types[
        encoding]  # using application/json, TODO: add define to openc2+json
    request.mid = int("0x" + headers["correlationID"],
                      16)  # 16-bit correlationID
    request.timestamp = headers[
        "date"]  # time message was sent from orchestrator

    # Add OIF-unique value used for routing to the desired actuator
    profile = Option()
    profile.number = 8
    profile.value = device.get("profile", "")[0]
    request.add_option(profile)

    source_socket = Option()
    source_socket.number = 3
    source_socket.value = headers["transport"]["socket"]
    request.add_option(source_socket)

    return request
Esempio n. 2
0
    def __init__(self,
                 hostname='127.0.0.1',
                 port=5672,
                 auth=_auth,
                 exchange=_exchange,
                 consumer_key=_consumerKey,
                 producer_exchange=_producerExchange,
                 callbacks=None):
        """
        Message Queue - holds a consumer class and producer class for ease of use
        :param hostname: server ip/hostname to connect
        :param port: port the AMQP Queue is listening
        :param exchange: name of the default exchange
        :param consumer_key: key to consumer
        :param producer_exchange: ...
        :param callbacks: list of functions to call on message receive
        """
        self._exchange = exchange if isinstance(exchange,
                                                str) else self._exchange
        self._consumerKey = consumer_key if isinstance(
            consumer_key, str) else self._consumerKey
        self._producerExchange = producer_exchange if isinstance(
            producer_exchange, str) else self._producerExchange

        self._publish_opts = dict(host=hostname, port=safe_cast(port, int))

        self._consume_opts = dict(host=hostname,
                                  port=safe_cast(port, int),
                                  exchange=self._exchange,
                                  routing_key=self._consumerKey,
                                  callbacks=callbacks)

        self.producer = Producer(**self._publish_opts)
        self.consumer = Consumer(**self._consume_opts)
Esempio n. 3
0
def mqtt_publish(recipients: List[str], source: dict, device: dict,
                 body: Union[bytes, str], topic: str, auth: Auth,
                 headers: dict) -> None:
    # pylint: disable=unbalanced-tuple-unpacking
    (orc_id, corr_id) = destructure(source, "orchestratorID", "correlationID")
    # pylint: disable=unbalanced-tuple-unpacking
    (fmt, encoding, broker_socket) = destructure(device,
                                                 ("format", "broadcast"),
                                                 ("encoding", "json"),
                                                 ("socket", "localhost:1883"))
    (host, port) = broker_socket.split(":", 1)
    payload = Message(recipients=recipients,
                      origin=f"{orc_id}@{broker_socket}",
                      msg_type=MessageType.Request,
                      request_id=uuid.UUID(corr_id),
                      content_type=SerialFormats(encoding)
                      if encoding in SerialFormats else SerialFormats.JSON,
                      content=json.loads(body))
    print(f"Sending {broker_socket} topic: {topic} -> {payload}")
    publish_props = Properties(PacketTypes.PUBLISH)
    publish_props.PayloadFormatIndicator = int(
        SerialFormats.is_binary(payload.content_type) is False)
    publish_props.ContentType = "application/openc2"  # Content-Type
    publish_props.UserProperty = ("msgType", payload.msg_type)  # User Property
    publish_props.UserProperty = ("encoding", payload.content_type
                                  )  # User Property

    try:
        publish_single(config=FrozenDict(MQTT_HOST=host,
                                         MQTT_PORT=safe_cast(port, int, 1883),
                                         USERNAME=auth.username,
                                         PASSWORD=auth.password,
                                         TLS_SELF_SIGNED=safe_cast(
                                             os.environ.get(
                                                 "MQTT_TLS_SELF_SIGNED", 0),
                                             int, 0),
                                         CAFILE=auth.caCert,
                                         CLIENT_CERT=auth.clientCert,
                                         CLIENT_KEY=auth.clientKey),
                       topic=topic,
                       payload=payload.serialize(),
                       properties=publish_props)
        print(f"Placed payload onto topic {topic} Payload Sent: {payload}")
    except Exception as e:
        print(
            f"There was an error sending command to {broker_socket} topic: {topic} -> {e}"
        )
        send_error_response(e, headers)
Esempio n. 4
0
def send_coap(body, message):
    """
    AMQP Callback when we receive a message from internal buffer to be published
    :param body: Contains the message to be sent.
    :param message: Contains data about the message as well as headers
    """
    # Set destination and build requests for multiple potential endpoints.
    for device in message.headers.get("destination", {}):
        host, port = device["socket"].split(":", 1)
        encoding = device["encoding"]

        # Check necessary headers exist
        if host and port and encoding:
            path = "transport"
            client = CoapClient(server=(host, safe_cast(port, int, 5683)))
            request = client.mk_request(defines.Codes.POST, path)
            response = client.post(path=path,
                                   payload=encode_msg(json.loads(body),
                                                      encoding),
                                   request=build_request(
                                       request,
                                       message.headers.get("source", {}),
                                       device))

            if response:
                print(f"Response from device: {response}")
            client.stop()
        else:
            # send error back to orch
            print(f"Error: not enough data - {host}, {port}, {encoding}")
Esempio n. 5
0
def build_request(request, headers):
    """
    Helper method to organized required headers into the CoAP Request.
    :param request: Request being build
    :param headers: Data from AMQP message which contains data to forward OpenC2 Command.
    """
    dev_host, dev_port = headers["socket"].split(
        ":", 1)  # location of device-side CoAP server
    request.source = (dev_host, safe_cast(dev_port, int, 5683))

    orc_host, orc_port = headers["socket"].split(
        ":", 1)  # location of orchestrator-side CoAP server
    request.destination = (orc_host, safe_cast(orc_port, int, 5683))

    encoding = f"application/{headers['encoding']}"  # Content Serialization
    request.content_type = defines.Content_types[
        encoding]  # using application/json, TODO: add define to openc2+json

    request.mid = headers["correlationID"]  # 16-bit value - correlationID

    return request
Esempio n. 6
0
    def _check_subscribe(self, data: FrozenDict) -> None:
        socket = "{host}:{port}".format(**data)
        client = self._clients.setdefault(
            socket,
            mqtt.Client(client_id=self.client_id,
                        userdata=self.topics,
                        protocol=mqtt.MQTTv5,
                        transport="tcp"))

        if client.is_connected():
            print(f"Update connection: {socket}")
            # TODO: Update connection??

            # Set topics
            # TODO: Set topics based on prefix
            # topics = ["+/+/oc2/rsp", "+/oc2/rsp", "oc2/rsp"]
            # client.user_data_set(topics)

        else:
            print(f"Create connection: {socket}")
            # Auth
            with Auth(data) as auth:
                if username := auth.username:
                    client.username_pw_set(username=username,
                                           password=auth.password)

                # TLS
                if auth.caCert and auth.clientCert and auth.clientKey:
                    client.tls_set(ca_certs=auth.caCert,
                                   certfile=auth.clientCert,
                                   keyfile=auth.clientKey,
                                   tls_version=ssl.PROTOCOL_TLSv1_2)

                # Set callbacks
                client.on_connect = mqtt_on_connect
                client.on_message = mqtt_on_message
                if self.debug:
                    client.on_log = mqtt_on_log

                try:
                    client.connect(
                        host=data["host"],
                        port=safe_cast(data["port"], int, 1883),
                        keepalive=300,
                        clean_start=mqtt.MQTT_CLEAN_START_FIRST_ONLY)
                except Exception as e:
                    print(f"MQTT Error: {e}")

                print(f"Connect to MQTT broker: {socket}")
                client.loop_start()
Esempio n. 7
0
def send_coap(body, message):
    """
    AMQP Callback when we receive a message from internal buffer to be published
    :param body: Contains the message to be sent.
    :param message: Contains data about the message as well as headers
    """
    host, port = message.headers["socket"].split(":", 1)
    path = "transport"

    client = CoapClient(server=(host, safe_cast(port, int, 5683)))
    request = client.mk_request(defines.Codes.POST, path)
    response = client.post(path=path,
                           payload=body,
                           request=build_request(request, message.headers))
    if response:
        print(f"Response from orchestrator: {response}")
    client.stop()
Esempio n. 8
0
    def _socketMsg(self, action, act_args, *args, **kwargs):
        auth = kwargs.get("headers", {}).get("Authorization", "")
        token = re.sub(r"^JWT\s+", "", auth) if auth.startswith("JWT") else ""

        url = f"api{act_args['url'].format(*args)}"
        url_params = act_args.get('params', {})
        if len(url_params) > 0:
            url += f"?{'&'.join(f'{k}={v}' for k, v in url_params.items())}"

        rtn = dict(
            body={},
            method=act_args["method"],
            status_code=500,
            url=f"{self._root_url}{url}",
            # Extra Options
            meta={})

        try:
            self._webSocket.send(
                json.dumps(
                    dict(endpoint=url,
                         method=act_args["method"],
                         jwt=token,
                         data=kwargs.get("body", {}),
                         types=dict(
                             success=f"@@socket/{action.upper()}_SUCCESS",
                             failure=f"@@socket/{action.upper()}_FAILURE"))))

            try:
                rslt = json.loads(self._webSocket.recv())
            except ValueError as e:
                rslt = {}

            rtn.update(
                body=rslt.get('payload', {}),
                status_code=safe_cast(
                    rslt.get('meta', {}).get("status_code", 200), int, 200),
                # Extra Options
                meta=rslt.get('meta', {}))
            return FrozenDict(rtn)

        except Exception as e:
            print(e)
            rtn.update(status_code=500, )
            return FrozenDict(rtn)
Esempio n. 9
0
    def _check_subscribe(self, data: FrozenDict) -> None:
        socket = '{host}:{port}'.format(**data)
        client = self.mqtt_clients.setdefault(
            socket,
            mqtt.Client(
                # TODO: add orc_id ??
                client_id=f"oif-orchestrator-subscribe"
                # clean_session=None
            ))

        if client.is_connected():
            # TODO: Update connection??
            pass
        else:
            # Auth
            with Auth(data) as auth:
                # prefix = data.get('prefix', None)
                if username := auth.username:
                    client.username_pw_set(username=username,
                                           password=auth.password)

                # TLS
                if auth.caCert and auth.clientCert and auth.clientKey:
                    client.tls_set(ca_certs=auth.caCert,
                                   certfile=auth.clientCert,
                                   keyfile=auth.clientKey)

                client.connect(
                    host=data['host'],
                    port=safe_cast(data['port'], int, 1883),
                    # keepalive=60,
                    # clean_start=MQTT_CLEAN_START_FIRST_ONLY
                )

                # Set topics
                # TODO: Set topics based on prefix
                topics = ['+/+/oc2/rsp', '+/oc2/rsp', 'oc2/rsp']
                client.user_data_set(topics)

                # Set callbacks
                client.on_connect = mqtt_on_connect
                client.on_message = mqtt_on_message

                print(f'Connect - {socket}')
                client.loop_start()
Esempio n. 10
0
    def __init__(self, root: str = _ROOT_DIR, act_id: str = _ACT_ID) -> None:
        """
        Initialize and start the Actuator Process
        :param root: rood directory of actuator - default CWD
        :param act_id: id of the actuator - default UUIDv4
        """
        config_file = os.path.join(root, "config.json")
        schema_file = os.path.join(root, "schema.json")

        config = general.safe_load(config_file)
        if "actuator_id" not in config.keys():
            config.setdefault("actuator_id", act_id)
            json.dump(config, open(config_file, "w"), indent=4)

        # Initialize etcd client
        self.etcdClient = etcd.Client(host=os.environ.get('ETCD_HOST', 'etcd'),
                                      port=safe_cast(
                                          os.environ.get('ETCD_PORT', 4001),
                                          int, 4001))

        schema = general.safe_load(schema_file)
        self._config = FrozenDict(**config, schema=schema)
        self._dispatch = dispatch.Dispatch(
            act=self, dispatch_transform=self._dispatch_transform)
        self._dispatch.register(exceptions.action_not_implemented, "default")
        self._pairs = None

        # Get valid Actions & Targets from the schema
        self._profile = self._config.schema.get("title",
                                                "N/A").replace(" ",
                                                               "_").lower()
        self._validator = general.ValidatorJSON(schema)
        schema_defs = self._config.schema.get("definitions", {})

        self._prefix = '/actuator'
        profiles = self.nsid if len(self.nsid) > 0 else [self._profile]

        for profile in profiles:
            self.etcdClient.write(f"{self._prefix}/{profile}",
                                  self._config.actuator_id)

        self._valid_actions = tuple(
            a["const"] for a in schema_defs.get("Action", {}).get("oneOf", []))
        self._valid_targets = tuple(
            schema_defs.get("Target", {}).get("properties", {}).keys())
Esempio n. 11
0
    def __init__(self, root=ROOT_DIR, act_id=ACT_ID, enable_etcd=True) -> None:
        """
        Initialize and start the Actuator Process
        :param root: rood directory of actuator - default CWD
        :param act_id: id of the actuator - default UUIDv4
        """
        config_file = os.path.join(root, "config.json")
        schema_file = os.path.join(root, "schema.json")

        # Set config
        config = general.safe_load(config_file)
        if "actuator_id" not in config.keys():
            config.setdefault("actuator_id", act_id)
            with open(config_file, "w", encoding="UTF-8") as f:
                json.dump(config, f, indent=4)
        schema = general.safe_load(schema_file)
        self._config = FrozenDict(
            **config,
            schema=schema
        )

        # Configure Action/Target functions
        self._dispatch = dispatch.Dispatch(namespace="root",  dispatch_transform=self._dispatch_transform, act=self)
        self._dispatch.register(exceptions.action_not_implemented, "default")

        # Get valid Actions & Targets from the schema
        self._validator = general.ValidatorJSON(schema)
        self._profile = self._config.schema.get("title", "N/A").replace(" ", "_").lower()
        schema_defs = self._config.schema.get("definitions", {})
        self._valid_actions = tuple(a["const"] for a in schema_defs.get("Action", {}).get("oneOf", []))
        self._valid_targets = tuple(schema_defs.get("Target", {}).get("properties", {}).keys())

        # Initialize etcd client and set profiles
        if enable_etcd:
            self._etcd = etcd.Client(
                host=os.environ.get('ETCD_HOST', 'etcd'),
                port=safe_cast(os.environ.get('ETCD_PORT', 4001), int, 4001)
            )
            profiles = self.nsid if len(self.nsid) > 0 else [self._profile]
            for profile in profiles:
                self._etcd.write(f"{self._prefix}/{profile}", self._config.actuator_id)
Esempio n. 12
0
def publish_single(config: FrozenDict,
                   topic,
                   payload,
                   client_id="",
                   properties: Properties = None):
    msg = {
        "topic": topic,
        "payload": payload,
        "qos": 1,
        "retain": False,
        "properties": properties
    }

    client = mqtt.Client(client_id=client_id,
                         userdata=[msg],
                         protocol=mqtt.MQTTv5,
                         transport="tcp")

    # set auth
    if config.USERNAME:
        client.username_pw_set(username=config.USERNAME,
                               password=config.PASSWORD)

    # check that certs exist
    if config.CAFILE and config.CLIENT_CERT and config.CLIENT_KEY:
        client.tls_insecure_set(safe_cast(config.TLS_SELF_SIGNED, bool, False))
        client.tls_set(ca_certs=config.CAFILE,
                       certfile=config.CLIENT_CERT,
                       keyfile=config.CLIENT_KEY,
                       tls_version=ssl.PROTOCOL_TLSv1_2)
    # set callbacks
    client.on_connect = _on_connect
    client.on_publish = _on_publish

    client.connect(host=config.MQTT_HOST,
                   port=config.MQTT_PORT,
                   keepalive=300,
                   clean_start=mqtt.MQTT_CLEAN_START_FIRST_ONLY)
    client.loop_forever()
except etcd.EtcdKeyNotFound:
    dev_uuid = uuid.uuid4()
    etcd_client.write('/device/id', str(dev_uuid))
dev_id = f'device_{str(dev_uuid).replace("-", "")[:15]}'


# begin listening to a single MQTT socket
print(f"Initializing Device: {dev_uuid}...")
client = mqtt.Client(
    client_id=dev_id,
    # clean_session=None
)

# check that certs exist
if Config.TLS_ENABLED:
    client.tls_insecure_set(safe_cast(Config.TLS_SELF_SIGNED, bool, False))
    client.tls_set(
        ca_certs=Config.CAFILE,
        certfile=Config.CLIENT_CERT,
        keyfile=Config.CLIENT_KEY
    )

if Config.USERNAME:
    client.username_pw_set(
        username=Config.USERNAME,
        password=Config.PASSWORD
    )

client.connect(
    host=Config.MQTT_HOST,
    port=Config.MQTT_PORT,
MESSAGE_QUEUE = None

# Security
CRYPTO = Fernet(os.environ['TRANSPORT_SECRET']
                ) if 'TRANSPORT_SECRET' in os.environ else None

# First key will be used to encrypt all new data
# Decryption of existing values will be attempted with all given keys in order
FERNET_KEYS = [
    k.decode('utf-8') if isinstance(k, bytes) else str(k) for k in [
        # Key Generation - URLSAFE_BASE64_ENCRYPT(RANDOM_32_BITS)
        '4k1wW0AwvNpOYLUazdXtpwLBc6MOaflTKV4UkkzVhS8=',
        base64.urlsafe_b64encode(SECRET_KEY[:32].encode('utf-8'))
    ] if k
]

# ETCD
ETCD = {
    'host': os.environ.get('ETCD_HOST', 'localhost'),
    'port': safe_cast(os.environ.get('ETCD_PORT', 4001), int, 4001)
}

ETCD_CLIENT = None

# App stats function
STATS_FUN = 'app_stats'

# GUI Configuration
ADMIN_GUI = True
# check that certs exist
if os.environ.get('MQTT_TLS_ENABLED', False):
    client.tls_set(
        ca_certs=os.environ.get('MQTT_CAFILE', None),
        certfile=os.environ.get('MQTT_CLIENT_CERT', None),
        keyfile=os.environ.get('MQTT_CLIENT_KEY', None)
    )
    client.username_pw_set(
        os.environ.get('MQTT_DEFAULT_USERNAME', 'guest'),
        os.environ.get('MQTT_DEFAULT_PASS', 'guest')
    )
    client.tls_insecure_set(os.environ.get('MQTT_TLS_SELF_SIGNED', 0))

client.connect(
    os.environ.get('MQTT_HOST', 'queue'),
    safe_cast(os.environ.get('MQTT_PORT', 1883), int, 1883)
)

topics = []
if "TRANSPORT_TOPICS" in os.environ:
    time.sleep(2)
    transports = [t.lower().strip() for t in os.environ.get("TRANSPORT_TOPICS", "").split(",")]
    topics.extend([t.lower().strip() for t in consumer.get_queues() if t not in transports])

if "MQTT_TOPICS" in os.environ:
    topics.extend([t.lower().strip() for t in os.environ.get("MQTT_TOPICS", "").split(",") if t not in topics])

client.user_data_set(topics)
client.on_connect = Callbacks.on_connect
client.on_message = Callbacks.on_message
client.loop_start()
import json
import os
import requests
import uuid
import kombu

from typing import Union
from sb_utils import Auth, Consumer, EtcdCache, FrozenDict, Message, MessageType, Producer, SerialFormats, safe_cast, safe_json

# Gather transport from etcd
transport_cache = EtcdCache(
    host=os.environ.get("ETCD_HOST", "localhost"),
    port=safe_cast(os.environ.get("ETCD_PORT", 2379), int, 2379),
    # Add base of 'orchestrator' ??
    base='transport/HTTPS',
    callbacks=[lambda d: print(f"Update - {d}")])


def process_message(body: Union[dict, str], message: kombu.Message) -> None:
    """
    Callback when we receive a message from internal buffer to publish to waiting flask.
    :param body: Contains the message to be sent.
    :param message: Contains data about the message as well as headers
    """
    producer = Producer()

    body = body if isinstance(body, dict) else safe_json(body)
    rcv_headers = message.headers

    orc_socket = rcv_headers["source"]["transport"]["socket"]  # orch IP:port
    orc_id = rcv_headers["source"]["orchestratorID"]  # orchestrator ID
Esempio n. 17
0
import os
from sb_utils import FrozenDict, safe_cast

Config = FrozenDict(
    TLS_ENABLED=os.environ.get('MQTT_TLS_ENABLED', False),
    TLS_SELF_SIGNED=safe_cast(os.environ.get('MQTT_TLS_SELF_SIGNED', 0), int,
                              0),
    CAFILE=os.environ.get('MQTT_CAFILE', None),
    CLIENT_CERT=os.environ.get('MQTT_CLIENT_CERT', None),
    CLIENT_KEY=os.environ.get('MQTT_CLIENT_KEY', None),
    USERNAME=os.environ.get('MQTT_DEFAULT_USERNAME', None),
    PASSWORD=os.environ.get('MQTT_DEFAULT_PASSWORD', None),
    MQTT_PREFIX=os.environ.get('MQTT_PREFIX', ''),
    MQTT_HOST=os.environ.get('MQTT_HOST', 'queue'),
    MQTT_PORT=safe_cast(os.environ.get('MQTT_PORT', 1883), int, 1883),
    # TODO: find alternatives??
    TRANSPORT_TOPICS=[
        t.lower().strip()
        for t in os.environ.get("MQTT_TRANSPORT_TOPICS", "").split(",")
    ],
    TOPICS=[
        t.lower().strip() for t in os.environ.get("MQTT_TOPICS", "").split(",")
    ],
    # ETCD Options
    ETCD_HOST=os.environ.get('ETCD_HOST', 'etcd'),
    ETCD_PORT=safe_cast(os.environ.get('ETCD_PORT', 2379), int, 2379))
    def send_mqtt(body, message):
        """
        AMQP Callback when we receive a message from internal buffer to be publishedorc_server
        :param body: Contains the message to be sent.
        :param message: Contains data about the message as well as headers
        """
        headers = message.headers
        source = headers.get('source', {})
        destinations = headers.get("destination", [])

        # iterate through all devices within the list of destinations
        for device in destinations:
            # check that all necessary parameters exist for device
            key_diff = Callbacks.required_device_keys.difference(
                {*device.keys()})
            if len(key_diff) != 0:
                err_msg = f"Missing required header data to successfully transport message - {', '.join(key_diff)}"
                print(err_msg)
                return send_error_response(err_msg, headers)

            orc_id = source.get('orchestratorID', '')
            corr_id = source.get('correlationID', '')
            prefix = device.get('prefix', '')
            fmt = device.get('format', '')
            encoding = device.get("encoding", "json")
            ip, port = device.get("socket", "localhost:1883").split(":")
            broker_socket = f'{ip}:{port}'

            with Auth(device.get("auth", {})) as auth:
                # iterate through actuator profiles to send message to
                for actuator in device.get("profile", []):
                    topic = getTopic(fmt=fmt,
                                     prefix=f"{prefix}/" if prefix else '',
                                     device_id=device.get('deviceID', ''),
                                     profile=actuator)

                    payload = Message(
                        recipients=[f"{actuator}@{broker_socket}"],
                        origin=f"{orc_id}@{broker_socket}",
                        msg_type=MessageType.Request,
                        request_id=uuid.UUID(corr_id),
                        serialization=SerialFormats(encoding)
                        if encoding in SerialValues else SerialFormats.JSON,
                        content=json.loads(body)).pack()
                    print(f"Sending {ip}:{port} topic: {topic} -> {payload}")

                    try:
                        publish.single(
                            topic,
                            client_id=f"oif-{orc_id[:5]}",
                            payload=payload,
                            qos=1,
                            retain=False,
                            hostname=ip,
                            port=safe_cast(port, int, 1883),
                            keepalive=60,
                            will=None,
                            # Authentication
                            auth=dict(username=auth.username,
                                      password=auth.password or None)
                            if auth.username else None,
                            tls=dict(ca_certs=auth.caCert,
                                     certfile=auth.clientCert,
                                     keyfile=auth.clientKey) if auth.caCert
                            and auth.clientCert and auth.clientKey else None)
                        print(
                            f"Placed payload onto topic {topic} Payload Sent: {payload}"
                        )
                    except Exception as e:
                        print(
                            f"There was an error sending command to {ip}:{port} topic: {actuator} -> {e}"
                        )
                        send_error_response(e, headers)
    def send_mqtt(body, message):
        """
        AMQP Callback when we receive a message from internal buffer to be published
        :param body: Contains the message to be sent.
        :param message: Contains data about the message as well as headers
        """
        # check for certs if TLS is enabled
        if os.environ.get("MQTT_TLS_ENABLED",
                          False) and os.listdir("/opt/transport/MQTT/certs"):
            tls = dict(ca_certs=os.environ.get("MQTT_CAFILE", None),
                       certfile=os.environ.get("MQTT_CLIENT_CERT", None),
                       keyfile=os.environ.get("MQTT_CLIENT_KEY", None))
        else:
            tls = None

        # iterate through all devices within the list of destinations
        for device in message.headers.get("destination", []):
            # check that all necessary parameters exist for device
            key_diff = Callbacks.required_device_keys.difference(
                {*device.keys()})
            if len(key_diff) == 0:
                encoding = device.get("encoding", "json")
                ip, port = device.get("socket", "localhost:1883").split(":")

                # iterate through actuator profiles to send message to
                for actuator in device.get("profile", []):
                    payload = {
                        "header": format_header(message.headers, device,
                                                actuator),
                        "body": encode_msg(json.loads(body), encoding)
                    }
                    print(f"Sending {ip}:{port} - {payload}")

                    try:
                        publish.single(actuator,
                                       payload=json.dumps(payload),
                                       qos=1,
                                       hostname=ip,
                                       port=safe_cast(port, int, 1883),
                                       will={
                                           "topic": actuator,
                                           "payload": json.dumps(payload),
                                           "qos": 1
                                       },
                                       tls=tls)
                        print(
                            f"Placed payload onto topic {actuator} Payload Sent: {payload}"
                        )
                    except Exception as e:
                        print(
                            f"There was an error sending command to {ip}:{port} - {e}"
                        )
                        send_error_response(e, payload["header"])
                        return
                get_response(
                    ip, port,
                    message.headers.get("source",
                                        {}).get("orchestratorID", ""))
            else:
                err_msg = f"Missing required header data to successfully transport message - {', '.join(key_diff)}"
                send_error_response(err_msg, payload["header"])
Esempio n. 20
0
    def send_mqtt(body, message):
        """
        AMQP Callback when we receive a message from internal buffer to be published to MQTT Broker
        :param body: Contains the message to be sent.
        :param message: Contains data about the message as well as headers
        """
        # check for certs if TLS is enabled
        if os.environ.get("MQTT_TLS_ENABLED",
                          False) and os.listdir("/opt/transport/MQTT/certs"):
            tls = dict(ca_certs=os.environ.get("MQTT_CAFILE", None),
                       certfile=os.environ.get("MQTT_CLIENT_CERT", None),
                       keyfile=os.environ.get("MQTT_CLIENT_KEY", None))
        else:
            tls = None

        # build message for MQTT
        encoding = message.headers.get("encoding", "json")
        broker_socket = message.headers.get("socket", "localhost:1883")
        content_type = f"application/openc2-cmd+{encoding};version=1.0"
        source = f"{message.headers.get('profile', '')}@{broker_socket}"
        dest = f"{message.headers.get('orchestratorID', '')}@{broker_socket}"
        corr_id = message.headers.get("correlationID", "")

        payload = {
            "header": {
                "to": dest,
                "from": source,
                "correlationID": corr_id,
                "content_type": content_type
            },
            "body": body
        }

        # Transport is running on device side, send response to orchestrator
        key_diff = Callbacks.required_header_keys.difference(
            {*message.headers.keys()})
        if len(key_diff) == 0:
            ip, port = broker_socket.split(":")[0:2]
            topic = message.headers.get("orchestratorID") + "/response"
            try:
                publish.single(
                    topic,
                    payload=json.dumps(payload),
                    qos=1,
                    hostname=ip,
                    port=safe_cast(port, int, 1883),
                    will={
                        "topic": topic,
                        "payload": json.dumps(payload),
                        "qos": 1
                    },
                    tls=tls,
                )
                print(f"Sent: {payload}")
            except Exception as e:
                print(f"An error occurred -  {e}")
                pass
        else:
            print(
                f"Missing required header data to successfully transport message - {', '.join(key_diff)}"
            )
            print(message.headers)