Compare commits

..

5 Commits

Author SHA1 Message Date
Brent Perteet
e552ae4e5c fix(S2-a): mount broker auth files outside source tree 2026-08-20 15:53:20 -05:00
Brent Perteet
8aea87c3cd fix(S2-a): keep broker credential database outside Git 2026-08-20 15:52:14 -05:00
Brent Perteet
8f638bfa4f fix(S2-a): keep plaintext MQTT inside compose network 2026-08-20 15:44:38 -05:00
Brent Perteet
9ffe021354 fix(S2-a): expose scoped app MQTT through WSS 2026-08-20 15:43:42 -05:00
Brent Perteet
b66ae2cc47 fix(S2-a): load public MQTT TLS cert from broker volume 2026-08-20 15:27:03 -05:00
4 changed files with 49 additions and 19 deletions

View File

@@ -12,12 +12,27 @@ exposes three listeners, each with a different trust model:
| Port | Protocol | Auth | Who it's for | | Port | Protocol | Auth | Who it's for |
|------|----------|------|---------------| |------|----------|------|---------------|
| `1883` | MQTT (plaintext) | username/password | internal services (e.g. the Laravel subscriber, Python publisher) | | `1883` | MQTT (plaintext, Compose network only) | username/password | internal backend service; not host-published |
| `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
`mosquitto/certs/public-privkey.pem`, copied from the host's Let's Encrypt certificate during
deployment with owner `1883:1883` and mode `0600`. These files are deployment secrets/artifacts
and are excluded from Git.
The live password database is likewise outside Git at
`/home/ubuntu/.config/ul-platform/mosquitto.passwd`, bind-mounted read-only as
`/run/secrets/mosquitto_passwd`. The deployed ACL is copied to
`/home/ubuntu/.config/ul-platform/mosquitto.acl` and bind-mounted beside it. Both files are owned
by broker uid/gid 1883 with mode 0600. Provisioning updates the host password file and restarts Mosquitto; the
tracked `mosquitto/config/passwd` is only a legacy/bootstrap sample and must not receive new
organization credentials.
Device authentication happens on **port 8883**. A device presents a client Device authentication happens on **port 8883**. A device presents a client
certificate signed by the app's own Certificate Authority (CA); Mosquitto certificate signed by the app's own Certificate Authority (CA); Mosquitto

View File

@@ -11,14 +11,13 @@ services:
mosquitto: mosquitto:
image: eclipse-mosquitto:2 image: eclipse-mosquitto:2
ports: ports:
- "1883:1883"
- "8883:8883" - "8883:8883"
- "8884:8884" - "8884:8884"
- "127.0.0.1:9001:9001"
volumes: volumes:
- ./mosquitto:/mosquitto - ./mosquitto:/mosquitto
# Publicly trusted server identity for app MQTTS. The device listener still - /home/ubuntu/.config/ul-platform/mosquitto.passwd:/run/secrets/mosquitto_passwd:ro
# validates client certificates against /mosquitto/certs/ca.crt. - /home/ubuntu/.config/ul-platform/mosquitto.acl:/run/secrets/mosquitto_acl:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
backend: backend:
build: build:

View File

@@ -2,8 +2,16 @@ per_listener_settings true
# Plain MQTT — internal services and clients authenticate with username/password on port 1883 # Plain MQTT — internal services and clients authenticate with username/password on port 1883
listener 1883 0.0.0.0 listener 1883 0.0.0.0
password_file /mosquitto/config/passwd password_file /run/secrets/mosquitto_passwd
acl_file /mosquitto/config/devices.acl acl_file /run/secrets/mosquitto_acl
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 /run/secrets/mosquitto_passwd
acl_file /run/secrets/mosquitto_acl
allow_anonymous false allow_anonymous false
# TLS MQTT — devices authenticate with client certificates (port 8883) # TLS MQTT — devices authenticate with client certificates (port 8883)
@@ -14,19 +22,19 @@ allow_anonymous false
# since there's no config/cert hot-reload. # since there's no config/cert hot-reload.
listener 8883 0.0.0.0 listener 8883 0.0.0.0
cafile /mosquitto/certs/ca.crt cafile /mosquitto/certs/ca.crt
certfile /etc/letsencrypt/live/dev.hub.umagul.net/fullchain.pem certfile /mosquitto/certs/public-fullchain.pem
keyfile /etc/letsencrypt/live/dev.hub.umagul.net/privkey.pem keyfile /mosquitto/certs/public-privkey.pem
require_certificate true require_certificate true
use_identity_as_username true use_identity_as_username true
allow_anonymous false allow_anonymous false
acl_file /mosquitto/config/devices.acl acl_file /run/secrets/mosquitto_acl
# TLS MQTT — app/admin username+password access (port 8884). App usernames are orgIds; # TLS MQTT — app/admin username+password access (port 8884). App usernames are orgIds;
# devices.acl confines them to ul/{orgId}/app/... . No anonymous listener is exposed. # devices.acl confines them to ul/{orgId}/app/... . No anonymous listener is exposed.
listener 8884 0.0.0.0 listener 8884 0.0.0.0
certfile /etc/letsencrypt/live/dev.hub.umagul.net/fullchain.pem certfile /mosquitto/certs/public-fullchain.pem
keyfile /etc/letsencrypt/live/dev.hub.umagul.net/privkey.pem keyfile /mosquitto/certs/public-privkey.pem
require_certificate false require_certificate false
password_file /mosquitto/config/passwd password_file /run/secrets/mosquitto_passwd
allow_anonymous false allow_anonymous false
acl_file /mosquitto/config/devices.acl acl_file /run/secrets/mosquitto_acl

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 / {