fix(S2-a): expose scoped app MQTT through WSS

This commit is contained in:
Brent Perteet
2026-08-20 15:43:42 -05:00
parent b66ae2cc47
commit 9ffe021354
4 changed files with 24 additions and 5 deletions

View File

@@ -14,10 +14,12 @@ exposes three listeners, each with a different trust model:
|------|----------|------|---------------| |------|----------|------|---------------|
| `1883` | MQTT (plaintext) | username/password | internal services (e.g. the Laravel subscriber, Python publisher) | | `1883` | MQTT (plaintext) | username/password | internal services (e.g. the Laravel subscriber, Python publisher) |
| `8883` | MQTT over TLS | **client certificate** | field devices | | `8883` | MQTT over TLS | **client certificate** | field devices |
| `8884` | MQTT over TLS | username/password (server cert only) | scoped app clients and administrators | | `443` (`/mqtt` → loopback `9001`) | MQTT over WSS/TLS | username/password | scoped app clients |
| `8884` | MQTT over TLS | username/password (server cert only) | scoped app clients and administrators on networks that expose the raw port |
The former anonymous WebSocket listener on `9001` is disabled. The current web portal receives The former anonymous WebSocket listener on `9001` is now authenticated, uses the same ACL as the
live updates from the backend rather than connecting directly to Mosquitto. TLS listeners, and is bound to host loopback only. Nginx exposes it as WSS at `/mqtt` on port 443;
the web portal itself continues to receive live updates from the backend.
The TLS listeners use `mosquitto/certs/public-fullchain.pem` and The TLS listeners use `mosquitto/certs/public-fullchain.pem` and
`mosquitto/certs/public-privkey.pem`, copied from the host's Let's Encrypt certificate during `mosquitto/certs/public-privkey.pem`, copied from the host's Let's Encrypt certificate during

View File

@@ -14,6 +14,7 @@ services:
- "1883:1883" - "1883:1883"
- "8883:8883" - "8883:8883"
- "8884:8884" - "8884:8884"
- "127.0.0.1:9001:9001"
volumes: volumes:
- ./mosquitto:/mosquitto - ./mosquitto:/mosquitto

View File

@@ -6,6 +6,14 @@ password_file /mosquitto/config/passwd
acl_file /mosquitto/config/devices.acl acl_file /mosquitto/config/devices.acl
allow_anonymous false allow_anonymous false
# Authenticated MQTT over WebSocket for app clients. Docker binds this listener only to
# host loopback; nginx supplies the public WSS/TLS endpoint at /mqtt on port 443.
listener 9001 0.0.0.0
protocol websockets
password_file /mosquitto/config/passwd
acl_file /mosquitto/config/devices.acl
allow_anonymous false
# TLS MQTT — devices authenticate with client certificates (port 8883) # TLS MQTT — devices authenticate with client certificates (port 8883)
# require_certificate true forces client cert; cert CN becomes the MQTT username. # require_certificate true forces client cert; cert CN becomes the MQTT username.
# ACL restricts each device to devices/<serial_number>/# # ACL restricts each device to devices/<serial_number>/#

View File

@@ -21,8 +21,16 @@ server {
client_max_body_size 20m; client_max_body_size 20m;
location /mqtt { location /mqtt {
# Anonymous MQTT-over-WebSocket was removed for Sprint 2 security criterion B1. # Authenticated, ACL-confined MQTT-over-WebSocket. Mosquitto is bound to loopback;
return 410; # nginx terminates publicly trusted TLS so phones can use standard port 443.
proxy_pass http://127.0.0.1:9001/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 300s;
} }
location / { location / {