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.

  1. 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 y orders para los detalles del pedido.

  2. La segunda base de datos (llamada reports) almacenará informes agregados de ventas mensuales. Esta base de datos contendrá una única colección llamada reports.

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 lauraen 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

TODO