Give the POS its own broker accounts
Two scoped users, pos_terminal and pos_ingest, with an ACL that keeps a till to its own topics: it can publish its bills, registrations and heartbeats, read its own acks, and nothing else. It cannot reach nearle/riders/# or doormile/#, and cannot forge an ack — only the ingest writes those. admin is deliberately left unrestricted. Its credentials are compiled into the rider app, so narrowing it would cut off the live fleet without warning; that change needs someone to confirm nothing else uses it first. Because admin's entry grants everything, applying the ACL changed nothing for existing traffic — verified by watching riders 852 and 1114 keep publishing battery, speed and periodic logs throughout. The scoping was verified by publishing as pos_terminal to four topics and observing which arrived: the order did, the rider topic, the doormile topic and its own ack topic did not. Config and password file were backed up first; the rollback is one cp and a container restart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -239,75 +239,56 @@ state.
|
||||
as zero: a board showing every till at 0% battery is worse than one showing
|
||||
nothing.
|
||||
|
||||
## Broker hardening — before a hundred tills join
|
||||
## Broker accounts
|
||||
|
||||
Measured on the live broker, not assumed. None of this is caused by the POS
|
||||
work; all of it gets worse the moment bills start flowing.
|
||||
Applied 2026-08-03 on `66.116.225.226`. Two scoped accounts now exist alongside
|
||||
`admin`, with an ACL at `/mosquitto/config/acl` referenced from
|
||||
`mosquitto.conf`.
|
||||
|
||||
**There is no ACL file.** `allow_anonymous false` is set and auth is by password
|
||||
file, but with no `acl_file` every authenticated user is unrestricted on every
|
||||
topic. The rider app ships `admin` credentials **hardcoded in its APK**, so
|
||||
anyone who decompiles it today has full publish and subscribe over `nearle/#`
|
||||
*and* `doormile/#` — a second project's traffic. Adding POS puts every shop's
|
||||
takings behind the same credential.
|
||||
| User | May publish | May subscribe |
|
||||
|---|---|---|
|
||||
| `pos_terminal` | `nearle/pos/+/+/{order,customer,status,health}` | `nearle/pos/+/+/{ack,command}`, `nearle/pos/+/catalogue` |
|
||||
| `pos_ingest` | `nearle/pos/+/+/{ack,command}`, `nearle/pos/+/catalogue` | `nearle/pos/+/+/{order,customer,health,status}` |
|
||||
| `admin` | everything — **deliberately unchanged** | everything |
|
||||
|
||||
A scoped account is two commands and a container restart:
|
||||
A till therefore cannot publish to `nearle/riders/#` or `doormile/#`, and cannot
|
||||
write its own ack topic — only the ingest may do that. Verified by publishing as
|
||||
`pos_terminal` to all four and watching which arrived: the order did, the other
|
||||
three did not.
|
||||
|
||||
```bash
|
||||
# A user for the tills, and one for this backend.
|
||||
mosquitto_passwd -b /mosquitto/config/passwd pos_terminal '<strong-unique-pw>'
|
||||
mosquitto_passwd -b /mosquitto/config/passwd pos_ingest '<different-pw>'
|
||||
```
|
||||
**`admin` was left unrestricted on purpose.** Its credentials are compiled into
|
||||
the rider app, so narrowing it here would cut off the live rider fleet without
|
||||
warning. The right next step is:
|
||||
|
||||
```conf
|
||||
# /mosquitto/config/acl — then add `acl_file /mosquitto/config/acl` to mosquitto.conf
|
||||
|
||||
# Tills: publish their own traffic, read only their own acks and their shop's
|
||||
# catalogue. The %c substitution binds a client to its own topics, so one till
|
||||
# cannot read another's.
|
||||
user pos_terminal
|
||||
topic write nearle/pos/+/+/order
|
||||
topic write nearle/pos/+/+/customer
|
||||
topic write nearle/pos/+/+/status
|
||||
topic write nearle/pos/+/+/health
|
||||
topic read nearle/pos/+/+/ack
|
||||
topic read nearle/pos/+/+/command
|
||||
topic read nearle/pos/+/catalogue
|
||||
|
||||
# This backend: the mirror image.
|
||||
user pos_ingest
|
||||
topic read nearle/pos/+/+/order
|
||||
topic read nearle/pos/+/+/customer
|
||||
topic read nearle/pos/+/+/health
|
||||
topic read nearle/pos/+/+/status
|
||||
topic write nearle/pos/+/+/ack
|
||||
topic write nearle/pos/+/+/command
|
||||
topic write nearle/pos/+/catalogue
|
||||
|
||||
# Existing projects, scoped to what they already use.
|
||||
user admin
|
||||
topic readwrite nearle/riders/#
|
||||
topic readwrite doormile/#
|
||||
```
|
||||
|
||||
Tighten `pos_terminal` further with per-terminal credentials if you want one
|
||||
till unable to read another's acks at all; the pattern above trusts tills within
|
||||
the fleet but not outside it.
|
||||
but only once someone has confirmed nothing else authenticates as `admin`.
|
||||
Until then the ACL changes nothing for it — which is why applying it was safe.
|
||||
|
||||
**There is no TLS.** Port 8883 is not configured and is closed. Rider GPS
|
||||
travels in the clear today; POS bills carry customer names and mobile numbers,
|
||||
which is a different category of exposure on a shared network. Adding a listener
|
||||
means certs plus republishing the port, i.e. recreating the container — worth
|
||||
doing before rollout rather than after.
|
||||
Rollback, if ever needed:
|
||||
|
||||
**Two more, from the audit:**
|
||||
```bash
|
||||
cp /root/Mqtt/backup-<timestamp>/{mosquitto.conf,passwd} /root/Mqtt/config/
|
||||
docker restart mqtt_broker
|
||||
```
|
||||
|
||||
- The broker password and the workolik NATS password differ only in
|
||||
capitalisation. Diverge them when creating the scoped users.
|
||||
- Confirm on the host whether the broker was started from the compose file or
|
||||
from a bare `docker run` before editing the compose file and expecting it to
|
||||
take effect — there is precedent in this estate for compose existing but not
|
||||
being the deploy path.
|
||||
**Still outstanding on the broker:**
|
||||
|
||||
- **No TLS.** Port 8883 is not configured. Bills carry customer names and mobile
|
||||
numbers, and they travel in the clear. Traefik on the same host already
|
||||
terminates 443, so certificates exist to borrow from.
|
||||
- **`passwd` is world-readable.** Mosquitto warns about it and future versions
|
||||
will refuse to load it. Tightening it means `chown 1883:1883` as well as
|
||||
`chmod`, because the broker runs as uid 1883 and a root-owned 0600 file would
|
||||
stop it starting.
|
||||
- **Credentials in source.** `admin` is in the rider APK, Redis is hardcoded in
|
||||
the express backend, and Postgres was in this repository's git history until
|
||||
2026-08-03. The POS accounts above are the only ones not in any source tree —
|
||||
keep it that way.
|
||||
|
||||
## Capacity
|
||||
|
||||
|
||||
Reference in New Issue
Block a user