RE:NODE

Uživatelé a role v MongoDB: createUser a least privilege

Text: Tým RE:NODE
16 min čtení


Uživatel v MongoDB je jméno plus databáze, ve které vznikl, a smí přesně to, co mu dovolují jeho role, a nic jiného. Vytvoříš ho pomocí db.createUser(), dáš mu jednu nebo více rolí - každou vázanou na jednu databázi - a uživatel se pak přihlašuje proti databázi, ve které byl vytvořen. Pro aplikaci je správná odpověď téměř vždy samostatný uživatel s rolí readWrite na databázi té aplikace a na ničem dalším. Pro dashboard read. Pro zálohovací úlohu backup. Superuživatel s rolí root zůstává v správci hesel a používá ho člověk, jen zřídka.

To je celý model. Zbytek tohoto návodu jsou detaily, díky kterým to funguje: která vestavěná role co dělá (včetně dvou, které jsou mnohem silnější, než naznačují jejich názvy), kde jsou uživatelé skutečně uloženi, jak je vypsat, změnit jim heslo a upravit role a jak napsat vlastní roli, když žádná vestavěná není dost úzká. Vše tady používá mongosh a platí pro MongoDB 5.0 a novější; většina je beze změny i zpět až k verzi 4.x.

Kde uživatelé žijí: autentizační databáze#

Každý uživatel je vytvořen v nějaké databázi a ta databáze je součástí jeho identity. shopapp vytvořený v shop a shopapp vytvořený v admin jsou dva různí uživatelé s oddělenými hesly a oddělenými rolemi. Databáze, ve které byl uživatel vytvořen, se nazývá jeho autentizační databáze a právě ji pojmenovává volba authSource v connection stringu. Connection stringy MongoDB popisují, jak se tato volba vyhodnocuje a proč její chybné nastavení vede k Authentication failed i při správném hesle.

Fyzicky jsou všichni uživatelé v jedné kolekci bez ohledu na to, kde vznikli: admin.system.users. Pole db v každém dokumentu zaznamenává autentizační databázi. Proto někdy uvidíš uživatele zapsané jako shopapp@shop - jméno, potom autentizační databáze.

Autentizační databáze a databáze, ve kterých uživatel smí pracovat, jsou na sobě nezávislé. Uživatel vytvořený v admin může mít readWrite na shop a nic na samotné admin, a uživateli vytvořenému v shop lze přidělit role i na analytics. Kde uživatel žije, určuje jen to, kde se přihlašuje; role určují, na co smí sáhnout.

Fungují dvě konvence:

  • Všichni uživatelé v `admin`. Jedno místo, kde se dívat, jedno authSource=admin v každém connection stringu a role vázané na jednotlivé databáze. Na jednotlivých serverech je to běžnější nastavení.
  • Každý aplikační uživatel ve vlastní databázi. shopapp žije v shop, blogapp žije v blog a show users v každé databázi vypíše přesně účty, které do ní patří. Connection string používá authSource=shop.

Vyber si jednu a drž se jí. Smíšený stav - někteří uživatelé v admin, jiní jinde, nikdo neví, kdo kde - je místo, kde se ztrácejí celá odpoledne.

Vytvoření uživatele pomocí db.createUser#

Nejdřív musí být zapnutá autentizace - security.authorization: enabled v mongod.conf. Bez ní se role nevynucují a každé připojení může dělat cokoli. U hostované databáze je to už zapnuté a dostaneš první sadu přihlašovacích údajů. Na vlastní instalaci umožňuje výjimka pro localhost (localhost exception) připojení ze samotného serveru vytvořit prvního uživatele v admin, dokud žádní uživatelé neexistují; zavře se hned, jakmile je tento uživatel vytvořen.

Připoj se jako uživatel, který smí spravovat uživatele, přepni se do zvolené autentizační databáze a vytvoř uživatele:

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

Pole:

PolePovinnéCo obsahuje
useranoJméno, unikátní v rámci této databáze
pwdanoHeslo; passwordPrompt() si ho vyžádá
rolesanoPole, které smí být prázdné []
customDataneLibovolný dokument pro tvé vlastní poznámky
authenticationRestrictionsnePovolené adresy klientů a serverů
mechanismsneSCRAM-SHA-1, SCRAM-SHA-256, nebo obojí

passwordPrompt() je důležitější, než vypadá. Heslo napsané přímo, pwd: "hunter2", zůstane v historii terminálu, při sdílení obrazovky a v každém záznamu relace nebo skriptu, do kterého ho vložíš. mongosh už známé příkazy s přihlašovacími údaji do souboru s historií neukládá, ale s promptem tato otázka vůbec nevznikne.

