La seguridad de la base de datos es importante para cualquier configuración de MySQL. Los usuarios son la base de cualquier sistema. En términos de sistemas de bases de datos, generalmente los considero en dos grupos distintos:
- Usuarios de aplicaciones, servicios o programas - básicamente clientes o clientes que usan un servicio.
- Desarrolladores de bases de datos, administradores, analistas, etc... - Aquellos que mantienen, trabajan o monitorean la infraestructura de la base de datos.
Si bien cada usuario necesita acceder a la base de datos en algún nivel, esos permisos no son todos iguales.
Por ejemplo, los clientes y clientes necesitan acceso a los datos de su 'cuenta de usuario relacionada', pero incluso eso debe monitorearse con cierto nivel de control. Sin embargo, algunas tablas y datos deben estar estrictamente prohibidos (por ejemplo, tablas del sistema).
Sin embargo:
- El analista necesita 'acceso de lectura ', para obtener información y conocimientos a través de tablas de consulta...
- Los desarrolladores necesitan una gran cantidad de permisos y privilegios para realizar su trabajo...
- Los DBA necesitan privilegios de 'raíz' o de tipo similar para ejecutar el programa...
- Los compradores de un servicio necesitan ver su historial de pedidos y pagos...
Puede imaginar (sé que lo hago) cuán difícil es una tarea de administrar múltiples usuarios o grupos de usuarios dentro de un ecosistema de base de datos.
En versiones anteriores de MySQL, un entorno de múltiples usuarios se establece de una manera un tanto monótona y repetitiva.
Sin embargo, la versión 8 implementa una función estándar de SQL excepcional y poderosa - Roles - que alivia una de las áreas más redundantes de todo el proceso:asignar privilegios a un usuario.
Entonces, ¿qué es un rol en MySQL?
Seguramente puede visitar MySQL en 2018:¿Qué hay en 8.0 y otras observaciones? Escribí para el blog de Variousnines aquí, donde menciono roles para una descripción general de alto nivel. Sin embargo, donde solo los resumí allí, esta publicación actual busca profundizar y centrarse únicamente en los roles.
Así es como la documentación en línea de MySQL define un rol:"Un rol de MySQL es una colección de privilegios con nombre".
¿No parece útil esa definición por sí sola?
¿Pero cómo?
Lo veremos en los ejemplos que siguen.
Para tomar nota de los ejemplos proporcionados
Los ejemplos incluidos en esta publicación se encuentran en una estación de trabajo/entorno personal de desarrollo y aprendizaje de "usuario único", así que asegúrese de implementar las mejores prácticas que lo beneficien para sus necesidades o requisitos particulares. Los nombres de usuario y contraseñas demostrados son puramente arbitrarios y débiles.
Usuarios y privilegios en versiones anteriores
En MySQL 5.7, los roles no existen. La asignación de privilegios a los usuarios se realiza individualmente. Para comprender mejor qué roles proporcionan, no los usemos. Eso no tiene ningún sentido en absoluto, lo sé. Pero, a medida que avancemos en la publicación, lo hará.
A continuación creamos algunos usuarios:
CREATE USER 'reader_1'@'localhost' IDENTIFIED BY 'some_password';
CREATE USER 'reader_writer'@'localhost' IDENTIFIED BY 'another_password';
CREATE USER 'changer_1'@'localhost' IDENTIFIED BY 'a_password';
Luego, a esos usuarios se les otorgan algunos privilegios:
GRANT SELECT ON some_db.specific_table TO 'reader_1'@'localhost';
GRANT SELECT, INSERT ON some_db.specific_table TO 'reader_writer'@'localhost';
GRANT UPDATE, DELETE ON some_db.specific_table TO 'changer_1'@'localhost';
Vaya, me alegro de que haya terminado. Ahora volvamos a…
Y así, tiene una solicitud para implementar dos usuarios más de 'solo lectura'...
De vuelta a la mesa de dibujo:
CREATE USER 'reader_2'@'localhost' IDENTIFIED BY 'password_2';
CREATE USER 'reader_3'@'localhost' IDENTIFIED BY 'password_3';
Asignandoles privilegios también:
GRANT SELECT ON some_db.specific_table TO 'reader_2'@'localhost';
GRANT ALL ON some_db.specific_table TO 'reader_3'@'localhost';
¿Puedes ver cómo esto es menos que productivo, lleno de repeticiones y propenso a errores? Pero, lo que es más importante, ¿te diste cuenta del error?
¡Bien por ti!
Al conceder privilegios a estos dos usuarios adicionales, accidentalmente otorgó TODOS los privilegios al nuevo usuario reader_3.
Ups.
Un error que cualquiera podría cometer.
Ingrese los roles de MySQL
Con roles, mucho de lo anterior sistemático la asignación y delegación de privilegios se puede simplificar .
La creación de usuarios básicamente sigue siendo la misma, pero la asignación de privilegios a través de roles difiere:
mysql> CREATE USER 'reader_1'@'localhost' IDENTIFIED BY 'some_password';
Query OK, 0 rows affected (0.19 sec)
mysql> CREATE USER 'reader_writer'@'localhost' IDENTIFIED BY 'another_password';
Query OK, 0 rows affected (0.22 sec)
mysql> CREATE USER 'changer_1'@'localhost' IDENTIFIED BY 'a_password';
Query OK, 0 rows affected (0.08 sec)
mysql> CREATE USER 'reader_2'@'localhost' IDENTIFIED BY 'password_2';
Query OK, 0 rows affected (0.28 sec)
mysql> CREATE USER 'reader_3'@'localhost' IDENTIFIED BY 'password_3';
Query OK, 0 rows affected (0.12 sec)
Al consultar la tabla del sistema mysql.user, puede ver que existen esos usuarios recién creados:
(Nota:tengo varias cuentas de usuario en este entorno de aprendizaje/desarrollo y he suprimido gran parte de la salida para una mejor claridad en pantalla).
mysql> SELECT User FROM mysql.user;
+------------------+
| User |
+------------------+
| changer_1 |
| mysql.infoschema |
| mysql.session |
| mysql.sys |
| reader_1 |
| reader_2 |
| reader_3 |
| reader_writer |
| root |
| | --multiple rows remaining here...
+------------------+
23 rows in set (0.00 sec)
Tengo esta tabla arbitraria y datos de muestra:
mysql> SELECT * FROM name;
+--------+------------+
| f_name | l_name |
+--------+------------+
| Jim | Dandy |
| Johhny | Applesauce |
| Ashley | Zerro |
| Ashton | Zerra |
| Ashmon | Zerro |
+--------+------------+
5 rows in set (0.00 sec)
Ahora usemos roles para establecer y asignar privilegios para que los nuevos usuarios usen la tabla de nombres.
Primero, crea los roles:
mysql> CREATE ROLE main_read_only;
Query OK, 0 rows affected (0.11 sec)
mysql> CREATE ROLE main_read_write;
Query OK, 0 rows affected (0.11 sec)
mysql> CREATE ROLE main_changer;
Query OK, 0 rows affected (0.14 sec)
Observe la tabla mysql.user nuevamente:
mysql> SELECT User FROM mysql.user;
+------------------+
| User |
+------------------+
| main_changer |
| main_read_only |
| main_read_write |
| changer_1 |
| mysql.infoschema |
| mysql.session |
| mysql.sys |
| reader_1 |
| reader_2 |
| reader_3 |
| reader_writer |
| root |
| |
+------------------+
26 rows in set (0.00 sec)
Con base en esta salida, podemos suponer; que, en esencia, los roles son, de hecho, los propios usuarios.
A continuación, asignación de privilegios:
mysql> GRANT SELECT ON practice.name TO 'main_read_only';
Query OK, 0 rows affected (0.14 sec)
mysql> GRANT SELECT, INSERT ON practice.name TO 'main_read_write';
Query OK, 0 rows affected (0.07 sec)
mysql> GRANT UPDATE, DELETE ON practice.name TO 'main_changer';
Query OK, 0 rows affected (0.16 sec)
Un breve interludio
Espera un minuto. ¿Puedo simplemente iniciar sesión y realizar cualquier tarea con las propias cuentas de rol? Después de todo, son usuarios y tienen los privilegios necesarios.
Intentemos iniciar sesión en la base de datos de práctica con el rol main_changer:
:~$ mysql -u main_changer -p practice
Enter password:
ERROR 1045 (28000): Access denied for user 'main_changer'@'localhost' (using password: YES
El simple hecho de que se nos presente una solicitud de contraseña es una buena indicación de que no podemos (al menos en este momento). Como recordará, no establecí una contraseña para ninguno de los roles durante su creación.
¿Qué tiene que decir la columna authentication_string de las tablas del sistema mysql.user?
mysql> SELECT User, authentication_string, password_expired
-> FROM mysql.user
-> WHERE User IN ('main_read_only', 'root', 'main_read_write', 'main_changer')\G
*************************** 1. row ***************************
User: main_changer
authentication_string:
password_expired: Y
*************************** 2. row ***************************
User: main_read_only
authentication_string:
password_expired: Y
*************************** 3. row ***************************
User: main_read_write
authentication_string:
password_expired: Y
*************************** 4. row ***************************
User: root
authentication_string: ***various_jumbled_mess_here*&&*&*&*##
password_expired: N
4 rows in set (0.00 sec)
Incluí el usuario raíz entre los nombres de roles para la verificación de predicado IN() para simplemente demostrar que tiene una cadena_autenticación, donde los roles no la tienen.
Este pasaje en la documentación CREATE ROLE lo aclara muy bien:"Una función cuando se crea está bloqueada, no tiene contraseña y se le asigna el complemento de autenticación predeterminado. (Estos atributos de función se pueden cambiar más tarde con la declaración ALTER USER, por usuarios que tienen la privilegio global CREAR USUARIO.)"
Volviendo a la tarea en cuestión, ahora podemos asignar las funciones a los usuarios en función del nivel de privilegios que necesiten.
Observe que no hay una cláusula ON presente en el comando:
mysql> GRANT 'main_read_only' TO 'reader_1'@'localhost', 'reader_2'@'localhost', 'reader_3'@'localhost';
Query OK, 0 rows affected (0.13 sec)
mysql> GRANT 'main_read_write' TO 'reader_writer'@'localhost';
Query OK, 0 rows affected (0.16 sec)
mysql> GRANT 'main_changer', 'main_read_only' TO 'changer_1'@'localhost';
Query OK, 0 rows affected (0.13 sec)
Puede ser menos confuso si usa algún tipo de 'convención de nomenclatura ' al establecer los nombres de los roles (no sé si MySQL proporciona uno en este momento... ¿Comunidad?) aunque solo sea para diferenciarlos visualmente de los usuarios regulares 'sin roles'.
Aún queda trabajo por hacer
Eso fue súper fácil, ¿no?
Menos redundante que la antigua forma de asignación de privilegios.
Ahora pongamos a esos usuarios a trabajar.
Podemos ver los privilegios otorgados para un usuario con sintaxis SHOW GRANTS. Esto es lo que está actualmente asignado a la cuenta de usuario reader_1:
mysql> SHOW GRANTS FOR 'reader_1'@'localhost';
+------------------------------------------------------+
| Grants for [email protected] |
+------------------------------------------------------+
| GRANT USAGE ON *.* TO `reader_1`@`localhost` |
| GRANT `main_read_only`@`%` TO `reader_1`@`localhost` |
+------------------------------------------------------+
2 rows in set (0.02 sec)
Aunque eso proporciona un resultado informativo, puede 'afinar ' la instrucción para obtener información aún más granular sobre los privilegios exactos que proporciona una función asignada al incluir una cláusula USING en la instrucción SHOW GRANTS y nombrar las funciones asignadas:
mysql> SHOW GRANTS FOR 'reader_1'@'localhost' USING 'main_read_only';
+-------------------------------------------------------------+
| Grants for [email protected] |
+-------------------------------------------------------------+
| GRANT USAGE ON *.* TO `reader_1`@`localhost` |
| GRANT SELECT ON `practice`.`name` TO `reader_1`@`localhost` |
| GRANT `main_read_only`@`%` TO `reader_1`@`localhost` |
+-------------------------------------------------------------+
3 rows in set (0.00 sec)
Después de iniciar sesión con reader_1:
mysql> SELECT * FROM practice.name;
ERROR 1142 (42000): SELECT command denied to user 'reader_1'@'localhost' for table 'name'
¿Que demonios? A ese usuario se le otorgaron privilegios SELECT a través del rol main_read_only.
Para investigar, visitemos 2 tablas nuevas en la versión 8, específicamente para roles.
La tabla mysql.role_edges muestra qué roles se han otorgado a cualquier usuario:
mysql> SELECT * FROM mysql.role_edges;
+-----------+-----------------+-----------+---------------+-------------------+
| FROM_HOST | FROM_USER | TO_HOST | TO_USER | WITH_ADMIN_OPTION |
+-----------+-----------------+-----------+---------------+-------------------+
| % | main_changer | localhost | changer_1 | N |
| % | main_read_only | localhost | changer_1 | N |
| % | main_read_only | localhost | reader_1 | N |
| % | main_read_only | localhost | reader_2 | N |
| % | main_read_only | localhost | reader_3 | N |
| % | main_read_write | localhost | reader_writer | N |
+-----------+-----------------+-----------+---------------+-------------------+
6 rows in set (0.00 sec)
Pero creo que la otra tabla adicional, mysql.default_roles, nos ayudará mejor a resolver los problemas de SELECCIÓN para el usuario lector_1:
mysql> DESC mysql.default_roles;
+-------------------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+----------+------+-----+---------+-------+
| HOST | char(60) | NO | PRI | | |
| USER | char(32) | NO | PRI | | |
| DEFAULT_ROLE_HOST | char(60) | NO | PRI | % | |
| DEFAULT_ROLE_USER | char(32) | NO | PRI | | |
+-------------------+----------+------+-----+---------+-------+
4 rows in set (0.00 sec)
mysql> SELECT * FROM mysql.default_roles;
Empty set (0.00 sec)
Conjunto de resultados vacío.
Resulta que, para que un usuario pueda usar un rol - y, en última instancia, los privilegios, se le debe asignar un rol predeterminado.
mysql> SET DEFAULT ROLE main_read_only TO 'reader_1'@'localhost', 'reader_2'@'localhost', 'reader_3'@'localhost';
Query OK, 0 rows affected (0.11 sec)
(Se puede asignar una función predeterminada a varios usuarios en un solo comando como se indica arriba...)
mysql> SET DEFAULT ROLE main_read_only, main_changer TO 'changer_1'@'localhost';
Query OK, 0 rows affected (0.10 sec)
(Un usuario puede tener varios roles predeterminados especificados como en el caso del usuario changer_1...)
El usuario lector_1 ahora está conectado...
mysql> SELECT CURRENT_USER();
+--------------------+
| CURRENT_USER() |
+--------------------+
| [email protected] |
+--------------------+
1 row in set (0.00 sec)
mysql> SELECT CURRENT_ROLE();
+----------------------+
| CURRENT_ROLE() |
+----------------------+
| `main_read_only`@`%` |
+----------------------+
1 row in set (0.03 sec)
Podemos ver el rol actualmente activo y también, que reader_1 puede emitir comandos SELECT ahora:
mysql> SELECT * FROM practice.name;
+--------+------------+
| f_name | l_name |
+--------+------------+
| Jim | Dandy |
| Johhny | Applesauce |
| Ashley | Zerro |
| Ashton | Zerra |
| Ashmon | Zerro |
+--------+------------+
5 rows in set (0.00 sec)
Otros matices ocultos
Hay otra parte importante del rompecabezas tenemos que entender.
Hay potencialmente 3 'niveles' o 'variantes' diferentes de asignación de roles:
SET ROLE …;
SET DEFAULT ROLE …;
SET ROLE DEFAULT …;
OTORGARÉ un rol adicional al usuario lector_1 y luego iniciaré sesión con ese usuario (no se muestra):
mysql> GRANT 'main_read_write' TO 'reader_1'@'localhost';
Query OK, 0 rows affected (0.17 sec)
Dado que el rol main_read_write tiene el privilegio INSERT, el usuario reader_1 ahora puede ejecutar ese comando, ¿verdad?
mysql> INSERT INTO name(f_name, l_name)
-> VALUES('Josh', 'Otwell');
ERROR 1142 (42000): INSERT command denied to user 'reader_1'@'localhost' for table 'name'
¿Qué está pasando aquí?
Esto puede ayudar...
mysql> SELECT CURRENT_ROLE();
+----------------------+
| CURRENT_ROLE() |
+----------------------+
| `main_read_only`@`%` |
+----------------------+
1 row in set (0.00 sec)
Recuerde, inicialmente configuramos al usuario reader_1 como un rol predeterminado de main_read_only. Aquí es donde necesitamos usar uno de esos 'niveles' distintos de lo que término vagamente 'configuración de roles':
mysql> SET ROLE main_read_write;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT CURRENT_ROLE();
+-----------------------+
| CURRENT_ROLE() |
+-----------------------+
| `main_read_write`@`%` |
+-----------------------+
1 row in set (0.00 sec)
Ahora intenta INSERTAR de nuevo:
mysql> INSERT INTO name(f_name, l_name)
-> VALUES('Josh', 'Otwell');
Query OK, 1 row affected (0.12 sec)
Sin embargo, una vez que el usuario lector_1 vuelve a cerrar sesión, el rol main_read_write ya no estará activo cuando lector_1 vuelva a iniciar sesión. Aunque el usuario lector_1 tiene el rol main_read_write otorgado, no es el predeterminado.
Pasemos ahora a conocer el tercer 'nivel' de 'configuración de roles', ESTABLECER ROL PREDETERMINADO.
Supongamos que el usuario lector_1 aún no tiene roles asignados:
mysql> SHOW GRANTS FOR 'reader_1'@'localhost';
+----------------------------------------------+
| Grants for [email protected] |
+----------------------------------------------+
| GRANT USAGE ON *.* TO `reader_1`@`localhost` |
+----------------------------------------------+
1 row in set (0.00 sec)
OTORGUEMOS a este usuario 2 roles:
mysql> GRANT 'main_changer', 'main_read_write' TO 'reader_1'@'localhost';
Query OK, 0 rows affected (0.07 sec)
Asigne una función predeterminada:
mysql> SET DEFAULT ROLE ‘main_changer’ TO 'reader_1'@'localhost';
Query OK, 0 rows affected (0.17 sec)
Luego, con el usuario lector_1 conectado, ese rol predeterminado está activo:
mysql> SELECT CURRENT_ROLE();
+--------------------+
| CURRENT_ROLE() |
+--------------------+
| `main_changer`@`%` |
+--------------------+
1 row in set (0.00 sec)
Ahora cambia al rol main_read_write:
mysql> SET ROLE 'main_read_write';
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT CURRENT_ROLE();
+-----------------------+
| CURRENT_ROLE() |
+-----------------------+
| `main_read_write`@`%` |
+-----------------------+
1 row in set (0.00 sec)
Pero, para volver al rol predeterminado asignado, use SET ROLE DEFAULT como se muestra a continuación:
mysql> SET ROLE DEFAULT;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT CURRENT_ROLE();
+--------------------+
| CURRENT_ROLE() |
+--------------------+
| `main_changer`@`%` |
+--------------------+
1 row in set (0.00 sec)
Funciones no otorgadas
Aunque el usuario changer_1 tiene 2 roles disponibles durante una sesión:
mysql> SELECT CURRENT_ROLE();
+-----------------------------------------+
| CURRENT_ROLE() |
+-----------------------------------------+
| `main_changer`@`%`,`main_read_only`@`%` |
+-----------------------------------------+
1 row in set (0.00 sec)
¿Qué sucede si intenta asignar a un usuario una función que no se le ha otorgado?
mysql> SET ROLE main_read_write;
ERROR 3530 (HY000): `main_read_write`@`%` is not granted to `changer_1`@`localhost`
Denegado.
Para llevar
Ningún sistema de administración de usuarios estaría completo sin la capacidad de restringir o incluso eliminar el acceso a ciertas operaciones en caso de necesidad.
Tenemos a nuestra disposición el comando SQL REVOKE para eliminar privilegios de usuarios y roles.
Recuerde que el rol main_changer tiene este conjunto de privilegios, esencialmente, todos los usuarios a los que se les otorgó este rol también los tienen:
mysql> SHOW GRANTS FOR main_changer;
+-----------------------------------------------------------------+
| Grants for [email protected]% |
+-----------------------------------------------------------------+
| GRANT USAGE ON *.* TO `main_changer`@`%` |
| GRANT UPDATE, DELETE ON `practice`.`name` TO `main_changer`@`%` |
+-----------------------------------------------------------------+
2 rows in set (0.00 sec)
mysql> REVOKE DELETE ON practice.name FROM 'main_changer';
Query OK, 0 rows affected (0.11 sec)
mysql> SHOW GRANTS FOR main_changer;
+---------------------------------------------------------+
| Grants for [email protected]% |
+---------------------------------------------------------+
| GRANT USAGE ON *.* TO `main_changer`@`%` |
| GRANT UPDATE ON `practice`.`name` TO `main_changer`@`%` |
+---------------------------------------------------------+
2 rows in set (0.00 sec)
Para saber a qué usuarios afectó este cambio, podemos visitar de nuevo la tabla mysql.role_edges:
mysql> SELECT * FROM mysql.role_edges WHERE FROM_USER = 'main_changer';
+-----------+--------------+-----------+-----------+-------------------+
| FROM_HOST | FROM_USER | TO_HOST | TO_USER | WITH_ADMIN_OPTION |
+-----------+--------------+-----------+-----------+-------------------+
| % | main_changer | localhost | changer_1 | N |
+-----------+--------------+-----------+-----------+-------------------+
1 row in set (0.00 sec)
Y podemos ver que el usuario changer_1 ya no tiene el privilegio DELETE:
mysql> SHOW GRANTS FOR 'changer_1'@'localhost' USING 'main_changer';
+--------------------------------------------------------------------------+
| Grants for [email protected] |
+--------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `changer_1`@`localhost` |
| GRANT UPDATE ON `practice`.`name` TO `changer_1`@`localhost` |
| GRANT `main_changer`@`%`,`main_read_only`@`%` TO `changer_1`@`localhost` |
+--------------------------------------------------------------------------+
3 rows in set (0.00 sec)
Finalmente, si necesitamos deshacernos de un rol por completo, tenemos el comando DROP ROLE para eso:
mysql> DROP ROLE main_read_only;
Query OK, 0 rows affected (0.17 sec)
Y al consultar la tabla mysql.role_edges, se eliminó el rol main_read_only:
mysql> SELECT * FROM mysql.role_edges;
+-----------+-----------------+-----------+---------------+-------------------+
| FROM_HOST | FROM_USER | TO_HOST | TO_USER | WITH_ADMIN_OPTION |
+-----------+-----------------+-----------+---------------+-------------------+
| % | main_changer | localhost | changer_1 | N |
| % | main_read_write | localhost | reader_1 | N |
| % | main_read_write | localhost | reader_writer | N |
+-----------+-----------------+-----------+---------------+-------------------+
3 rows in set (0.00 sec)
(Extra:este fantástico video de YouTube fue un excelente recurso de aprendizaje para mí sobre los roles).
Este ejemplo de creación de usuarios, asignación de funciones y configuración es, en el mejor de los casos, rudimentario. Sin embargo, los roles tienen su propio conjunto de reglas que hacen que no sean triviales. Mi esperanza es que a través de esta publicación de blog, haya arrojado luz sobre aquellas áreas que son menos intuitivas que otras, permitiendo a los lectores comprender mejor los posibles usos de roles dentro de sus sistemas.
Gracias por leer.