triggers - 是否可以向需要用户没有权限的触发器授予权限?
问题描述
过去,我使用数据库触发器来记录所有架构更改。这是光荣的。但是,它要求任何需要修改架构的人都必须具有某些权限。我相信需要的是查看服务器状态。无法确定,但这篇文章需要哪个(s)并不重要。
问题是,如果有用户可以更改架构但不应该拥有该权限怎么办?这完全破坏了我的工具,因为触发器只会导致他们收到此错误。我可以将它包装在一个 try catch 中,但是如果它不记录更改,那显然会破坏整个目的。
有没有办法让这个触发器为每个人运行,即使我不能给每个人所有必需的权限?我猜答案是否定的,因为我不知道如何在某种模拟下触发触发器,而且我想不出任何其他解决方案。但我希望你们知道一些我不知道的事情!
这是触发器:
CREATE TRIGGER [LogSchemaEvent]
ON DATABASE
FOR CREATE_TABLE, ALTER_TABLE, DROP_TABLE,
CREATE_VIEW, ALTER_VIEW, DROP_VIEW,
CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE,
CREATE_FUNCTION, ALTER_FUNCTION, DROP_FUNCTION,
CREATE_TRIGGER, ALTER_TRIGGER, DROP_TRIGGER,
CREATE_SCHEMA, ALTER_SCHEMA, DROP_SCHEMA,
CREATE_INDEX, ALTER_INDEX, DROP_INDEX,
CREATE_TYPE, DROP_TYPE
AS
BEGIN
SET NOCOUNT ON
DECLARE @EventData XML = EVENTDATA()
DECLARE @ip VARCHAR(32) = (
SELECT client_net_address
FROM sys.dm_exec_connections
WHERE session_id = @@SPID)
INSERT SchemaEvents (
EventType,
EventDDL,
EventXML,
DatabaseName,
SchemaName,
ObjectName,
HostName,
IPAddress,
ProgramName,
LoginName)
SELECT
@EventData.value('(/EVENT_INSTANCE/EventType)[1]', 'NVARCHAR(100)'),
@EventData.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'NVARCHAR(MAX)'),
@EventData,
DB_NAME(),
@EventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(255)'),
@EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)'),
HOST_NAME(),
@ip,
PROGRAM_NAME(),
SUSER_SNAME();
END
解决方案
通过使用与具有所需权限的用户关联的证书对其进行签名,可以将提升的权限授予模块(过程、函数、触发器)。这适用于模式范围的对象,但不适用于 DDL 触发器,因为它们不能直接签名。
但是,您可以创建具有所需功能的模式范围 proc,使用证书对其进行签名,然后从 DDL 触发器中调用它。这种方法的唯一缺点是数据库必须是可信的。我通常建议避免使用 TRUSTWORTHY,但在开发数据库的情况下可能是可以接受的。
下面是一个示例脚本。
USE YourDatabase;
GO
CREATE OR ALTER PROCEDURE dbo.LogDDLEvent
@EventData xml
AS
INSERT SchemaEvents (
EventType,
EventDDL,
EventXML,
DatabaseName,
SchemaName,
ObjectName,
HostName,
IPAddress,
ProgramName,
LoginName)
SELECT
@EventData.value('(/EVENT_INSTANCE/EventType)[1]', 'NVARCHAR(100)'),
@EventData.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'NVARCHAR(MAX)'),
@EventData,
DB_NAME(),
@EventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(255)'),
@EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)'),
HOST_NAME(),
(SELECT TOP 1 client_net_address FROM sys.dm_exec_connections WHERE session_id = @@SPID),
PROGRAM_NAME(),
SUSER_SNAME();
GO
GRANT EXECUTE ON dbo.LogDDLEvent TO public;
GO
CREATE OR ALTER TRIGGER [LogSchemaEvent]
ON DATABASE
FOR CREATE_TABLE, ALTER_TABLE, DROP_TABLE,
CREATE_VIEW, ALTER_VIEW, DROP_VIEW,
CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE,
CREATE_FUNCTION, ALTER_FUNCTION, DROP_FUNCTION,
CREATE_TRIGGER, ALTER_TRIGGER, DROP_TRIGGER,
CREATE_SCHEMA, ALTER_SCHEMA, DROP_SCHEMA,
CREATE_INDEX, ALTER_INDEX, DROP_INDEX,
CREATE_TYPE, DROP_TYPE
AS
SET NOCOUNT ON;
DECLARE @EventData XML = EVENTDATA();
EXECUTE dbo.LogDDLEvent @EventData = @EventData;
GO
USE master
GO
-- Create certificate in master.
CREATE CERTIFICATE LogSchemaEventCertificate
ENCRYPTION BY PASSWORD = 'ephemoral password'
WITH SUBJECT = 'For view server state';
CREATE LOGIN LogSchemaEventCertificateUser FROM CERTIFICATE LogSchemaEventCertificate;
GRANT VIEW SERVER STATE TO LogSchemaEventCertificateUser;
-- Copy cert to user database
DECLARE @cert_id int = cert_id('LogSchemaEventCertificate')
DECLARE @public_key varbinary(MAX) = certencoded(@cert_id),
@private_key varbinary(MAX) =
certprivatekey(@cert_id,
'ephemoral password',
'ephemoral password');
SELECT @cert_id, @public_key, @private_key; --these values should be non-null
DECLARE @sql nvarchar(MAX) =
'CREATE CERTIFICATE LogSchemaEventCertificate
FROM BINARY = ' + convert(varchar(MAX), @public_key, 1) + '
WITH PRIVATE KEY (BINARY = ' +
convert(varchar(MAX), @private_key, 1) + ',
DECRYPTION BY PASSWORD = ''ephemoral password'',
ENCRYPTION BY PASSWORD = ''ephemoral password'')';
EXEC YourDatabase.sys.sp_executesql @sql;
ALTER CERTIFICATE LogSchemaEventCertificate REMOVE PRIVATE KEY;
ALTER DATABASE YourDatabase SET TRUSTWORTHY ON;
GO
USE YourDatabase;
ADD SIGNATURE TO dbo.LogDDLEvent BY CERTIFICATE LogSchemaEventCertificate WITH PASSWORD = 'ephemoral password';
ALTER CERTIFICATE LogSchemaEventCertificate REMOVE PRIVATE KEY;
GO
推荐阅读
- html - 如何让 dir="rtl" 与 bootstrap 4.3.1 一起使用?
- android - 为什么roboto bold 在android 上看起来如此“大胆”(700)?
- python - 如何在 url 中传递字典类型对象?
- sql - last_value() 和分组依据
- rest - Tornado 装饰器 cors 标头
- python - 从 Django 页面生成静态 HTML 页面
- ansible - 如果第一次失败,Ansible 使用不同的主机名
- ubuntu-16.04 - sudo apt update && sudo apt dist-upgrade 7 未升级
- python - 无法使用 __init__.py 创建模块
- java - 最近 Xodus 版本中的服务提供者接口