Role lze zapsat dvěma způsoby. { role: "readWrite", db: "shop" } je explicitní a vždy správné. Holý řetězec "readWrite" znamená tuto roli na databázi, ve které se právě nacházíš - takže stejný příkaz spuštěný v admin přidělí readWrite na admin, což skoro nikdy nechceš. Používej pokaždé zápis jako dokument a tahle otázka nevznikne.

customData je volná struktura a vyplatí se do ní dát jediný údaj, který budeš později chtít: customData: { owner: "checkout-api", created: "2026-10-15" }. Zobrazí se při výpisu uživatelů, tedy ve chvíli, kdy zjišťuješ, k čemu starý účet slouží.

Omezení, odkud se smí uživatel připojit

authenticationRestrictions omezí uživatele na určité adresy klientů, což je levná druhá vrstva za firewallem:

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

Přihlášení z jakékoli jiné adresy selže, i když je heslo správné. Je to užitečné jen natolik, nakolik jsou tvé adresy stabilní: aplikace na platformě bez pevné odchozí adresy to použít nemůže. Také to nenahrazuje vázání na rozhraní a firewall - nedostupný port je pořád lepší kontrola, jak podrobně rozebírá bezpečnostní checklist databáze.

Vestavěné role a co doopravdy dovolují#

MongoDB se dodává se sadou rolí, které pokryjí většinu potřeb. Role vázané na databázi se přidělují vždy na jednu databázi:

RoleDovolujeTypický držitel
readDotazovat všechny nesystémové kolekceDashboardy, reporty, lidé
readWriteread plus vkládání, úpravy, mazáníAplikace
dbAdminIndexy, statistiky, profilování, validaceMálokdo samostatně
userAdminVytvářet a měnit uživatele a roleNikdo běžně
dbOwnerreadWrite, dbAdmin a userAdminDůvěryhodný správce jedné databáze

Role, které existují jen v databázi admin a platí pro celý server:

RoleDovolujeTypický držitel
readAnyDatabaseread na každé databáziReporty přes celý server
readWriteAnyDatabasereadWrite na každé databáziMigrační nástroje, krátkodobě
dbAdminAnyDatabasedbAdmin na každé databáziProvozní nástroje
userAdminAnyDatabaseuserAdmin na každé databáziPrakticky superuživatel
clusterMonitorStatistiky serveru a replikace jen pro čteníMonitorovací agenti
backupVšechno, co potřebuje mongodumpZálohovací úlohy
restoreVšechno, co potřebuje mongorestoreObnovovací úlohy, krátkodobě
rootVše výše dohromadyNouzový účet

Tři z nich si zaslouží upřímnou poznámku, protože jejich názvy jsou zavádějící.

`readWrite` umí mazat kolekce. Kromě vkládání, úprav a mazání dokumentů zahrnuje createCollection, createIndex, dropIndex a dropCollection. Databázi smazat nemůže, ale aplikace s readWrite a špatným migračním skriptem ji může vyprázdnit kolekci po kolekci. Pokud tvá aplikace za běhu kolekce nevytváří ani neodstraňuje, vlastní role (níže) to může odebrat.

`dbAdmin` není přístup pro čtení - ale smazat umí. Pokrývá indexy, statistiky, profiler, collMod a validaci a nedovolí ti dělat find nad dokumenty v běžných kolekcích. Zahrnuje ale dropCollection a dropDatabase. Uživatel jen s dbAdmin tedy nepřečte tvá data, ale může je všechna zničit. To je zvláštní kombinace, kterou nikomu nedávej, a proto se samostatně objevuje zřídka.

`userAdmin` je root v přestrojení. Uživatel, který umí vytvářet uživatele a přidělovat role, může komukoli, včetně sebe, přidělit libovolnou roli na dané databázi. Na jedné databázi je tedy userAdmin rovnocenný s dbOwner. V admin může userAdmin nebo userAdminAnyDatabase přidělit root. Říká to i oficiální dokumentace MongoDB. Každý účet s jednou z nich ber jako superuživatele, chraň ho jako superuživatele a nikdy ho nedávej do connection stringu aplikace.

dbOwner a vše, co končí na AnyDatabase, patří z hlediska aplikací do stejné kategorie: nikdy. Pokud se uživatel aplikace dostane na každou databázi, chyba v dotazu typu SQL injection nebo uniklý soubor .env vystaví všechny.

Least privilege: jeden uživatel na jednu úlohu#

