RE:NODE

Пользователи и роли MongoDB: createUser и минимум прав

Автор Команда RE:NODE
16 мин чтения


Пользователь MongoDB - это имя плюс база данных, в которой он создан, и делать он может ровно то, что разрешают его роли, и ничего больше. Вы создаёте его через db.createUser(), выдаёте одну или несколько ролей, каждая из которых привязана к своей базе, и затем пользователь проходит аутентификацию в той базе, где был создан. Для приложения почти всегда правильный ответ - отдельный пользователь с ролью readWrite на базу этого приложения и без лишнего. Для дашборда - read. Для задачи резервного копирования - backup. Суперпользователь с ролью root хранится в менеджере паролей, и пользуется им человек, и то редко.

Это вся модель. Остальная часть руководства - детали, благодаря которым она работает: что делает каждая встроенная роль (включая две, которые гораздо мощнее, чем подсказывают их названия), где на самом деле хранятся пользователи, как получить их список, сменить пароль и изменить роли, и как написать собственную роль, когда ни одна встроенная не подходит по узости. Везде используется mongosh, и всё сказанное относится к MongoDB 5.0 и новее; большая часть работает без изменений вплоть до 4.x.

Где живут пользователи: база аутентификации#

Каждый пользователь создаётся в какой-то базе, и эта база входит в его идентичность. shopapp, созданный в shop, и shopapp, созданный в admin, - это два разных пользователя с разными паролями и разными ролями. База, в которой создан пользователь, называется его базой аутентификации, и именно её называет параметр authSource в строке подключения. В статье строки подключения MongoDB рассказано, как этот параметр разрешается и почему ошибка в нём приводит к Authentication failed при верном пароле.

Физически все пользователи лежат в одной коллекции независимо от того, где они созданы: admin.system.users. Поле db в каждом документе хранит базу аутентификации. Поэтому пользователей иногда записывают как shopapp@shop - имя, затем база аутентификации.

База аутентификации и базы, в которых пользователь может работать, не зависят друг от друга. Пользователь, созданный в admin, может иметь readWrite на shop и ничего на самой admin, а пользователю, созданному в shop, можно выдать роли и на analytics. Место создания пользователя определяет только то, где он входит; что он может трогать, определяют роли.

Работают два подхода:

  • Все пользователи в `admin`. Одно место, где можно посмотреть, один authSource=admin в каждой строке подключения, а роли привязаны к конкретным базам. Это более распространённая схема на одиночных серверах.
  • Каждый пользователь приложения в своей базе. shopapp живёт в shop, blogapp живёт в blog, и show users внутри каждой базы показывает ровно те учётные записи, которые ей принадлежат. В строке подключения используется authSource=shop.

Выберите один подход и придерживайтесь его. Смешанное состояние - часть пользователей в admin, часть где-то ещё, и никто не помнит, где что - это то, откуда берутся потерянные вечера.

Создание пользователя через db.createUser#

Сначала нужно включить аутентификацию - security.authorization: enabled в mongod.conf. Без этого роли не применяются, и любое подключение может делать что угодно. В хостинговой базе она уже включена, и вы получаете первый набор учётных данных. На собственной установке исключение localhost позволяет подключению с самого сервера создать первого пользователя в admin, пока пользователей нет; оно закрывается, как только этот пользователь создан.

Подключитесь под пользователем, которому разрешено управлять пользователями, переключитесь на выбранную базу аутентификации и создайте пользователя:

javascript
use admindb.createUser({  user: "shopapp",  pwd: passwordPrompt(),  roles: [ { role: "readWrite", db: "shop" } ]})

Поля:

ПолеОбязательноЧто хранит
userдаИмя, уникальное в пределах этой базы
pwdдаПароль; passwordPrompt() запрашивает его
rolesдаМассив, который может быть пустым []
customDataнетЛюбой документ, для ваших собственных заметок
authenticationRestrictionsнетРазрешённые адреса клиентов и сервера
mechanismsнетSCRAM-SHA-1, SCRAM-SHA-256 или оба

passwordPrompt() важнее, чем кажется. Пароль, набранный прямо в команде, pwd: "hunter2", остаётся в истории терминала, в демонстрации экрана и в любой записи сессии или скрипте, куда вы его вставите. mongosh и так не сохраняет известные команды с учётными данными в файл истории, но при запросе пароля сам вопрос не возникает.

Роли можно записать двумя способами. { role: "readWrite", db: "shop" } - явная форма, она всегда верна. Просто строка, "readWrite", означает эту роль на той базе, в которой вы сейчас находитесь, так что та же команда, выполненная в admin, выдаст readWrite на admin, а это почти никогда не то, что вы хотели. Используйте форму с документом каждый раз, и вопроса не возникнет.

