MongoDB utiliza el control de acceso basado en roles (RBAC) para controlar el acceso y los privilegios en las bases de datos
Introducción
El control de acceso, también conocido como autorización, es una técnica de seguridad que consiste en determinar quién puede acceder a qué recursos.
Autenticación
En muchos sistemas de gestión de bases de datos, un usuario se identifica sólo con un nombre de usuario.
En cambio, en MongoDB un usuario no sólo se identifica por su nombre de usuario, sino también por la base de datos en la que se crea (que es como base de datos de autenticación de ese usuario).
Esto significa que en MongoDB es posible tener varios usuarios con el mismo nombre de usuario siempre que se creen en bases de datos de autenticación distintas. Para autenticarte como usuario, debes proporcionar no sólo un nombre de usuario y una contraseña, sino también el nombre de la base de datos de autenticación asociada a ese usuario.
Se podría suponer que los usuarios creados en una base de datos de autenticación determinada tendrían privilegios de acceso disponibles sólo para esa base de datos en particular, pero éste no es el caso. Cada usuario, independientemente de la base de datos de autenticación en la que se creó, puede tener privilegios asignados a diferentes bases de datos.
Autorización
En un control de acceso basado en roles, los usuarios no tienen permiso para realizar acciones directamente sobre los recursos, como insertar un documento nuevo en la base de datos o consultar una colección concreta, sino que las reglas que permiten acciones sobre recursos concretos se asignan a roles.
Los roles se definen con un conjunto de uno o varios privilegios. Cada privilegio consiste en una acción (como crear nuevos documentos, recuperar datos de un documento o crear y suprimir usuarios) y el recurso sobre el que se puede realizar esta acción (como una base de datos llamada reports
o una colección llamada orders
).
Los roles se identifican con la combinación del nombre del rol y la base de datos, ya que cada rol, excepto los creados en la base de datos admin
, sólo puede incluir privilegios que se aplican a su propia base de datos. Al conceder a un usuario roles definidos en una base de datos distinta a su base de datos de autenticación, se puede dar permiso a un usuario para actuar en más de una base de datos. Los roles se pueden conceder cuando creas un usuario o en cualquier momento después. La revocación de la pertenencia al rol también puede realizarse a voluntad, por lo que es sencillo desvincular la gestión de usuarios de la gestión de los derechos de acceso.
MongoDB proporciona un conjunto de roles integrados que describen privilegios que se utilizan habitualmente en sistemas de bases de datos, tales como read
para conceder acceso de sólo lectura, readWrite
para otorgar tanto permisos de lectura como de escritura o dbOwner
para conceder privilegios administrativos completos sobre una base de datos determinada. Para escenarios más específicos, también pueden crearse roles definidos por el usuario con conjuntos personalizados de privilegios.
El control de acceso basado en roles permite asignar a los usuarios sólo el nivel mínimo y preciso de permisos de acceso que necesitan para trabajar en sus respectivas tareas. Ésta es una práctica de seguridad importante conocida como principio de privilegios mínimos.
Entorno de trabajo
El control de acceso de MongoDB no está habilitado por defecto
Arranca la base de datos y crea una sesión:
> start-process -NoNewWindow mongodb
> mongosh
Crea el usuario administrador root
:
> use admin
switched to db admin
admin> db.createUser(
{
user: "root",
pwd: passwordPrompt(),
roles: [
{
role: "userAdminAnyDatabase",
db: "admin"
},
"readWriteAnyDatabase"
]
}
)
Enter password
********{ ok: 1 }
Para el servidor y sal del shell:
> db.adminCommand({ shutdown: 1 })
> exit
Vuelve a ejecutar mongod
, pero esta vez con el flag --auth
:
> start-process -NoNewWindow mongod --auth
Crea una nueva sesión con el usuario root
:
> mongosh.exe -u root
Enter password: ********
...
test>
Puedes ver que ahora ya no te avisa de que RBAC está deshabilitado.
Roles predefinidos
Para explicar cómo funciona el control de acceso basado en roles (RBAC, para abreviar) en la práctica utilizaremos de ejemplo una empresa de ventas que utiliza dos bases de datos.
-
La primera base de datos (llamada
sales
) almacenará datos sobre los pedidos de los clientes en la tienda de la empresa con dos colecciones separadas:customers
para los datos personales de sus clientes yorders
para los detalles del pedido. -
La segunda base de datos (llamada
reports
) almacenará informes agregados de ventas mensuales. Esta base de datos contendrá una única colección llamadareports
.
La empresa sólo tiene dos empleados, Laura y Roser.
Y estos son los permisos que crearás a continuación:
Usuari | sales |
reports |
admin |
---|---|---|---|
root |
readWriteAnyDatabase , admin:userAdminAnyDatabase |
||
laura |
readWrite |
||
roser |
read |
readWrite |
Cambia a la base de datos sales
:
> use sales
switched to db sales
Inserta un cliente en la colección customers
:
sales> db.customers.insertOne({name: "David"})
{
acknowledged: true,
insertedId: ObjectId('67882f9242d68a24b1cb0ce3')
}
Inserta una orden en la colección orders
:
sales> db.orders.insert({total: 100})
{
acknowledged: true,
insertedIds: { '0': ObjectId('67882fd442d68a24b1cb0ce4') }
}
Laura
A continuación crea el usuario "Laura" que trabaja en el departamento de ventas y necesita acceso completo a las dos colecciones de la base de datos sales
, pero no necesita trabajar con la base de datos reports
.
sales> db.createUser( {
user: "laura",
pwd: passwordPrompt(),
roles: [ {role : "readWrite", db: "sales" }]
})
Enter password
********{ ok: 1 }
Puedes confirmar que se ha añadido la "Laura" a la lista de usuarios de la base de datos sales
:
sales> show users
[
{
_id: 'sales.laura',
userId: UUID('f97f35ec-b6c9-4175-adef-7ddd1b3f47da'),
user: 'laura',
db: 'sales',
roles: [ { role: 'readWrite', db: 'sales' } ],
mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}
]
Cambia a la base de datos reports
:
sales> use reports
switched to db reports
Inserta un documento en la colección reports
:
> db.reports.insertOne({orders: 1})
{
acknowledged: true,
insertedId: ObjectId('678833800c36be4e5fcb0ce2')
}
Cierra la sesión y crea una nueva sesión con el usuario laura
:
> mongosh -u laura sales
...
Ejecuta el orden show dbs
para listar las bases de datos disponibles:
sales> show dbs
sales 80.00 KiB
A diferencia de su cuenta de administrador, sólo aparecerá una base de datos para la laura
, ya que sólo le has concedido acceso a la base de datos sales
.
Ahora compruebe si laura
puede recuperar objetos de ambas colecciones en la base de datos sales
:
sales> db.customers.find()
[ { _id: ObjectId('67882f9242d68a24b1cb0ce3'), name: 'David' } ]
sales> db.orders.find()
[ { _id: ObjectId('67882fd442d68a24b1cb0ce4'), total: 100 } ]
Para asegurarte de que los derechos de acceso a la base de datos sales
se han configurado correctamente, puedes comprobar si laura
también puede insertar nuevos documentos:
sales> db.customers.insertOne({name: "Maria"})
{
acknowledged: true,
insertedId: ObjectId('6789044acdca8d8d69cb0ce2')
}
Como has concedido a la laura
el rol readWrite
, está autorizado a escribir nuevos documentos en esta base de datos.
Finalmente, verifica que laura
no puede leer ni escribir ningún dato en la base de datos reports
, ya que no le concediste acceso a través de los roles asignados.
sales> use reports
switched to db reports
reports> db.reports.find()
MongoServerError[Unauthorized]: not authorized on reports to execute command { find: "reports", filter: {}, lsid: { id: UUID("461bf330-c3d1-4344-a350-d89e646843eb") }, $db: "reports" }
El mensaje de error Unauthorized
indica que laura
no tiene suficientes derechos de acceso para interactuar con los datos de la base de datos reports
.
Roser
Ahora debes crear la cuenta para roser
, la analista de ventas de la empresa.
La roser
necesita acceso de escritura a la base de datos reports
para crear informes, así como sólo acceso de lectura a la base de datos sales
para recuperar los datos.
Cambia a la base de datos admin
y auténticate como root
:
sales> use admin
switched to db admin
admin> db.auth("root")
Enter password
********{ ok: 1 }
Crea el usuario roser
en la base de datos reports
:
admin> use reports
switched to db reports
reports> db.createUser({
... user: "roser",
... pwd: passwordPrompt(),
... roles: [ { role: "readWrite", db: "reports"}, {role: "read", db: "sales"} ]
... })
Enter password
********{ ok: 1 }
Autentícate como el usuario roser
:
reports> db.auth("roser")
Enter password
********{ ok: 1 }
Ejecuta la orden show dbs
para listar las bases de datos disponibles para roser
:
reports> show dbs
reports 40.00 KiB
sales 112.00 KiB
Puesto que la roser
puede utilizar las bases de datos sales
y reports
, estas dos bases de datos se mostrarán en la salida.
Comprueba si la roser
puede recuperar objetos de la base de datos sales
:
reports> use sales
switched to db sales
sales> db.orders.find()
[ { _id: ObjectId('67882fd442d68a24b1cb0ce4'), total: 100 } ]
A continuación, puedes intentar insertar un documento nuevo en la colección orders
:
sales> db.orders.insertOne({total: 50})
MongoServerError[Unauthorized]: not authorized on sales to execute command { insert: "orders", documents: [ { total: 50, _id: ObjectId('67890a7d86d96a7e1dcb0ce2') } ], ordered: true, lsid: { id: UUID("f2005137-cf06-411a-8cae-5bd2e3d878c1") }, $db: "sales" }
Al asignar a roser
sólo el rol read
para esta base de datos, la orden insertOne
fallará con un mensaje de error.
A continuación, confirma si la roser
puede leer y escribir datos en la base de datos reports
:
sales> use reports
switched to db reports
reports> db.reports.find()
[ { _id: ObjectId('678833800c36be4e5fcb0ce2'), orders: 1 } ]
reports> db.reports.insertOne({orders:2})
{
acknowledged: true,
insertedId: ObjectId('67890b2086d96a7e1dcb0ce3')
}
Concesión y revocación de roles para usuarios existentes
En la práctica, los administradores de bases de datos a menudo necesitan revocar o conceder nuevos privilegios a los usuarios que ya se han creado en su sistema.
A continuación, auténticate como el usuario root
,
reports> use admin
switched to db admin
admin> db.auth("root")
Enter password
********{ ok: 1 }
Concede permiso de sólo lectura a la laura
en la base de datos reports
:
admin> use sales
switched to db sales
sales> db.grantRolesToUser("laura", [{role: "read", db: "reports"}])
{ ok: 1 }
Verifica los roles de la laura
:
sales> show users
[
{
_id: 'sales.laura',
userId: UUID('f97f35ec-b6c9-4175-adef-7ddd1b3f47da'),
user: 'laura',
db: 'sales',
roles: [
{ role: 'readWrite', db: 'sales' },
{ role: 'read', db: 'reports' }
],
mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}
]
Autentícate con el usuario laura
y verifica que puede acceder a la base de datos reports
después del cambio:
sales> db.auth("laura")
Enter password
********{ ok: 1 }
sales> use reports
switched to db reports
reports> db.reports.find()
[
{ _id: ObjectId('678833800c36be4e5fcb0ce2'), orders: 1 },
{ _id: ObjectId('67890b2086d96a7e1dcb0ce3'), orders: 2 }
]
Si al cabo de un tiempo quieres revocar la capacidad del usuario laura
para acceder a los informes, con el usuario root
elimina el permiso correspondiente:
...
sales> db.revokeRolesFromUser("laura",[ {role:"read", db:"reports"} ])
{ ok: 1 }
Verifica que la laura
ya no puede leer documentos de la base de datos reports
:
...
reports> db.reports.find()
MongoServerError[Unauthorized]: not authorized on reports to execute command { find: "reports", filter: {}, lsid: { id: UUID("55abd9f8-d8b1-479c-a7b2-cae6a4163505") }, $db: "reports" }
Roles definidos por el usuario
Mediante el método createRole
, puedes crear un rol según tus necesidades.
Añade algunos productos a la base de datos sales
:
sales> db.products.insertMany( [{"name":"orange"}, {"name": "apple"}, {"name":"banana"}] )
...
Cambia al usuario root
y crea un rol customer
en la base de datos sales
que pueda buscar y leer de la colección products
:
sales> db.createRole({
... role: "customer",
... privileges: [{
... resource: { db: "sales", collection: "products"},
... actions: [ "find"]
... }],
... roles: []
... })
{ ok: 1 }
Cuando añades un rol, creas el rol en una base de datos específica: MongoDB utiliza la combinación de la base de datos y el nombre del rol para definir de forma única un rol.
Más información en: User-Defined Roles on Self-Managed Deployments
En este enlace tienes la lista de acciones: Privilege Actions
Si miras la información de los roles de la base de datos sales
puedes ver que la propiedad isBuiltin
indica que el rol customer
está definido por el usuario.
sales> db.getRoles()
{
roles: [
{
_id: 'sales.customer',
role: 'customer',
db: 'sales',
roles: [],
isBuiltin: false,
inheritedRoles: []
}
],
ok: 1
}
Este rol lo utilizarás para añadir clientes que puedan consultar la lista de productos de nuestra empresa:
sales> db.customers.find()
[
{ _id: ObjectId('67882f9242d68a24b1cb0ce3'), name: 'David' },
{ _id: ObjectId('6789044acdca8d8d69cb0ce2'), name: 'Maria' }
]
Por ejemplo, añade a maria
como usuario de la base de datos sales
:
sales> db.createUser( {
... user: "maria",
... pwd: passwordPrompt(),
... roles: [ {role: "customer", db: "sales"} ]
... })
Enter password
********{ ok: 1 }
sales>
A continuación modifica el documento de Maria para registrar que tiene un usuario en la base de datos:
sales> db.customers.updateOne( {name: "Maria"}, { $set: {username: "maria"}} )
...
Ahora Maria puedes consultar la lista de nuestros productos, pero ¿cómo puede crear una orden de compra?
Crea un rol maria
con el que pueda realizar acciones find
y insert
en la colección orders_maria
:
> db.createRole({
... role: "maria",
... privileges: [{
... resource: {db: "sales", collection: "orders_maria"},
... actions: [ "find", "insert" ]
... }],
... roles: ["customer"]
... })
{ ok: 1 }
Añade el rol maria
al usuario maria
:
sales> db.grantRolesToUser("maria", [{role: "maria", db: "sales"}])
{ ok: 1 }
Como el rol maria
hereda los privilegios de customer
, puedes revocar el rol customer
al usuario maria
:
sales> db.revokeRolesFromUser("maria", [{ role:"customer", db: "sales"}])
{ ok: 1 }
Autentícate como el usuario maria
:
sales> db.auth("maria")
Enter password
********{ ok: 1 }
Verifica que el usuario maria
puede buscar productos e insertar órdenes en la colección orders_maria
:
sales> db.products.find()
[
{ _id: ObjectId('67893a9520c44545d1cb0ce2'), name: 'orange' },
{ _id: ObjectId('67893a9520c44545d1cb0ce3'), name: 'apple' },
{ _id: ObjectId('67893a9520c44545d1cb0ce4'), name: 'banana' }
]
sales> db.orders_maria.insertOne({product: "orange", quantity: 5, price: 1.23 })
{
acknowledged: true,
insertedId: ObjectId('678946b720c44545d1cb0ce5')
}
Verifica que el usuario maria
no puede insertar documentos en la colección orders
:
sales> db.orders.insertOne({product: "orange", quantity: 5, price: 1.23 })
MongoServerError[Unauthorized]: not authorized on sales to execute command { insert: "orders", documents: [ { product: "orange", quantity: 5, price: 1.23, _id: ObjectId('6789477120c44545d1cb0ce6') } ], ordered: true, lsid: { id: UUID("51dc4213-8d64-4c2f-b5a2-8dd5a9d838a4") }, $db: "sales" }
Inmutabilidad
Si te fijas, los permisos de un usuario customer no permiten las acciones update
o remove
en su colección de órdenes.
Esto es importante porque ¿cómo podemos seguir todas las órdenes de todos los clientes?
Debemos permitir que los usuarios con el rol de customer insertan órdenes en la colección ordres
, pero nada más.
TODO