Skip to content

Fix/telegram reseller rbac - #132

Open
rvalitov wants to merge 2 commits into
SamNet-dev:mainfrom
rvalitov:fix/telegram-reseller-rbac
Open

Fix/telegram reseller rbac#132
rvalitov wants to merge 2 commits into
SamNet-dev:mainfrom
rvalitov:fix/telegram-reseller-rbac

Conversation

@rvalitov

Copy link
Copy Markdown

Fixes #131

Problem

A reseller could drive 18 of the 22 administrative commands, contradicting the role hierarchy documented in the README. _check_tg_role() returns superadmin / reseller / none, but _process_cmd() only rejected none plus four superadmin-gated commands — five role comparisons in the entire file. A reseller therefore reached /mp_secrets, /mp_link, /mp_add, /mp_rotate, /mp_setlimit, /mp_broadcast, /mp_help, /reply and more. Two of those are credential disclosure (/mp_link hands out proxy links and QR codes) and /reply sends an arbitrary message to an arbitrary chat labelled "Support Team Reply", i.e. impersonation of the support team.

The README's claimed "security violation logging" was also unimplemented.

The same gate had a second, independent flaw: a role value that was neither superadmin nor reseller — a typo in a hand-edited admins.conf, since admin_add() normalises anything else to resellerfailed open, falling through into the admin case with the same access as a reseller (everything except the four superadmin-gated commands, which deny by default).

Changes

The control plane is now an explicit allowlist, evaluated immediately after the existing unauthenticated check so nothing can reach a handler first:

Role Result
superadmin Full control plane, unchanged
reseller /mp_voucher only; everything else refused and logged
none Silently ignored, unchanged
any other value Refused and logged, naming the role

Placing the gate before the handlers also means the four destructive commands are now caught here rather than at the superadmin gates — one place to reason about, and those gates become unreachable defence-in-depth.

Roles are matched exactly, so a differently-cased value such as SUPERADMIN fails closed rather than open. admin_add() only ever writes superadmin or reseller, so any other value is a misconfiguration, and the denial log names it to make that obvious.

Denials are sent to the sender via tg_send_to() rather than to the admin chat, and recorded in ${INSTALL_DIR}/audit.log:

2026-09-10 21:06:34 UTC | telegram:333 | SECURITY: denied /mp_restart

A new _tg_security_log() writes that entry from inside the bot daemon. The daemon is deliberately self-contained and never sources the manager, so it cannot call the manager's audit_log(); the helper writes the same line format inline instead.

README updated to document the fail-closed rule for unrecognised roles.

Testing

Adds tests/test_telegram_reseller_rbac.sh62 assertions, following the existing test_traffic_reset.sh pattern: generate the daemon, extract the real _process_cmd() and _tg_security_log() from it, stub the Telegram senders, and exercise the dispatcher directly.

Coverage:

  • A reseller is refused across the control plane (/mp_status, /mp_restart, /mp_lockdown, /mp_update, /mp_remove, /mp_add, /mp_broadcast, /mp_secrets, /mp_link, /mp_setlimit, /mp_help, /mp_traffic, /reply) — and each refusal is logged.
  • A reseller keeps /mp_voucher list, /mp_voucher create and all public commands.
  • Refusals go to the sender, not the admin chat.
  • A superadmin is unaffected, and legitimate actions are not logged as violations.
  • Unrecognised roles (operator, administrator, root, SUPERADMIN, superadmin2) are refused and logged with the role named, cannot reach the voucher engine, and still receive public commands.

Before the fix the unknown-role assertions fail with the role reaching admin handlers and the voucher engine; before the first commit the reseller assertions fail with the account receiving the full /mp_help list.

Verified with bash tests/test_telegram_reseller_rbac.sh (62/62, 0 failures) and bash -n mtproxymax.sh. The wider suite is unchanged: the only failures are test_client_mss.sh and test_lxc_ram_and_resources.sh, which fail identically on unmodified main (test_lxc requires root).

The README restricts a `reseller` to voucher redemption (/redeem), voucher
batch generation (/mp_voucher create) and voucher inventory auditing
(/mp_voucher list), with everything else "automatically blocked with security
violation logging". None of that was true.

_check_tg_role() can return superadmin, reseller or none, but _process_cmd
only rejected none plus four superadmin-only commands. A reseller therefore
reached nearly the whole Admin Control Plane, including /mp_secrets, /mp_add,
/mp_rotate, /mp_setlimit, /mp_broadcast and /mp_help. The claimed violation
logging did not exist anywhere in the repo.

Gate the dispatcher on the reseller role immediately after the existing
unauthenticated check, so non-voucher control plane commands are refused
before reaching any handler. Placing it there also catches the four
destructive commands, which previously fell through to the superadmin gates.
Denials reply to the sender rather than the admin chat and are recorded in
${INSTALL_DIR}/audit.log.

The daemon is self-contained and never sources the manager, so it cannot call
the manager's audit_log(); _tg_security_log() writes the same line format
inline instead.
_check_tg_role() returns whatever admins.conf holds, and admins.conf is a
plain file an operator can hand-edit. The dispatcher only special-cased
"none" and "reseller", so any other value -- a typo like "SUPERADMIN", or a
stale role such as "operator" -- fell through into the admin case and was
granted the control plane, minus only the four superadmin-gated commands.

Make the control plane an allowlist: superadmin and reseller are handled
explicitly and everything else is refused and logged, naming the offending
role so the misconfiguration is obvious. Roles are matched exactly, so a
differently-cased value fails closed rather than open.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram bot RBAC: reseller role unenforced, unknown roles fail open

1 participant