Užitečný způsob uvažování o uživatelích je podle úlohy, ne podle člověka nebo systému. Každá věc, která se připojuje, dostane vlastního uživatele s nejužší rolí, se kterou může pracovat. Typické malé nastavení vypadá takhle:

javascript
use admin// The running application: read and write its own database only.db.createUser({  user: "shop_app", pwd: passwordPrompt(),  roles: [ { role: "readWrite", db: "shop" } ],  customData: { owner: "checkout-api" }})// Dashboards and people poking about: read only.db.createUser({  user: "shop_ro", pwd: passwordPrompt(),  roles: [ { role: "read", db: "shop" } ]})// Nightly dump: the backup role, nothing else.db.createUser({  user: "backup_job", pwd: passwordPrompt(),  roles: [ { role: "backup", db: "admin" } ]})// The monitoring check: server stats, no data.db.createUser({  user: "monitor", pwd: passwordPrompt(),  roles: [ { role: "clusterMonitor", db: "admin" } ]})

Oddělení uživatelé se vyplatí třemi způsoby. Uniklé přihlašovací údaje odhalí práva jedné úlohy, ne všeho. Rotace jednoho hesla se dotkne jednoho konzumenta, ne všech služeb najednou. A log serveru i db.currentOp() ukazují uživatele u každé operace, takže když se objeví devítivteřinový dotaz, víš, která služba ho poslala, dřív než začneš hádat. Přidej k tomu appName v každém connection stringu a otázka „kdo to dělá“ se zodpoví sama.

Pro zálohovacího uživatele, který vždy exportuje jen jednu databázi, stačí read na té databázi, aby mongodump vyexportoval její kolekce. Role backup je určena pro export celého serveru, včetně uživatelů a rolí pomocí --dumpDbUsersAndRoles. Mechanika obojího je v mongodump a mongorestore. Roli restore dej uživateli jen na dobu obnovy; může zapisovat do jakékoli databáze.

readWrite na shopread na shopbackupclusterMonitorAplikaceshop_appDashboardshop_roZálohovací úlohabackup_jobMonitoringmonitormongodauth přes adminDatabáze shop
Každý konzument se připojuje jako vlastní uživatel

Stejná myšlenka v jiných databázích má jinou syntaxi a stejné odůvodnění: pokud provozuješ víc než jednu, pokrývají je uživatelé a oprávnění v MySQL a role a oprávnění v PostgreSQL.

Výpis uživatelů a kontrola, co smějí#

Čtyři příkazy odpoví na téměř každou otázku o tom, kdo existuje a co smí:

javascript
use adminshow users                                   // users in the current databasedb.getUsers()                                // the same, as documentsdb.getUser("shop_app", { showPrivileges: true })db.adminCommand({ usersInfo: { forAllDBs: true } })   // every user on the server

show users a db.getUsers() vypisují jen uživatele, jejichž autentizační databáze je ta, ve které právě jsi. Pokud vytváříš uživatele ve více databázích, jediný úplný výpis dává varianta s forAllDBs a vyžaduje uživatele s právem zobrazovat uživatele napříč serverem.

showPrivileges: true rozbalí role uživatele na skutečné akce, které dávají, včetně rolí zděděných z jiných rolí, pod inheritedPrivileges. Je to dlouhé, ale je to odpověď na otázku „může tenhle uživatel smazat tu kolekci“ bez zkoušení na produkci.

Chceš-li zkontrolovat uživatele, jako kterého jsi právě připojen:

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

authInfo.authenticatedUsers ukazuje, kdo jsi a kde ses autentizoval; authenticatedUserRoles ukazuje role, které platí. Když aplikace dostane not authorized on shop to execute command, spuštění tohoto příkazu s přihlašovacími údaji aplikace v mongosh ti přesně řekne, co má, a je to rychlejší než číst kód, který ji vytvořil.

Změna hesel a rolí a odebrání uživatelů#

Uživatelé se během života mění a každá změna má svého pomocníka:

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")

Spouštěj je v autentizační databázi uživatele - changeUserPassword v shop pro uživatele, který žije v admin, selže s User shop_app@shop not found, což je stejná chyba jako špatný authSource, jen viděná z druhé strany.

db.updateUser() s polem roles nahradí celé pole, místo aby k němu přidávalo. Předání jedné role „na přidání“ tiše odebere všechny ostatní role, které uživatel měl. Pro postupné změny používej grantRolesToUser a revokeRolesFromUser a updateUser si nech pro customData, authenticationRestrictions a úplné přepsání, které jsi myslel vážně.

