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
idis not found in database, plugin stores it and marks it active. - If its
idalready exists in database, plugin skips loading and logs at info level.
- If its
- 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 anactiveflag).GET /models/:id— get a specific model by ID. 404 if not found.POST /models— create or update a model; optionalactivateflag.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 toid.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
typeis 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
_payloadcan reference a key inpayload_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): explicitnamespace | variable | endpoint._var_type(optional, compatibility): variable type name.
Node type inference:
- If
childrenexists: node is non-endpoint. - If
childrenis absent: node is endpoint. - For non-endpoint keys:
- key
{name}=> variable node - key
+=> variable wildcard node - any other key => namespace node
- key
Variable type resolution:
- For key
{name}:- use
_var_typeif provided - otherwise use inferred type name
name
- use
- For key
+:- matcher is
any(matches one segment)
- matcher is
Wildcard keys in tree:
+: match exactly one topic segment.#: match remaining topic segments (including zero remaining segments).
Full Example
{
"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 astopic_nomatch(exceptexempt_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_actionis set todisconnect, the client is disconnected on topic authorization failure (the setting isdisconnect, notdrop). - If
authorization.deny_actionisignore(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 inper_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 inper_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}/xbecomesfoo/+/x.
- Example:
- 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 byexempt_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_totalmessages_allowedmessages_droppedtopic_invalidnot_endpointpayload_invalid
Counter semantics:
record_allowedbumpsmessages_totalandmessages_allowedfor 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_nomatchis 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 Version | Plugin Version | Package |
|---|---|---|
| 6.3.0 | 0.1.4 | emqx_unsgov-0.1.4.tar.gz (sha256) |