Una política de permisos mínimos para usuarios (y roles) es fundamental para la seguridad de nuestros datos. Sin embargo, a menudo nos enfrentamos al dilema de cómo hacerlo sin sacrificar funcionalidad ni exponer en exceso nuestros objetos. Como decía, la necesidad de implementar un principio de privilegios mínimos es esencial en entornos seguros y controlados, especialmente en bases de datos con múltiples aplicaciones o equipos interactuando. Una técnica muy potente y poco utilizada en SQL Server es la firma de procedimientos almacenados con certificados. Hoy vamos a explorar cómo funciona y por qué puede marcar una diferencia importante en nuestras estrategias de seguridad.
Delegación segura con certificados
Cuando se habla de conceder permisos a través de procedimientos almacenados, una de las primeras aproximaciones suele ser el uso de EXECUTE AS. Aunque funcional, esta técnica presenta inconvenientes, especialmente cuando entra en juego el “chaining” de ejecución o cuando queremos evitar tener que gestionar contextos de ejecución elevados.
Ahí es donde entran los certificados. Firmar procedimientos almacenados nos permite encapsular permisos de forma precisa y segura sin cambiar el contexto de ejecución, sin depender de impersonaciones y sin debilitar el modelo de seguridad de la base de datos.
¿Cómo funciona la firma con certificados?
El procedimiento se basa en tres pasos fundamentales: creación del certificado, firma del procedimiento y asignación del permiso requerido a un login asociado al certificado. Cada uno de estos pasos es determinante para conseguir una delegación limpia y controlada.
Partimos de una situación donde un procedimiento necesita acceder a un objeto (por ejemplo, una tabla o vista), pero el usuario que lo ejecuta no tiene permiso directo sobre ese objeto. Esto normalmente no sería un problema salvo que el procedimiento y el objeto sean de un propietario distinto o que la consulta desde el procedimiento se ejecute en otro contexto de seguridad diferente, por ejemplo por usar sp_executesql. En estos casos, si firmamos el procedimiento con un certificado y asociamos el certificado a un login o usuario que sí tenga ese permiso, el procedimiento se ejecutará con esos privilegios, pero sin necesidad de cambiar el contexto de ejecución del usuario.
Caso práctico detallado
Supongamos que queremos que ciertos usuarios puedan ejecutar un procedimiento llamado usp_ObtenerVentasPrivadas, que accede a una tabla sensible como VentasPrivadas, pero sin darles acceso directo a dicha tabla. El procedimiento sería como el siguiente:
CREATE PROCEDURE dbo.usp_ObtenerVentasPrivadas
AS
BEGIN
SELECT * FROM dbo.VentasPrivadas;
END;
GO
Tal como está, cualquier usuario que tenga permiso de ejecución sobre usp_ObtenerVentasPrivadas podrá acceder a VentasPrivadas, pero sin tener acceso directo a ella ni posibilidad de usarla fuera de este procedimiento. Esto es porque el contexto de ejecución sigue siendo el del usuario original al no tener código dinámico y al pertenecer la tabla y el procedimiento al mismo esquema (con el mismo owner del esquema).
Cuando la cosa se complica
Pero, ¿qué pasa si cambiamos el contexto de ejecución o tenemos distinto owner? Aquí es donde entran en juego los certificados.
Yo para la demo que os pongo lo he simplificado y ejecuto una consulta simple pero, pongamos que tenemos un procedimiento que genera SQL dinámico y lo ejecuta con sp_executesql. Este sería el ejemplo que podeis reproducir vosotros mismos.
-- ============================================
-- DEMO: Firmar procedimientos con certificado
-- ============================================
-- Crear base de datos de prueba
USE master
GO
IF DB_ID('DemoCertificados') IS NOT NULL
BEGIN
ALTER DATABASE DemoCertificados SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE DemoCertificados;
END;
GO
CREATE DATABASE DemoCertificados;
GO
USE DemoCertificados;
GO
-- ========================
-- Limpieza previa (por si ya existe algo)
-- ========================
IF EXISTS (SELECT * FROM sys.database_principals WHERE name = 'UsuarioLimitado')
DROP USER UsuarioLimitado;
IF EXISTS (SELECT * FROM sys.database_principals WHERE name = 'UsuarioCertSeguridad')
DROP USER UsuarioCertSeguridad;
IF EXISTS (SELECT * FROM sys.certificates WHERE name = 'CertSeguridad')
DROP CERTIFICATE CertSeguridad;
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'usp_VerSeguridad')
DROP PROCEDURE dbo.usp_VerSeguridad;
IF OBJECT_ID('dbo.Seguridad') IS NOT NULL
DROP TABLE dbo.Seguridad;
GO
-- ========================
-- Crear tabla de ejemplo
-- ========================
CREATE TABLE dbo.Seguridad (
Id INT PRIMARY KEY,
InfoPrivada NVARCHAR(100)
);
-- ========================
-- Insertar datos de ejemplo
-- ========================
INSERT INTO dbo.Seguridad (Id, InfoPrivada)
VALUES (1, 'Datos internos'), (2, 'Contenido sensible');
GO
-- ========================
-- Crear usuario sin login (para simplificar la demo)
-- ========================
CREATE USER UsuarioLimitado WITHOUT LOGIN;
GO
-- ========================
-- Crear procedimiento con SQL dinámico
-- ========================
CREATE PROCEDURE dbo.usp_VerSeguridad
AS
BEGIN
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM dbo.Seguridad';
EXEC sp_executesql @sql;
END;
GO
GRANT EXECUTE ON dbo.usp_VerSeguridad TO UsuarioLimitado;
GO
-- ========================
-- Comprobación sin firma (debería fallar)
-- ========================
PRINT '--- Prueba SIN firma (esperado: error de permisos) ---';
EXECUTE AS USER = 'UsuarioLimitado';
BEGIN TRY
EXEC dbo.usp_VerSeguridad;
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
END CATCH
REVERT;
GO
-- ========================
-- Crear certificado y usuario asociado
-- ========================
CREATE CERTIFICATE CertSeguridad
ENCRYPTION BY PASSWORD = 'UnPasswordSegurísimo!'
WITH SUBJECT = 'Certificado para lectura controlada';
GO
CREATE USER UsuarioCertSeguridad FOR CERTIFICATE CertSeguridad;
GRANT SELECT ON dbo.Seguridad TO UsuarioCertSeguridad;
GO
-- ========================
-- Firmar el procedimiento con el certificado
-- ========================
ADD SIGNATURE TO OBJECT::dbo.usp_VerSeguridad
BY CERTIFICATE CertSeguridad
WITH PASSWORD = 'UnPasswordSegurísimo!';
GO
-- ========================
-- Comprobación con firma (debería funcionar)
-- ========================
PRINT '--- Prueba CON firma (esperado: acceso concedido) ---';
EXECUTE AS USER = 'UsuarioLimitado';
BEGIN TRY
EXEC dbo.usp_VerSeguridad;
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
END CATCH
REVERT;
GO
-- ========================
-- FIN DE LA DEMO
-- ========================
Como veis, si probamos a ejecutar el procedimiento almacenado con el usuario limitado va a dar error porque no tiene permisos sobre la tabla. Sin embargo, cuando creamos un certificado, lo asociamos a un usuario que tiene permisos sobre la tabla y firmamos el procedimiento con ese usuario ya podemos ejecutar sin error el procedimiento con el usuario limitado.
Ventajas clave de usar certificados
Una de las principales ventajas frente a EXECUTE AS es que el certificado no interfiere con el contexto de ejecución. Esto significa que si dentro del procedimiento hay llamadas a otros objetos que usan permisos del usuario original, todo seguirá funcionando correctamente. Además, los certificados son inmunes a problemas comunes como la pérdida de contexto entre bases de datos, lo que resulta útil en entornos distribuidos.
Otra ventaja es la auditabilidad. Como los permisos no se conceden directamente a los usuarios finales, sino que se encapsulan dentro de procedimientos firmados, es más sencillo identificar los puntos de entrada permitidos y realizar auditorías.
También se evita el problema clásico de los permisos residuales. Si un usuario necesita ejecutar varios procedimientos que requieren distintos permisos, no es necesario concederle un permiso amplio ni crear roles intermedios complejos. Firmamos cada procedimiento con los permisos justos que requiere, y el acceso queda perfectamente delimitado.
Consideraciones de los certificados a tener en cuenta
La firma de procedimientos con certificados no está exenta de ciertas limitaciones. Para empezar, no podemos firmar procedimientos encriptados ni CLR. Además, si un procedimiento se modifica, se pierde la firma y hay que volver a aplicarla.
En cuanto a la gestión de certificados, conviene centralizar su creación y almacenamiento de forma segura. El uso de contraseñas fuertes y una política clara de mantenimiento y renovación de certificados es fundamental para evitar riesgos.
Por último, es importante evitar el uso excesivo de esta técnica, especialmente si se convierte en la única forma de delegación. En bases de datos muy grandes o con cientos de procedimientos, puede ser más conveniente crear roles bien definidos y controlar el acceso de forma más clásica. La clave está en encontrar el equilibrio adecuado.
Comportamiento de DENY
Otra consideración muy importante que debemos tener en cuenta es que el uso de DENY no puede ser sobrepasado por una firma de certificado. Si el usuario que ejecuta el procedimiento tiene un DENY explícito sobre el objeto que se consulta dentro del procedimiento, nada podrá permitirle el acceso, por mucho que el certificado tenga ese GRANT.
Esto tiene dos consecuencias prácticas. La primera es que si queremos controlar el acceso mediante firmas, no debemos usar DENY. Sencillamente basta con no conceder permisos. Por otro lado, si existe un DENY, servirá como veto absoluto, incluso para accesos indirectos a través de procedimientos firmados.
Conclusión
Firmar procedimientos almacenados con certificados es una herramienta extremadamente útil y robusta para aplicar el principio de privilegios mínimos de forma segura y mantenible. Nos permite conceder permisos de forma encapsulada, sin necesidad de impersonaciones ni contextos elevados, y facilita una delegación precisa del acceso.
Es una técnica que deberíamos tener siempre en nuestro arsenal cuando diseñamos la arquitectura de seguridad de una base de datos SQL Server. En combinación con otras prácticas como el uso de roles, vistas seguras y auditoría de permisos, puede contribuir a sistemas mucho más sólidos, trazables y mantenibles.
Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