Změny rolí se na samostatném mongod projeví okamžitě, i pro již otevřená připojení. V shardovaném clusteru mongos ukládá informace o uživatelích do cache a obnovuje je po userCacheInvalidationIntervalSecs, ve výchozím stavu 30 sekund.

Rotace hesla aplikace bez výpadku

Změna hesla uživatele, kterého běžící aplikace používá, rozbije nová připojení v okamžiku, kdy to uděláš. Stávající připojení v poolu zůstanou autentizovaná, takže porucha se projeví později a vypadá náhodně. Čistý postup jsou dva uživatelé:

  1. Vytvoř shop_app_2 s úplně stejnými rolemi jako shop_app.
  2. Vlož nového uživatele a heslo do prostředí aplikace a aplikaci restartuj nebo znovu nasaď.
  3. Sleduj db.currentOp() nebo log serveru, dokud se nikdo nepřihlašuje jako shop_app.
  4. db.dropUser("shop_app").

Při příští rotaci jdi opačným směrem. Stojí to jednoho uživatele navíc na pár minut a úplně to zabrání napůl rozbitému stavu. Kde mají přihlašovací údaje během toho být uloženy, popisují proměnné prostředí a secrets.

Uživatelé mění vlastní heslo

Žádná vestavěná role pod userAdmin nezahrnuje změnu vlastního hesla, takže uživatel jen s readWrite nemůže na sobě spustit changeUserPassword. Pokud chceš, aby lidé rotovali vlastní přihlašovací údaje, přiděl malou vlastní roli s akcí changeOwnPassword, ukázanou v další části.

Vlastní role pro cokoli užšího#

Když žádná vestavěná role nesedí - worker, který smí vkládat do jedné kolekce a číst jinou, reportovací uživatel, který nesmí vidět kolekci customers - napiš roli. Role je seznam oprávnění, každé je zdroj plus akce, a případně dalších rolí, které dědí:

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" } ])

Ten worker může číst produkty, číst, přidávat a měnit objednávky a nic víc: žádné remove, žádné dropCollection, žádný přístup k jiným kolekcím.

Tvary zdrojů, které stojí za to znát:

ZdrojVýznam
{ db: "shop", collection: "orders" }Jedna kolekce
{ db: "shop", collection: "" }Každá nesystémová kolekce v shop
{ db: "", collection: "orders" }orders v jakékoli databázi
{ cluster: true }Akce pro celý server, například serverStatus

Dvě pravidla o tom, kde role žijí. Role vytvořená v běžné databázi může přidělovat oprávnění jen na této databázi. Role vytvořená v admin může přidělovat oprávnění na jakékoli databázi i na clusteru, takže role přes více databází patří do admin. A na roli se odkazuje jménem a databází dohromady, přesně jako u uživatele - ordersWriter v shop je { role: "ordersWriter", db: "shop" }.

Několik akcí pokrývá většinu vlastních rolí: find, insert, update, remove pro data; createIndex, dropIndex, createCollection, dropCollection pro strukturu; listCollections, listIndexes, collStats, dbStats pro nástroje, které zkoumají; changeOwnPassword a changeOwnCustomData pro samoobsluhu. Úplný seznam je na stránce o privilege actions v manuálu MongoDB a vyplatí se tam zkontrolovat, než hádat, protože špatně napsaná akce se odmítne už při createRole, ale chybějící se projeví až ve chvíli, kdy na ni aplikace narazí.

Role pro samoobslužnou změnu hesla zmíněná výše:

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

Zdrojem je autentizační databáze uživatele. Přiděl ji vedle skutečné role uživatele a bude moci spustit db.changeUserPassword() sám na sobě a na nikom jiném.

Role kontroluj a spravuj pomocí db.getRoles({ showPrivileges: true }), db.getRole("ordersWriter", { showPrivileges: true }), db.grantPrivilegesToRole(), db.revokePrivilegesFromRole() a db.dropRole(). Změna role změní najednou všechny uživatele, kteří ji mají, což je smysl věci - a také důvod, proč role pojmenovávat podle úlohy, ne podle člověka.

Chyby, na které narazíš#

`Authentication failed` (kód 18). Obvykle správné heslo ověřované proti špatné autentizační databázi. Spusť jako admin db.adminCommand({ usersInfo: { forAllDBs: true } }), podívej se na pole db u uživatele a pak podle něj nastav authSource.

