def command_response(body, message): """ Process a command received from an actuator :param body: command body :param message: complete message (headers, meta, ...) :return: None """ log.info(msg=f'Message response received: {body}') headers = getattr(message, "headers", {}) actuator = None if headers.get('error', False): correlation_ID = headers['source'].get('correlationID', '') opts = { '_coap_id' if isHex(correlation_ID) else 'command_id': correlation_ID } command = get_or_none(SentHistory, **opts) log.error(msg=f'Message Failure: cmd - {command.command_id}, {body}') response = {'error': body} else: act_host, act_port = headers.get('socket', '').split(':')[0:2] correlation_ID = headers.get('correlationID', '') opts = { '_coap_id' if isHex(correlation_ID) else 'command_id': correlation_ID } command = get_or_none(SentHistory, **opts) profile = headers.get('profile', '') encode = headers.get('encode', 'json') response = decode_msg(body, encode) actuator = get_or_none( model=Actuator, profile__iexact=profile, device__transport__host__iexact=act_host, device__transport__port=safe_cast(act_port, int), device__transport__protocol=get_or_none(Protocol, name__iexact=headers.get( 'transport', ''))) if hasattr(actuator, '__iter__'): log.warn( msg= f'Multiple actuators match for command response - {command.command_id}' ) actuator = random.choice(actuator) try: cmd_rsp = ResponseHistory(command=command, actuator=actuator, response=response) cmd_rsp.save() except Exception as e: log.error(msg=f'Message response failed to save: {e}')
def check_command_id(sender, instance=None, **kwargs): """ Validate the command id given is a UUID :param sender: sender instance - SentHistory :param instance: SENDER instance :param kwargs: key/value args :return: None """ if instance.command_id is None: log.info( msg=f"Command submitted without command id, command id generated") instance.command_id = uuid.uuid4() instance.command.update({"id": str(instance.command_id)}) else: try: val = uuid.UUID(str(instance.command_id), version=4) except ValueError: log.info(msg=f"Invalid command id received: {instance.command_id}") raise ValueError("Invalid command id") tmp = get_or_none(sender, command_id=val) if val is None and tmp is not None: log.info( msg=f"Duplicate command id received: {instance.command_id}") raise ValueError("command id has been used")
def action_send(usr=None, cmd={}, actuator="", channel={}): """ Process a command prior to sending it to the specified actuator(s)/profile :param usr: user sending command :param cmd: OpenC2 command :param actuator: actuator/profile receiving command :param channel: serialization & protocol to send the command :return: response dict """ err = validate_usr(usr) if err: return err err = validate_cmd(usr, cmd) if err: return err actuators = None err = validate_actuator(usr, actuator) if err: if isinstance(err, (Actuator, list)): actuators = err else: return err protocol, serialization = validate_channel(actuators, channel) # Store command in db cmd_id = cmd.get("id", uuid.uuid4()) if get_or_none(SentHistory, command_id=cmd_id): return dict(command_id=["This ID is used by another command."]), 400 else: com = SentHistory(command_id=cmd_id, user=usr, command=cmd) try: com.save() except ValueError as e: return dict(detail="command error", response=str(e)), 400 orc_ip = global_preferences.get("orchestrator__host", "127.0.0.1") orc_id = global_preferences.get("orchestrator__id", "") # Process Actuators that should receive command processed_acts = set() # Process Protocols protocols = [protocol] if protocol else Protocol.objects.all() for proto in protocols: proto_acts = [ a for a in actuators if a.device.transport.filter(protocol__name=proto.name).exists() ] proto_acts = list( filter(lambda a: a.id not in processed_acts, proto_acts)) processed_acts.update({act.id for act in proto_acts}) if len(proto_acts) >= 1: if proto.name.lower() == "coap" and com.coap_id is b"": corr_id = com.gen_coap_id() com.save() else: corr_id = str(com.command_id) header = dict(source=dict( orchestratorID=orc_id, transport=dict(type=proto.name, socket=f"{orc_ip}:{proto.port}"), correlationID=corr_id, date=f"{com.received_on:%a, %d %b %Y %X %Z}"), destination=[]) for act in proto_acts: com.actuators.add(act) trans = act.device.transport.filter( protocol__name=proto.name).first() encoding = (serialization if serialization else trans.serialization.first()).name.lower() dev = list( filter( lambda d: d["deviceID"] == str(act.device.device_id), header["destination"])) profile = str(act.profile).lower() if len(dev) == 1: idx = header["destination"].index(dev[0]) header["destination"][idx]["profile"].append(profile) else: dest = dict(deviceID=str(act.device.device_id), socket=f"{trans.host}:{trans.port}", profile=[profile], encoding=encoding) if trans.protocol.pub_sub: print("PUB SUB") header["destination"].append(dest) # Send command to transport log.info( usr=usr, msg= f"Send command {com.command_id}/{com.coap_id.hex()} to buffer") settings.MESSAGE_QUEUE.send(msg=json.dumps(cmd), headers=header, routing_key=proto.name.lower().replace( " ", "_")) wait = safe_cast(global_preferences.get("command__wait", 1), int, 1) rsp = None for _ in range(wait): rsp = get_or_none(ResponseHistory, command=com) if rsp: break else: time.sleep(1) rsp = [r.response for r in rsp] if hasattr(rsp, "__iter__") else ( [rsp.response] if hasattr(rsp, "response") else None) return dict(detail=f"command {'received' if rsp is None else 'processed'}", response=rsp if rsp else "pending", command_id=com.command_id, command=com.command, wait=wait), 200
def action_send(usr: User, cmd: dict, actuator: str, channel: dict): """ Process a command prior to sending it to the specified actuator(s)/profile :param usr: user sending command :param cmd: OpenC2 command :param actuator: actuator/profile receiving command :param channel: serialization & protocol to send the command :return: response Tuple(dict, int) """ val = Validator(usr, cmd, actuator, channel) acts, protocol, serialization = val.validate() (actuators, fmt) = acts # Store command in db if "id" in cmd: cmd_id = cmd.get("id", uuid.uuid4()) try: cmd_id = uuid.UUID(cmd_id, version=4) except ValueError: cmd_id = uuid.uuid4() cmd["id"] = str(cmd_id) else: cmd_id = uuid.uuid4() if get_or_none(SentHistory, command_id=cmd_id): return dict( command_id=[ "This ID is used by another command." ] ), 400 com = SentHistory(command_id=cmd_id, user=usr, command=cmd) try: com.save() except ValueError as e: return dict( detail="command error", response=str(e) ), 400 # Process Actuators that should receive command processed_acts = set() # Process Protocols for proto in [protocol] if protocol else Protocol.objects.all(): proto_acts = [a for a in actuators if a.device.transport.filter(protocol__name=proto.name).exists()] proto_acts = list(filter(lambda a: a.id not in processed_acts, proto_acts)) processed_acts.update({act.id for act in proto_acts}) if len(proto_acts) >= 1: if proto.name.lower() == "coap" and com.coap_id == b"": com.gen_coap_id() com.save() # Send command to transport log.info(usr=usr, msg=f"Send command {com.command_id}/{com.coap_id.hex()} to buffer") settings.MESSAGE_QUEUE.send( msg=json.dumps(cmd), headers=get_headers(proto, com, proto_acts, serialization, fmt), routing_key=proto.name.lower().replace(" ", "_") ) wait = safe_cast(global_preferences.get("command__wait", 1), int, 1) rsp = None for _ in range(wait): rsp = get_or_none(ResponseHistory, command=com) if rsp: break time.sleep(1) rsp = [r.response for r in rsp] if hasattr(rsp, "__iter__") else ([rsp.response] if hasattr(rsp, "response") else None) return dict( detail=f"command {'received' if rsp is None else 'processed'}", response=rsp if rsp else "pending", command_id=com.command_id, command=com.command, wait=wait ), 200