Skip to content

UNS Governance

This plugin enforces Unified Namespace topic structure at ACL check time and can validate the payloads of messages published to topics governed by UNS Governance.

Plugin API

Base path: /api/v5/plugin_api/emqx_unsgov

Bootstrap Models

  • On startup, UNS Governance scans priv/bootstrap_models/*.json.
  • For each bootstrap model:
    • If its id is not found in database, plugin stores it and marks it active.
    • If its id already exists in database, plugin skips loading and logs at info level.
  • Bundled default bootstrap model: priv/bootstrap_models/model-v1.json.

NOTE: Bootstrap models are loaded into the database when the first plugin starts up in the cluster, later plugin or node restarts will not cause a reload. Use API to update the models store in DB.

JSON Data Endpoints

  • GET /status — plugin status (on_mismatch, exempt_topics).
  • GET /stats — cluster-aggregated counters and recent drops.
  • GET /models — list all stored models (each entry includes an active flag).
  • GET /models/:id — get a specific model by ID. 404 if not found.
  • POST /models — create or update a model; optional activate flag.
  • POST /models/:id/activate — activate a stored model.
  • POST /models/:id/deactivate — deactivate a model.
  • DELETE /models/:id — delete a stored model.
  • POST /validate/topic — validate a topic against active models.

Other Endpoints

  • GET /ui — interactive model editor UI.
  • GET /metrics — Prometheus text exposition format.

UNS Model Schema

This section defines the complete model JSON format accepted by UNS Governance.

Top-Level Keys

  • id (required, string): model ID. Must match ^[A-Za-z0-9_-]+$. Controls evaluation order (alphabetical by ID).
  • name (optional, string): model display name. Defaults to id.
  • variable_types (optional, object): reusable variable constraints.
  • tree (required, object): topic tree definition.
  • payload_types (optional, object): reusable payload schemas.

variable_types

Map of variable type name to constraint object.

Supported forms:

  • String regex matcher:
    • {"type":"string","pattern":"^...$"}
  • Enum matcher:
    • {"type":"enum","values":["A","B","C"]}

If a variable type is missing or invalid, matcher falls back to permissive any.

payload_types

Map of payload schema name to schema object.

Validation uses JSON Schema, with one compatibility patch:

  • If top-level type is omitted, UNS Governance patches it to "object".
  • Top-level payload schema must be object-root. Primitive roots are rejected.

This allows both:

  • Full self-contained object JSON Schema.
  • Existing shorthand object schema (for example only required/properties).

Endpoint payload binding:

  • Endpoint _payload can reference a key in payload_types, or "any" to skip payload validation.

tree

tree is an object where each key is a root topic segment and each value is a node object.

Node object keys:

  • children (optional, object): child segment map.
  • _payload (optional, string): payload type name for endpoint node, default "any".
  • _type (optional, compatibility): explicit namespace | variable | endpoint.
  • _var_type (optional, compatibility): variable type name.

Node type inference:

  • If children exists: node is non-endpoint.
  • If children is absent: node is endpoint.
  • For non-endpoint keys:
    • key {name} => variable node
    • key + => variable wildcard node
    • any other key => namespace node

Variable type resolution:

  • For key {name}:
    • use _var_type if provided
    • otherwise use inferred type name name
  • For key +:
    • matcher is any (matches one segment)

Wildcard keys in tree:

  • +: match exactly one topic segment.
  • #: match remaining topic segments (including zero remaining segments).

Full Example

json
{
  "id": "model-v1",
  "name": "UNS Model V1",
  "variable_types": {
    "site_id": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,31}$" },
    "line_id": { "type": "string", "pattern": "^Line[0-9]{1,4}$" },
    "mode": { "type": "enum", "values": ["auto", "manual"] }
  },
  "payload_types": {
    "line_control": {
      "type": "object",
      "required": ["Status", "Mode"],
      "properties": {
        "Status": { "type": "string", "enum": ["running", "stopped"] },
        "Mode": { "type": "string", "enum": ["auto", "manual"] }
      },
      "additionalProperties": false
    }
  },
  "tree": {
    "default": {
      "children": {
        "{site_id}": {
          "children": {
            "Lines": {
              "children": {
                "{line_id}": {
                  "children": {
                    "LineControl": { "_payload": "line_control" }
                  }
                }
              }
            },
            "stream": {
              "children": {
                "#": { "_payload": "any" }
              }
            }
          }
        }
      }
    }
  }
}

Enforcement Behavior

UNS Governance validates both topic structure and (optionally) payload schema.

  • Topic violations (topic_nomatch, topic_invalid, not_endpoint):

    • topic_nomatch: no active model topic filter matched the topic. (No model-specific validation is run.) If there are no active models and UNS Governance is enabled, topics are fail-closed as topic_nomatch (except exempt_topics).
    • topic_invalid: selected model filter matched, but topic failed selected model structure/segment constraints.
    • not_endpoint: selected model matched topic path, but target node is not an endpoint.
    • QoS 0: message is ignored.
    • QoS 1/2: publish is rejected and a protocol reason code is returned to client (Not Authorized).
    • If EMQX authorization.deny_action is set to disconnect, the client is disconnected on topic authorization failure (the setting is disconnect, not drop).
    • If authorization.deny_action is ignore (default), no disconnect is done; QoS 1/2 still receive reject reason codes.
    • Observable counters: messages_dropped, topic_nomatch, topic_invalid, not_endpoint, and per-model counters in per_model.
  • Payload violations (payload_invalid):

    • When a message is published to a topic governed by UNS Governance and that topic has a payload schema, payload validation runs during publish processing for every matching message, including publishes that hit the authorization cache and non-client publishes such as rule-engine republish or bridge ingress.
    • Message is dropped by UNS Governance in publish processing.
    • No auth reject/disconnect is required for this path.
    • Observable counters: messages_dropped, payload_invalid, and per-model counters in per_model.

Topic-Filter Pre-Check

When multiple models are active, UNS Governance pre-screens models before full validation:

  • Each model is compiled into topic-filter patterns derived from its tree paths.
  • Variable segments are converted to single-level wildcards (+).
    • Example: foo/{bar}/x becomes foo/+/x.
  • Active models are ordered by model ID.
  • UNS Governance selects the first model (by ID order) whose compiled filter matches the publish topic.
  • Pre-check uses direct topic/filter matching only; it does not implicitly expand a publish topic prefix (for example appending /#).
  • Only that selected model is fully validated; UNS Governance does not continue to the next model.
  • Models that fail this pre-check are skipped and do not contribute per-model drop counters.

This avoids unrelated active models inflating counters and keeps model behavior deterministic. It also means overlapping topic trees across models should be avoided.

Counters

GET /stats returns cluster-aggregated counters.

Top-level counters:

  • messages_total: total handled messages (messages_allowed + messages_dropped); exempt traffic is included.
  • messages_allowed: allowed messages plus exempt messages.
  • messages_dropped: dropped/rejected messages due to UNS validation failures.
  • topic_nomatch: dropped/rejected because no active model filter matched.
  • topic_invalid: dropped/rejected due to selected-model topic mismatch.
  • not_endpoint: dropped/rejected because topic matched a non-endpoint node.
  • payload_invalid: dropped due to payload schema mismatch.
  • exempt: messages skipped by exempt_topics.
  • per_model: per-model breakdown map keyed by model ID.
  • recent_drops: recent drop events (topic, error_type, error_detail, timestamp_ms).

Per-model counters (per_model.<model_id>):

  • messages_total
  • messages_allowed
  • messages_dropped
  • topic_invalid
  • not_endpoint
  • payload_invalid

Counter semantics:

  • record_allowed bumps messages_total and messages_allowed for the matched model.
  • Topic/payload drops bump messages_total, messages_dropped, and the specific reason counter for the selected model.
  • If no model passes the topic-filter pre-check, topic_nomatch is bumped globally and no per-model drop counter is bumped. This includes the case where the active model set is empty.

Download

Tarballs for each EMQX release:

EMQX VersionPlugin VersionPackage
6.3.00.1.4emqx_unsgov-0.1.4.tar.gz (sha256)