`not authorized on shop to execute command { ... }` (kód 13). Autentizace proběhla, ale role akci nezahrnují. Hláška jmenuje příkaz; connectionStatus s showPrivileges ukáže, co uživatel opravdu má. Přiděl nejužší roli, která ji přidá, ne dbOwner.

`command createUser requires authentication`. Jsi připojen anonymně k serveru se zapnutou autorizací. Připoj se znovu s přihlašovacími údaji.

Uživatel už existuje, nebo uživatel nenalezen. use před příkazem tě přesunul do jiné databáze, než sis myslel, takže MongoDB hledal shop_app@shop místo shop_app@admin. Pomůže spustit db.getName() před příkazy pro uživatele.

`Could not find role: ordersWriter@admin`. Odkázal ses na vlastní roli jménem v databázi, kde nebyla vytvořena. Uveď úplný dokument { role, db } s databází, ve které role žije. Stejná chyba se objeví u vestavěné role se špatnou velikostí písmen: názvy jsou readWrite, dbAdmin, userAdmin a clusterMonitor a readwrite neexistuje.

U hostované MongoDB#

Na serveru, který si pronajímáš a neinstaluješ, je autentizace už zapnutá a první přihlašovací údaje dostaneš hotové - na RE:NODE se generují pro každý server a zobrazují v panelu vedle hostitele a portu, takže neexistuje sdílená výchozí hodnota, kterou by bylo třeba měnit. Ber ten účet jako superuživatele, kterým je: použij ho k vytvoření uživatelů pro jednotlivé úlohy popsaných výše, ulož ho do správce hesel a nedávej ho do prostředí aplikace.

Spravované clustery někdy správu uživatelů z shellu úplně odebírají. MongoDB Atlas například spravuje databázové uživatele přes vlastní rozhraní a API místo db.createUser(), ačkoli role jsou stejné koncepty. Na samostatném serveru, ke kterému se připojuješ přes hostitele a port, funguje vše v tomto návodu tak, jak je napsáno.

Uživatelé a role jsou uloženi uvnitř databáze, takže jsou součástí toho, co musí úplná záloha zachytit. Zálohování celého serveru pomocí mongodump s rolí backup nebo kopie datového adresáře na úrovni souborového systému zahrnuje admin.system.users; záloha jedné databáze uživatele nezahrnuje, pokud o ně nepožádáš. Zálohy a obnova databází popisuje, jaký druh zálohy zvolit pro jakou katastrofu.

FAQ#

V jaké databázi mám vytvářet uživatele MongoDB?

Funguje buď admin, nebo databáze samotné aplikace a uživatel v kterékoli z nich může mít role na jiných databázích. admin drží všechny uživatele na jednom místě s authSource=admin všude; uživatel ve vlastní databázi drží účty každé aplikace vedle jejích dat. Vyber si jednu konvenci a používej ji důsledně, protože authSource jí musí odpovídat.

Jaký je rozdíl mezi readWrite a dbOwner?

readWrite dovoluje uživateli číst a měnit data a spravovat kolekce a indexy. dbOwner přidává dbAdmin a userAdmin, což znamená, že může také smazat databázi a vytvářet uživatele s libovolnou rolí na ní. Aplikace potřebuje první a nikdy by neměla mít druhou.

Jak zobrazím všechny uživatele MongoDB na serveru?

Spusť db.adminCommand({ usersInfo: { forAllDBs: true } }) jako uživatel s oprávněním zobrazovat uživatele. show users vypíše jen uživatele vytvořené v databázi, ve které právě jsi, takže vypadá neúplně, pokud máš uživatele na více místech.

Můžu uživateli dát přístup jen pro čtení jen k některým kolekcím?

Vestavěnou rolí ne - read pokrývá každou nesystémovou kolekci v databázi. Vytvoř vlastní roli s find na každé kolekci, kterou chceš zviditelnit, a přiděl ji místo toho. Kolekce, které nejsou uvedeny, zůstanou pro dotazy tohoto uživatele neviditelné.

Vyžadují změny rolí restart nebo opětovné připojení?

Ne. Na samostatném mongod se přidělení nebo odebrání role projeví okamžitě, i u již otevřených připojení. V shardovaném clusteru to může mongos trvat až userCacheInvalidationIntervalSecs - ve výchozím stavu 30 sekund - než si změny všimne.

Stačí silné heslo, když má aplikační uživatel roli root?

Ne. Heslo chrání před hádáním, ne před tím, že aplikace bude oklamána a spustí dotaz, který neměla, nebo že unikne její soubor s prostředím. Omezení role omezuje škodu v obou případech, což je část, kterou heslo udělat nedokáže.

Pomohlo vám to?