customData - свободное поле, и его стоит использовать для одного факта, который вам понадобится позже: customData: { owner: "checkout-api", created: "2026-10-15" }. Оно показывается при выводе списка пользователей, а это как раз тот момент, когда вы пытаетесь понять, зачем нужна старая учётная запись.

Ограничение адресов, откуда можно подключаться

authenticationRestrictions ограничивает пользователя определёнными адресами клиентов - это недорогой второй слой защиты за firewall:

javascript
use admindb.createUser({  user: "reporting",  pwd: passwordPrompt(),  roles: [ { role: "read", db: "shop" } ],  authenticationRestrictions: [ { clientSource: ["10.0.0.0/24"] } ]})

Вход с любого другого адреса завершится ошибкой аутентификации, даже если пароль верный. Польза от этого настолько велика, насколько стабильны ваши адреса: приложение на платформе без фиксированного исходящего адреса использовать это не сможет. Оно также не заменяет привязку к интерфейсу и firewall - недоступный порт остаётся лучшим средством контроля, о чём подробно говорится в чек-листе безопасности баз данных.

Встроенные роли и что они разрешают на самом деле#

MongoDB поставляется с набором ролей, закрывающим большинство потребностей. Роли уровня базы выдаются на одну базу за раз:

РольЧто разрешаетКто обычно её имеет
readЗапросы ко всем несистемным коллекциямДашборды, отчёты, люди
readWriteread плюс insert, update, deleteПриложение
dbAdminИндексы, статистика, профилирование, валидацияРедко кто-либо в одиночку
userAdminСоздание и изменение пользователей и ролейНикто на постоянной основе
dbOwnerreadWrite, dbAdmin и userAdminДоверенный администратор одной базы

Роли, существующие только в базе admin и действующие на весь сервер:

РольЧто разрешаетКто обычно её имеет
readAnyDatabaseread на каждую базуОтчёты по всему серверу
readWriteAnyDatabasereadWrite на каждую базуИнструменты миграции, ненадолго
dbAdminAnyDatabasedbAdmin на каждую базуИнструменты эксплуатации
userAdminAnyDatabaseuserAdmin на каждую базуПо сути суперпользователь
clusterMonitorСтатистика сервера и репликации только для чтенияАгенты мониторинга
backupВсё, что нужно mongodumpЗадачи резервного копирования
restoreВсё, что нужно mongorestoreЗадачи восстановления, ненадолго
rootВсё перечисленное вместеАварийная учётная запись

Три из этих ролей требуют честного замечания, потому что названия преуменьшают их возможности.

`readWrite` может удалять коллекции. Помимо вставки, обновления и удаления, в неё входят createCollection, createIndex, dropIndex и dropCollection. Удалить саму базу она не может, но приложение с readWrite и неудачным скриптом миграции всё равно способно опустошить базу коллекция за коллекцией. Если ваше приложение во время работы не создаёт и не удаляет коллекции, собственная роль (ниже) может это отнять.

`dbAdmin` - это не доступ на чтение, но она может удалять. Она охватывает индексы, статистику, профилировщик, collMod и валидацию и не позволяет выполнять find по документам в обычных коллекциях. При этом в неё входят dropCollection и dropDatabase. Так что пользователь только с dbAdmin не может прочитать ваши данные и может уничтожить их все. Выдавать такое сочетание кому-либо странно, и поэтому она редко встречается сама по себе.

`userAdmin` - это замаскированный root. Пользователь, который может создавать пользователей и выдавать роли, может выдать любую роль на этой базе кому угодно, в том числе себе. На одной базе это делает userAdmin равной dbOwner. В admin роль userAdmin или userAdminAnyDatabase может выдать root. Об этом прямо говорит документация MongoDB. Относитесь к любой учётной записи с одной из этих ролей как к суперпользователю, защищайте её соответственно и никогда не помещайте в строку подключения приложения.

dbOwner и всё, что оканчивается на AnyDatabase, для приложений попадает в ту же категорию: никогда. Если пользователь приложения может добраться до каждой базы, ошибка в запросе вроде SQL-инъекции или утёкший файл .env откроют их все.

Минимум прав: один пользователь на одну задачу#

Полезно думать о пользователях по задачам, а не по людям и не по системам. Каждый, кто подключается, получает собственного пользователя с самой узкой ролью, позволяющей ему работать. Типичная небольшая конфигурация выглядит так:

javascript
use admin// Работающее приложение: чтение и запись только в своей базе.db.createUser({  user: "shop_app", pwd: passwordPrompt(),  roles: [ { role: "readWrite", db: "shop" } ],  customData: { owner: "checkout-api" }})// Дашборды и люди, которые просто заглядывают: только чтение.db.createUser({  user: "shop_ro", pwd: passwordPrompt(),  roles: [ { role: "read", db: "shop" } ]})// Ночной дамп: роль backup и ничего больше.db.createUser({  user: "backup_job", pwd: passwordPrompt(),  roles: [ { role: "backup", db: "admin" } ]})// Проверка мониторинга: статистика сервера, без данных.db.createUser({  user: "monitor", pwd: passwordPrompt(),  roles: [ { role: "clusterMonitor", db: "admin" } ]})

Отдельные пользователи окупаются тремя способами. Утёкшие учётные данные раскрывают права одной задачи, а не всего сразу. Смена одного пароля затрагивает одного потребителя, а не все сервисы разом. А журнал сервера и db.currentOp() показывают пользователя для каждой операции, так что когда появляется запрос на девять секунд, вы знаете, какой сервис его прислал, ещё до того, как начнёте гадать. Добавьте appName в каждую строку подключения, и вопрос "кто это делает" решается сам собой.

Для пользователя резервного копирования, который всегда снимает дамп только одной базы, достаточно read на эту базу, чтобы mongodump выгрузил её коллекции. Роль backup нужна для дампа всего сервера, включая пользователей и роли через --dumpDbUsersAndRoles. Механика обоих вариантов описана в статье mongodump и mongorestore. Роль restore выдавайте пользователю только на время восстановления: она может писать в любую базу.

readWrite на shopread на shopbackupclusterMonitorПриложениеshop_appДашбордshop_roРезервная копияbackup_jobМониторингmonitormongodauth через adminБаза shop
Каждый потребитель подключается под своим пользователем

Та же идея в других СУБД выглядит с другим синтаксисом и тем же смыслом: если вы используете больше одной, см. пользователи и привилегии MySQL и роли и права PostgreSQL.

Список пользователей и проверка их прав#

Четыре команды отвечают почти на любой вопрос о том, кто существует и что ему разрешено:

javascript
use adminshow users                                   // пользователи текущей базыdb.getUsers()                                // то же, в виде документовdb.getUser("shop_app", { showPrivileges: true })db.adminCommand({ usersInfo: { forAllDBs: true } })   // все пользователи сервера

show users и db.getUsers() перечисляют только тех пользователей, чья база аутентификации - та, в которой вы находитесь. Если вы создаёте пользователей в нескольких базах, полный список даёт только форма с forAllDBs, и для неё нужен пользователь с правом просматривать пользователей по всему серверу.

showPrivileges: true раскрывает роли пользователя до реальных действий, которые они дают, включая роли, унаследованные от других ролей, в поле inheritedPrivileges. Вывод длинный, но это ответ на вопрос "может ли этот пользователь удалить ту коллекцию" без проверки на боевой базе.

Чтобы проверить пользователя, под которым вы подключены сейчас:

javascript
db.runCommand({ connectionStatus: 1, showPrivileges: true })

authInfo.authenticatedUsers показывает, кто вы и где аутентифицировались; authenticatedUserRoles показывает действующие роли. Когда приложение получает not authorized on shop to execute command, запуск этой команды в mongosh с учётными данными приложения точно показывает, что у него есть, и это быстрее, чем читать код, который его создавал.

Смена паролей, ролей и удаление пользователей#

Пользователи меняются в течение жизни, и для каждого изменения есть своя команда:

javascript
use admindb.changeUserPassword("shop_app", passwordPrompt())db.grantRolesToUser("shop_ro", [ { role: "read", db: "analytics" } ])db.revokeRolesFromUser("shop_ro", [ { role: "read", db: "analytics" } ])db.updateUser("shop_app", { customData: { owner: "checkout-api-v2" } })db.dropUser("old_worker")

Выполняйте их в базе аутентификации пользователя - changeUserPassword в shop для пользователя, который живёт в admin, завершится ошибкой User shop_app@shop not found; это та же ошибка, что и неверный authSource, только с другой стороны.

db.updateUser() с полем roles заменяет весь массив, а не дополняет его. Если передать одну роль, чтобы "добавить" её, то все остальные роли пользователя молча исчезнут. Для пошаговых изменений используйте grantRolesToUser и revokeRolesFromUser, а updateUser оставьте для customData, authenticationRestrictions и полных перезаписей, которые вы задумали намеренно.

