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 as zero: a board showing every till at 0% battery is worse than one showing
nothing. 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 Applied 2026-08-03 on `66.116.225.226`. Two scoped accounts now exist alongside
work; all of it gets worse the moment bills start flowing. `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 | User | May publish | May subscribe |
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 | `pos_terminal` | `nearle/pos/+/+/{order,customer,status,health}` | `nearle/pos/+/+/{ack,command}`, `nearle/pos/+/catalogue` |
anyone who decompiles it today has full publish and subscribe over `nearle/#` | `pos_ingest` | `nearle/pos/+/+/{ack,command}`, `nearle/pos/+/catalogue` | `nearle/pos/+/+/{order,customer,health,status}` |
*and* `doormile/#` — a second project's traffic. Adding POS puts every shop's | `admin` | everything — **deliberately unchanged** | everything |
takings behind the same credential.
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 **`admin` was left unrestricted on purpose.** Its credentials are compiled into
# A user for the tills, and one for this backend. the rider app, so narrowing it here would cut off the live rider fleet without
mosquitto_passwd -b /mosquitto/config/passwd pos_terminal '<strong-unique-pw>' warning. The right next step is:
mosquitto_passwd -b /mosquitto/config/passwd pos_ingest '<different-pw>'
```
```conf ```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 user admin
topic readwrite nearle/riders/# topic readwrite nearle/riders/#
topic readwrite doormile/# topic readwrite doormile/#
``` ```
Tighten `pos_terminal` further with per-terminal credentials if you want one but only once someone has confirmed nothing else authenticates as `admin`.
till unable to read another's acks at all; the pattern above trusts tills within Until then the ACL changes nothing for it — which is why applying it was safe.
the fleet but not outside it.
**There is no TLS.** Port 8883 is not configured and is closed. Rider GPS Rollback, if ever needed:
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.
**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 **Still outstanding on the broker:**
capitalisation. Diverge them when creating the scoped users.
- Confirm on the host whether the broker was started from the compose file or - **No TLS.** Port 8883 is not configured. Bills carry customer names and mobile
from a bare `docker run` before editing the compose file and expecting it to numbers, and they travel in the clear. Traefik on the same host already
take effect — there is precedent in this estate for compose existing but not terminates 443, so certificates exist to borrow from.
being the deploy path. - **`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 ## Capacity