IoT protocol security agent for Zentinel reverse proxy. Provides topic-based ACLs, client authentication, payload inspection, rate limiting, and QoS enforcement for MQTT traffic.
Transport: This agent processes MQTT packets carried over WebSocket frames. Native MQTT (TCP port 1883/8883) is not supported — MQTT clients must connect via WebSocket.
- Topic-Based ACLs — Allow/deny publish/subscribe per topic with
+and#wildcards - Client Authentication — Username/password (bcrypt), JWT tokens
- Payload Inspection — SQLi, command injection, XSS detection + JSON schema validation
- Rate Limiting — Per-client and per-topic message rate limits (token bucket)
- QoS Enforcement — Maximum QoS levels with automatic downgrade
- Retained Message Control — Allow/deny retained flag per topic
# Install just this agent
zentinel bundle install mqtt-gateway
# Or install all bundled agents
zentinel bundle installThe bundle command downloads the correct binary for your platform and places it in the standard location. See the bundle documentation for details.
zentinel-agent-mqtt-gateway is not published on crates.io, so cargo install zentinel-agent-mqtt-gateway does not
work. Install straight from the repository instead:
cargo install --git https://github.com/zentinelproxy/zentinel-agent-mqtt-gatewayThis builds and installs the zentinel-mqtt-gateway-agent binary.
Each release ships binaries
for linux-x86_64, linux-aarch64, and darwin-aarch64:
VERSION=0.4.0
PLATFORM=linux-x86_64 # or linux-aarch64, darwin-aarch64
curl -fsSL -o zentinel-mqtt-gateway-agent.tar.gz \
"https://github.com/zentinelproxy/zentinel-agent-mqtt-gateway/releases/download/v${VERSION}/zentinel-mqtt-gateway-agent-${VERSION}-${PLATFORM}.tar.gz"
tar -xzf zentinel-mqtt-gateway-agent.tar.gz
sudo install -m 0755 zentinel-mqtt-gateway-agent /usr/local/bin/git clone https://github.com/zentinelproxy/zentinel-agent-mqtt-gateway
cd zentinel-agent-mqtt-gateway
cargo build --release# Run with defaults
zentinel-mqtt-gateway-agent --socket /tmp/zentinel-mqtt.sock
# With configuration file
zentinel-mqtt-gateway-agent \
--socket /tmp/zentinel-mqtt.sock \
--config /etc/zentinel/mqtt-gateway.json
# With JSON logging
zentinel-mqtt-gateway-agent \
--socket /tmp/zentinel-mqtt.sock \
--json-logs \
--log-level debug| Option | Env Var | Description | Default |
|---|---|---|---|
--socket |
AGENT_SOCKET |
Unix socket path | /tmp/zentinel-mqtt-gateway-agent.sock |
--grpc-address |
AGENT_GRPC_ADDRESS |
gRPC listen address | - |
--config |
MQTT_CONFIG |
Configuration file path | - |
--log-level |
MQTT_LOG_LEVEL |
Log level (trace, debug, info, warn, error) | info |
--json-logs |
- | Enable JSON log format | false |
{
"auth": {
"enabled": true,
"allow-anonymous": false,
"providers": [
{
"type": "file",
"path": "/etc/zentinel/mqtt-users.json"
}
]
},
"acl": {
"enabled": true,
"default-action": "deny",
"rules": [
{
"name": "sensors-publish",
"match": { "username-regex": "^sensor-" },
"topics": ["sensors/+/data"],
"actions": ["publish"],
"decision": "allow",
"max-qos": 1,
"priority": 10
},
{
"name": "operators-subscribe",
"match": { "groups": ["operators"] },
"topics": ["sensors/#"],
"actions": ["subscribe"],
"decision": "allow",
"priority": 10
},
{
"name": "block-internal",
"topics": ["$SYS/#", "internal/#"],
"decision": "deny",
"priority": 100
}
]
},
"inspection": {
"enabled": true,
"max-payload-size": 262144,
"patterns": {
"sqli": true,
"command-injection": true
}
},
"rate-limit": {
"enabled": true,
"per-client": {
"messages-per-second": 100,
"bytes-per-second": 1048576,
"burst": 50
}
},
"qos": {
"enabled": true,
"max-qos": 1,
"downgrade": true
},
"retained": {
"enabled": true,
"allow-retained": false,
"allowed-topics": ["config/#"]
}
}Rules are evaluated by priority (highest value first). The first matching rule wins. If no rule matches, default-action applies. This is a strict first-match model — rules do not accumulate.
Standard MQTT wildcards:
+— Matches exactly one level (sensors/+/datamatchessensors/temp/data)#— Matches zero or more levels (sensors/#matchessensors/temp/living/zone1)
agents {
agent "mqtt-gateway" {
type "custom"
unix-socket "/tmp/zentinel-mqtt.sock"
events "websocket_frame"
timeout-ms 100
failure-mode "closed"
}
}
routes {
route "mqtt" {
matches { path-prefix "/mqtt" }
websocket #true {
max-frame-size 65536
}
agents "mqtt-gateway"
upstream "mqtt-broker"
}
}| Packet | Checks Applied |
|---|---|
| CONNECT | Authentication, client ID validation |
| PUBLISH | ACL, rate limit, QoS, retained, payload inspection |
| SUBSCRIBE | ACL per topic filter |
| UNSUBSCRIBE | ACL (optional) |
| PINGREQ/PINGRESP | Allowed (keep-alive) |
| DISCONNECT | Cleanup connection state |
| Decision | WebSocket Action | Use Case |
|---|---|---|
| Allow | Forward frame | Request passes all checks |
| Drop | Drop frame silently | Policy violation, rate limit |
| Close | Close connection (code 1008) | Auth failure, protocol error |
# Run with debug logging
RUST_LOG=debug cargo run -- --socket /tmp/test.sock
# Run tests
cargo test
# Build release binary
cargo build --releaseApache-2.0