Fix/telegram reseller rbac - #132
Open
rvalitov wants to merge 2 commits into
Open
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #131
Problem
A
resellercould drive 18 of the 22 administrative commands, contradicting the role hierarchy documented in the README._check_tg_role()returnssuperadmin/reseller/none, but_process_cmd()only rejectednoneplus foursuperadmin-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,/replyand more. Two of those are credential disclosure (/mp_linkhands out proxy links and QR codes) and/replysends 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
superadminnorreseller— a typo in a hand-editedadmins.conf, sinceadmin_add()normalises anything else toreseller— failed open, falling through into the admin case with the same access as a reseller (everything except the foursuperadmin-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:
superadminreseller/mp_voucheronly; everything else refused and loggednonePlacing the gate before the handlers also means the four destructive commands are now caught here rather than at the
superadmingates — one place to reason about, and those gates become unreachable defence-in-depth.Roles are matched exactly, so a differently-cased value such as
SUPERADMINfails closed rather than open.admin_add()only ever writessuperadminorreseller, 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: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'saudit_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.sh— 62 assertions, following the existingtest_traffic_reset.shpattern: generate the daemon, extract the real_process_cmd()and_tg_security_log()from it, stub the Telegram senders, and exercise the dispatcher directly.Coverage:
/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./mp_voucher list,/mp_voucher createand all public commands.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_helplist.Verified with
bash tests/test_telegram_reseller_rbac.sh(62/62, 0 failures) andbash -n mtproxymax.sh. The wider suite is unchanged: the only failures aretest_client_mss.shandtest_lxc_ram_and_resources.sh, which fail identically on unmodifiedmain(test_lxcrequires root).