إنتقل إلى المحتوى الرئيسي

CPS MQTT ingest contract

AuroraSOC CPS firmware nodes (environmental sentinels, the ESP32-S3 environmental and presence node, the STM32F401 RFID access bench node, and the STM32F401 door node with a real PN532 reader and solenoid lock) talk to the backend over MQTT. This page documents the wire contract and how the backend ingests it, so firmware and backend stay in agreement.

The message shapes are produced by the no_std aurora-firmware-contracts crate on the device and parsed by MQTTEdgeConsumer (aurorasoc/events/mqtt_consumer.py) on the backend. Both sides treat this contract as the single source of truth.

Transport

  • Broker: Mosquitto, mutual TLS (TLS 1.3), client certificate per device.
  • The backend subscribes as a single consumer, supervised by run_cps_mqtt_consumer (aurorasoc/tools/cps/mqtt_bridge.py), which reconnects with backoff and shuts down cleanly with the API.
  • Ingest is opt-in. It runs only when MQTT_ENABLED=true (settings.mqtt.enabled); broker host, port, and certificate paths come from the other MQTT_* settings.

Topics

TopicDirectionPurpose
aurora/sensors/<device_id>/statusdevice to backendHeartbeat plus firmware identity
aurora/sensors/<device_id>/telemetrydevice to backendOne numeric reading
aurora/sensors/<device_id>/alertsdevice to backendDevice-raised alert
aurora/access/<device_id>/badge_eventdevice to backendRFID badge read
aurora/access/<device_id>/commandbackend to deviceLock verb (unlock, lock, lockdown, lockdown_clear, keepalive)
aurora/attestation/<device_id>/responsedevice to backendAttestation reply to a challenge
aurora/attestation/<device_id>/requestbackend to deviceAttestation challenge (nonce)

<device_id> is validated against ^[A-Za-z0-9_-]{1,64}$; any other shape is rejected before processing.

Payloads

All payloads are compact JSON objects.

Status:

{"device_type":"environmental_presence","board_family":"esp32s3","firmware_stack":"rust_embassy","firmware_version":"0.1.0","device_id":"esp32s3-env-01"}

A node that performs measured boot adds a firmware_hash (SHA-256 of its flash image, lowercase hex), which the backend records as the trusted baseline (see ADR 052):

{"device_type":"access_control_node","board_family":"stm32f401","firmware_stack":"rust_embassy","firmware_version":"0.1.0","firmware_hash":"<64 hex>","device_id":"stm32f401-door-01"}

Telemetry:

{"device_id":"esp32s3-env-01","metric":"temperature_c","value":-4.25,"unit":"C"}

Alert:

{"device_id":"esp32s3-env-01","type":"sensor_unhealthy","severity":"critical","event_details":"AHT21B unhealthy: repeated sensor timeout"}

Badge event:

{"device_id":"stm32f401-door-03","uid":"0455AF21","event":"badge_read"}

Backend behaviour

  • status: an unknown device is auto-registered in cps_devices (so a booting node appears in GET /api/v1/cps/devices and the CPS console without manual registration); a known device has its last_seen refreshed. A device that reports a measured firmware_hash is marked trusted at that baseline (trust-on-first-use, ADR 052); a hash that later changes flips it to drift_detected and raises a tamper alert.
  • telemetry: the reading is range-checked against the per-metric thresholds in settings.mqtt.telemetry_thresholds; an out-of-range value raises a telemetry_anomaly CPS alert. Every reading is also written to the audit stream for time-series consumers.
  • alerts: forwarded verbatim to the CPS alert stream for the CPS Security agent.
  • badge_event: the reader is registered or refreshed, the UID is authorized against the access policy, and the read is written to the audit trail for physical-cyber correlation. On a grant the backend dispatches an unlock command on aurora/access/<device_id>/command, so the door node never opens on its own; the device is the actuator, the backend is the authority. A denial raises an access_denied CPS alert and the lock stays shut. Operator commands from the console use the same command topic.
  • attestation response: the reported firmware hash is compared to the expected hash and the certificate validity is checked. The result is stored in attestation_results and the device status is set to one of the attestation_status_enum members (verified, failed, expired); a failure raises a critical CPS alert and bumps the risk score.

Security notes

  • Device identity is pinned by the mTLS client certificate; the topic device_id is treated as an untrusted label and validated by pattern.
  • Attestation statuses are constrained to the database enum, so a malformed or hostile response cannot smuggle an unexpected status value through.
  • Routine badge reads go to the audit trail, not the alert stream, to keep alert volume meaningful; only denials and anomalies are alerts.