Изменения ролей вступают в силу немедленно на одиночном mongod, в том числе для уже открытых подключений. В шардированном кластере mongos кэширует сведения о пользователях и обновляет их с интервалом userCacheInvalidationIntervalSecs, по умолчанию 30 секунд.

Смена пароля приложения без простоя

Если сменить пароль пользователя, которым пользуется работающее приложение, новые подключения ломаются в тот же момент. Уже существующие подключения из пула остаются аутентифицированными, поэтому сбой проявляется позже и выглядит случайным. Чистый способ - два пользователя:

  1. Создайте shop_app_2 с точно теми же ролями, что у shop_app.
  2. Пропишите нового пользователя и пароль в окружении приложения и перезапустите или заново разверните его.
  3. Следите за db.currentOp() или журналом сервера, пока никто не будет аутентифицироваться как shop_app.
  4. db.dropUser("shop_app").

При следующей смене пароля идите в обратную сторону. Это стоит одного лишнего пользователя на несколько минут и полностью избавляет от полусломанного состояния. Где должны храниться учётные данные в процессе, описано в статье переменные окружения и секреты.

Как позволить пользователям менять свой пароль

Ни одна встроенная роль ниже userAdmin не включает смену собственного пароля, поэтому пользователь только с readWrite не может выполнить changeUserPassword для самого себя. Если вы хотите, чтобы люди сами меняли свои учётные данные, выдайте небольшую собственную роль с действием changeOwnPassword, она показана в следующем разделе.

Собственные роли для всего более узкого#

Когда ни одна встроенная роль не подходит - например, воркер, которому можно вставлять в одну коллекцию и читать другую, или пользователь отчётов, который не должен видеть коллекцию customers, - напишите роль. Роль - это список привилегий, каждая из которых состоит из ресурса и действий, и при желании других ролей, которые она наследует:

javascript
use shopdb.createRole({  role: "ordersWriter",  privileges: [    { resource: { db: "shop", collection: "orders" },      actions: [ "find", "insert", "update" ] },    { resource: { db: "shop", collection: "products" },      actions: [ "find" ] }  ],  roles: []})use admindb.grantRolesToUser("order_worker", [ { role: "ordersWriter", db: "shop" } ])

Этот воркер может читать products, читать, добавлять и изменять orders, и больше ничего: ни remove, ни dropCollection, ни доступа к другим коллекциям.

Формы ресурсов, которые стоит знать:

РесурсОзначает
{ db: "shop", collection: "orders" }Одну коллекцию
{ db: "shop", collection: "" }Все несистемные коллекции в shop
{ db: "", collection: "orders" }orders в любой базе
{ cluster: true }Действия уровня сервера, например serverStatus

Два правила о том, где живут роли. Роль, созданная в обычной базе, может выдавать привилегии только на эту базу. Роль, созданная в admin, может выдавать привилегии на любую базу и на кластер, поэтому роли, охватывающие несколько баз, создают в admin. И на роль ссылаются по имени и базе вместе, точно так же, как на пользователя: ordersWriter в shop - это { role: "ordersWriter", db: "shop" }.

Несколько действий покрывают большинство собственных ролей: find, insert, update, remove для данных; createIndex, dropIndex, createCollection, dropCollection для структуры; listCollections, listIndexes, collStats, dbStats для инструментов, которые изучают базу; changeOwnPassword и changeOwnCustomData для самообслуживания. Полный список есть на странице руководства MongoDB о действиях привилегий, и лучше свериться с ней, чем гадать: опечатка в названии действия отвергается при createRole, а вот отсутствующее действие обнаруживается лишь тогда, когда приложение на него наткнётся.

Роль самообслуживания для смены пароля, упомянутая выше:

javascript
use admindb.createRole({  role: "changeOwnPassword",  privileges: [    { resource: { db: "admin", collection: "" },      actions: [ "changeOwnPassword", "changeOwnCustomData" ] }  ],  roles: []})

Ресурс - это база аутентификации пользователя. Выдайте её вместе с настоящей ролью пользователя, и он сможет выполнить db.changeUserPassword() для себя и ни для кого другого.

Просматривать и поддерживать роли можно через db.getRoles({ showPrivileges: true }), db.getRole("ordersWriter", { showPrivileges: true }), db.grantPrivilegesToRole(), db.revokePrivilegesFromRole() и db.dropRole(). Изменение роли сразу меняет права всех, у кого она есть, - в этом и смысл, и это же причина называть роли по задаче, а не по человеку.

