Usuarios, permisos y privilegios: CREATE USER y GRANT
Lección 04 · Motores y ecosistema
Usuarios, permisos y privilegios: CREATE USER y GRANT
Hasta ahora hemos hablado con la base de datos como si fuéramos su único usuario. Pero en la vida real, contra un mismo servidor conectan muchas personas y muchas aplicaciones: la app web, el script de analítica, el equipo de soporte, el backup nocturno… y no todos deberían poder hacer lo mismo. A esta capa de SQL se la llama DCL (Data Control Language): el lenguaje que controla quién puede entrar y qué puede hacer.
Nota: esta lección es teórica a propósito. El playground del curso usa SQLite, que no tiene usuarios ni permisos: es una base embebida, sin servidor, y quien abre el archivo es el “dueño”.
CREATE USERyGRANTexisten en motores cliente-servidor como MySQL y PostgreSQL. Puedes practicarlos gratis instalando MySQL con Docker, tal y como vimos en la lección anterior.
Crear un usuario con contraseña
En MySQL, los usuarios se identifican con la forma 'nombre'@'desde_dónde': no solo importa quién eres, sino desde qué equipo te conectas.
CREATE USER 'analista'@'localhost' IDENTIFIED BY 'MiContraseña#2024';
CREATE USER 'app_web'@'10.0.0.%' IDENTIFIED BY 'OtraClave!Segura1';
'analista'@'localhost'solo puede conectarse desde la propia máquina del servidor.'app_web'@'10.0.0.%'puede conectarse desde cualquier equipo de la red10.0.0.x(el%es el comodín, como enLIKE).
Para cambiar una contraseña:
ALTER USER 'analista'@'localhost' IDENTIFIED BY 'NuevaClave#2025';
Las contraseñas nunca viajan en claro. Cuando ejecutas
IDENTIFIED BY '...', el motor guarda un hash, no el texto. Y cuando tu aplicación se conecta, la contraseña no debe estar escrita en el código: va en variables de entorno o en un gestor de secretos.
Asignar privilegios con GRANT
Crear el usuario solo le da permiso para conectarse. Lo que puede hacer una vez dentro se define con GRANT:
GRANT SELECT ON tienda.products TO 'analista'@'localhost';
Con esto, analista puede leer la tabla products de la base de datos tienda… y nada más. No puede insertar, ni borrar, ni tocar otras tablas.
Los privilegios se pueden dar a distintos niveles de alcance:
| Sintaxis | Alcance |
|---|---|
GRANT ... ON *.* TO ... |
Todo el servidor (todas las bases, todas las tablas) |
GRANT ... ON tienda.* TO ... |
Una base de datos completa |
GRANT ... ON tienda.products TO ... |
Una tabla concreta |
Y los privilegios más comunes son los que ya conoces del curso:
| Privilegio | Qué permite |
|---|---|
SELECT |
Leer datos |
INSERT |
Añadir filas |
UPDATE |
Modificar filas |
DELETE |
Eliminar filas |
CREATE / DROP |
Crear o eliminar tablas y bases |
ALTER |
Cambiar estructuras |
ALL PRIVILEGES |
Todo (reservado para administradores) |
Se pueden combinar varios en una sola sentencia:
GRANT SELECT, INSERT, UPDATE ON tienda.* TO 'app_web'@'10.0.0.%';
Fíjate en el patrón: la aplicación web puede escribir datos, pero el analista solo puede leerlos. Cada quien tiene lo suyo.
Quitar privilegios con REVOKE
REVOKE es la operación inversa de GRANT:
REVOKE INSERT, UPDATE ON tienda.* FROM 'app_web'@'10.0.0.%';
Y para eliminar un usuario por completo:
DROP USER 'analista'@'localhost';
Para auditar qué puede hacer un usuario:
SHOW GRANTS FOR 'analista'@'localhost';
Roles: privilegios empaquetados
Asignar privilegios uno a uno se vuelve inmanejable en cuanto tienes varios usuarios con el mismo perfil. La solución son los roles: un paquete de privilegios con nombre, que luego se asigna a los usuarios.
-- MySQL 8+ y PostgreSQL
CREATE ROLE 'lector_ventas';
GRANT SELECT ON ventas.* TO 'lector_ventas';
GRANT 'lector_ventas' TO 'analista'@'localhost';
GRANT 'lector_ventas' TO 'becario'@'localhost';
Si mañana el perfil cambia, actualizas el rol una vez y afecta a todos sus miembros. En PostgreSQL esto está integrado desde hace años (de hecho, todo es un rol allí, incluso los usuarios); en MySQL llegó con la versión 8.
Qué soporta cada motor
| Capacidad | MySQL | PostgreSQL | SQLite |
|---|---|---|---|
| Usuarios con contraseña | ✅ CREATE USER ... IDENTIFIED BY |
✅ CREATE USER ... PASSWORD |
❌ No existen |
GRANT / REVOKE |
✅ | ✅ | ❌ |
| Roles | ✅ (desde la v8) | ✅ (todo es un rol) | ❌ |
| Modelo | Cliente-servidor | Cliente-servidor | Archivo embebido |
En SQLite la seguridad vive fuera de la base de datos: en los permisos del archivo y del proceso que lo abre. Por eso el playground del curso puede ejecutar tu SQL sin cuentas ni contraseñas.
Buenas prácticas: el principio de mínimo privilegio
- Cada aplicación, su usuario. Nada de que la app web se conecte como
root. Si la comprometen, el atacante hereda todos los privilegios. - Mínimo privilegio. Dale solo lo que necesita: si una parte del código solo lee, que su usuario solo tenga
SELECT. - Contraseñas fuera del código. Variables de entorno o gestor de secretos, nunca
password: "..."en el repositorio. - Distinto usuario para desarrollo y producción. Y rota las contraseñas cuando alguien deja el equipo (o mejor: usa roles y solo reasignas).
- Audita de vez en cuando.
SHOW GRANTSte dice en segundos si un usuario acumuló permisos que ya no necesita.
En resumen
- La capa de seguridad de SQL se llama DCL:
CREATE USER,GRANT,REVOKE,DROP USER. - En MySQL los usuarios son
'nombre'@'host'; la contraseña se guarda como hash, nunca en claro. GRANTasigna privilegios (SELECT,INSERT, …) a niveles de servidor, base o tabla;REVOKElos quita.- Los roles empaquetan privilegios para asignarlos a muchos usuarios de una vez.
- SQLite no tiene usuarios ni permisos: es embebida, y la seguridad vive fuera de la base.
- La regla de oro es el principio de mínimo privilegio: cada quien, solo lo que necesita.