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:
Suriya
2026-08-03 18:01:05 +05:30
parent 64a219e7da
commit ef647d3395

View File

@@ -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