Ошибки, с которыми вы столкнётесь#

`Authentication failed` (код 18). Обычно это верный пароль, проверяемый в не той базе аутентификации. Выполните db.adminCommand({ usersInfo: { forAllDBs: true } }) под администратором, посмотрите на поле db пользователя и приведите authSource в соответствие с ним.

`not authorized on shop to execute command { ... }` (код 13). Аутентификация прошла, но среди ролей нет нужного действия. Сообщение называет команду; connectionStatus с showPrivileges показывает, что у пользователя есть. Выдайте самую узкую роль, которая добавляет нужное, а не dbOwner.

`command createUser requires authentication`. Вы подключены анонимно к серверу с включённой авторизацией. Подключитесь заново с учётными данными.

Пользователь уже существует или пользователь не найден. Команда use перед вашей командой переместила вас в другую базу, чем вы думали, и MongoDB искала shop_app@shop вместо shop_app@admin. Выполняйте db.getName() перед командами для пользователей, и этого не случится.

`Could not find role: ordersWriter@admin`. Вы сослались на собственную роль по имени в базе, где она не создавалась. Укажите полный документ { role, db } с базой, в которой роль живёт. Та же ошибка возникает для встроенной роли с неверным регистром: правильные названия - readWrite, dbAdmin, userAdmin и clusterMonitor, а readwrite не существует.

В хостинговой MongoDB#

На сервере, который вы арендуете, а не устанавливаете, аутентификация уже включена, и первые учётные данные вам выдают - на RE:NODE они генерируются для каждого сервера и показываются в панели рядом с хостом и портом, так что никакого общего пароля по умолчанию, который нужно менять, нет. Относитесь к этой учётной записи как к суперпользователю, которым она и является: используйте её, чтобы создать описанных выше пользователей по задачам, храните её в менеджере паролей и не помещайте в окружение приложения.

Управляемые кластерные сервисы иногда полностью выводят управление пользователями из оболочки. MongoDB Atlas, например, управляет пользователями баз данных через собственный интерфейс и API, а не через db.createUser(), хотя роли там те же по смыслу. На одиночном сервере, к которому вы подключаетесь по хосту и порту, всё в этом руководстве работает так, как написано.

Пользователи и роли хранятся внутри базы, поэтому они входят в то, что должна захватывать полная резервная копия. Дамп всего сервера через mongodump с ролью backup или копия каталога данных на уровне файловой системы включает admin.system.users; дамп одной базы не включает пользователей, пока вы не попросите об этом. В статье резервные копии и восстановление баз данных описано, какой вид копии снимать на какой случай беды.

FAQ#

В какой базе создавать пользователей MongoDB?

Подойдёт и admin, и собственная база приложения, а пользователь в любой из них может иметь роли на других базах. admin хранит всех пользователей в одном месте с authSource=admin везде; пользователь в своей базе держит учётные записи каждого приложения рядом с его данными. Выберите один подход и используйте его последовательно, потому что authSource должен ему соответствовать.

Чем отличаются readWrite и dbOwner?

readWrite позволяет читать и изменять данные и управлять коллекциями и индексами. dbOwner добавляет dbAdmin и userAdmin, а значит, можно ещё и удалить базу и создавать пользователей с любой ролью на ней. Приложению нужна первая и никогда не должна быть выдана вторая.

Как посмотреть всех пользователей MongoDB на сервере?

Выполните db.adminCommand({ usersInfo: { forAllDBs: true } }) под пользователем, у которого есть право просматривать пользователей. show users показывает только тех, кто создан в текущей базе, поэтому список выглядит неполным, если пользователи есть в нескольких местах.

Можно ли дать пользователю доступ только на чтение к некоторым коллекциям?

Встроенной ролью - нет: read охватывает все несистемные коллекции базы. Создайте собственную роль с find на каждую коллекцию, которую нужно показать, и выдайте её. Коллекции, которых нет в списке, остаются невидимыми для запросов этого пользователя.

Нужен ли перезапуск или переподключение после изменения ролей?

Нет. На одиночном mongod выдача или отзыв роли применяется сразу, даже к уже открытым подключениям. В шардированном кластере mongos может заметить изменение не сразу, а в течение userCacheInvalidationIntervalSecs - по умолчанию 30 секунд.

Достаточно ли надёжного пароля, если у пользователя приложения роль root?

Нет. Пароль защищает от подбора, а не от того, что приложение обманом заставят выполнить запрос, который ему не положен, и не от утечки файла окружения. Ограничение роли ограничивает ущерб в обоих случаях, а этого пароль сделать не может.