Han*_*non 9 sql-server permissions sql-server-2019
我正在尝试将存储过程配置为 EXECUTE AS 具有 sysadmin 权限的用户,以允许非特权用户从未记录的 TVF 获取结果sys.fn_dump_dblog。
我正在作为角色成员创建该过程sysadmin,但是当我随后尝试使用该子句运行该过程时WITH EXECUTE AS SELF,出现以下错误:
消息 9011,级别 14,状态 1,过程 dbo.read_log,第 16 行 [批处理开始第 65 行] 用户无权使用虚拟表 DBLog 查询备份文件。只有 sysadmin 固定服务器角色的成员才有此权限
这对我来说似乎很奇怪,因为我是服务器角色的成员sysadmin,并且WITH EXECUTE AS SELF在过程定义中指定子句应该会导致使用我的用户帐户执行该过程。根据学习网站:
EXECUTE AS SELF 相当于 EXECUTE AS user_name,其中指定的用户是创建或更改模块的人。创建或修改模块的人员的实际用户 ID 存储在 sys.sql_modules 或 sys.service_queues 目录视图的execute_as_principal_id 列中。
如果我WITH EXECUTE AS SELF从过程的定义中删除该子句,TVF 将按预期返回结果。
以下是SQL Server 2019 中的最小、完整且可验证的示例。
首先,证明我们是该sysadmin角色的成员:
USE [master];
GO
SELECT
[roles].[name]
FROM sys.server_principals [members]
INNER JOIN sys.server_role_members [srm] ON members.[principal_id] = srm.[member_principal_id]
INNER JOIN sys.server_principals [roles] ON srm.[role_principal_id] = roles.[principal_id]
WHERE members.[sid] = SUSER_SID();
GO
Run Code Online (Sandbox Code Playgroud)
| 姓名 |
|---|
| 系统管理员 |
接下来,创建一个测试数据库,我们将在其中托管存储过程:
IF DB_ID(N'test_db') IS NOT NULL
BEGIN
ALTER DATABASE [test_db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
END;
DROP DATABASE IF EXISTS [test_db];
GO
CREATE DATABASE [test_db];
GO
ALTER DATABASE [test_db] SET RECOVERY FULL;
BACKUP DATABASE [test_db] TO DISK = N'C:\temp\test_db.bak' WITH FORMAT;
BACKUP LOG [test_db] TO DISK = N'C:\temp\test_db_txlog.bak' WITH FORMAT;
GO
Run Code Online (Sandbox Code Playgroud)
在测试数据库中创建存储过程:
USE [test_db];
GO
DROP PROCEDURE IF EXISTS [dbo].[read_log];
GO
CREATE PROCEDURE [dbo].[read_log]
(
@start_lsn nvarchar(25)
, @end_lsn nvarchar(25)
, @log_file nvarchar(260)
, @seq_num int
)
WITH EXECUTE AS SELF
AS
BEGIN
SELECT
[SUSER_SNAME()] = SUSER_SNAME()
, [USER_NAME()] = USER_NAME()
, [ORIGINAL_LOGIN] = ORIGINAL_LOGIN();
SELECT [fdd].*
FROM sys.fn_dump_dblog
(
@start_lsn
, @end_lsn
, N'DISK'
, @seq_num
, @log_file
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL
) [fdd];
END;
GO
Run Code Online (Sandbox Code Playgroud)
从日志备份中获取值FirstLSN和值:LastLSN
RESTORE HEADERONLY FROM DISK = N'C:\temp\test_db_txlog.bak';
GO
Run Code Online (Sandbox Code Playgroud)
使用这些值来运行过程:
DECLARE @start_lsn nvarchar(25) = N'154000000026400001';
DECLARE @end_lsn nvarchar(25) = N'154000000043200001';
EXEC [dbo].[read_log] @start_lsn, @end_lsn, N'C:\temp\test_db_txlog.bak', 1;
GO
Run Code Online (Sandbox Code Playgroud)
过程内语句的结果SELECT将如下所示:
| SUSER_SNAME() | 用户名() | 原始登录 |
|---|---|---|
| 域\用户 | dbo | 域\用户 |
产生的错误:
消息 9011,级别 14,状态 1,过程 dbo.read_log,第 16 行 [批处理开始第 62 行] 用户无权使用虚拟表 DBLog 查询备份文件。只有 sysadmin 固定服务器角色的成员才有此权限
如果从存储过程中删除该WITH EXECUTE AS SELF子句并重新运行它,我会从 TVF 获得结果。
这是我做错了什么,还是条款有问题EXECUTE AS?
Zik*_*ato 11
我添加了来自sys.login_token的选择来帮助进行权限调试。
USE master
go
CREATE OR ALTER PROCEDURE dbo.read_log
(
@start_lsn nvarchar(25)
, @end_lsn nvarchar(25)
, @log_file nvarchar(260)
, @seq_num int
)
WITH EXECUTE AS self
AS
BEGIN
SELECT
[SUSER_SNAME()] = SUSER_SNAME()
, [USER_NAME()] = USER_NAME()
, ORIGINAL_LOGIN = ORIGINAL_LOGIN();
SELECT
*
FROM sys.login_token AS lt
SELECT fdd.*
FROM sys.fn_dump_dblog
(
@start_lsn
, @end_lsn
, N'DISK'
, @seq_num
, @log_file
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
, NULL, NULL, NULL, NULL, NULL, NULL, NULL
) fdd;
END;
GO
Run Code Online (Sandbox Code Playgroud)
如果我保留EXECUTE as self,我们可以看到sa和sysadmin标记在用法DENY ONLY列中。
当我删除或注释掉 时EXECUTE as self,我们可以看到更多的登录令牌,并且用法也发生了变化GRANT OR DENY
您链接的文档是这么说的(强调我的)
EXECUTE AS SELF 相当于 EXECUTE AS user_name,其中指定的用户是创建或更改模块的人。创建或修改模块的人员的实际用户 ID存储在 sys.sql_modules 或 sys.service_queues 目录视图中的execute_as_principal_id 列中。
它提到用户是数据库级主体。该过程需要服务器级主体(登录)权限。似乎EXECUTE AS SELF将其自身限制在数据库范围内。
如果要向没有 sysadmin 权限的其他用户授予访问权限,可以使用模块签名来签署该过程。一种方法是将过程移至master数据库中:
master- 这是因为服务器登录位于数据库中并且需要视线到证书USE master
CREATE CERTIFICATE SaPermissionCert
ENCRYPTION BY PASSWORD = 'password123$'
WITH
SUBJECT = 'Used to grant sa permissions to a module'
, EXPIRY_DATE = '99991231'
Run Code Online (Sandbox Code Playgroud)
sysadmin服务器角色,但可能需要较少的权限才能到达那里CREATE LOGIN SaPermissionUser FROM CERTIFICATE SaPermissionCert
ALTER SERVER ROLE sysadmin ADD MEMBER SaPermissionUser
Run Code Online (Sandbox Code Playgroud)
USE master
ADD SIGNATURE TO dbo.read_log
BY CERTIFICATE SaPermissionCert WITH PASSWORD = 'password123$'
Run Code Online (Sandbox Code Playgroud)
现在,您只需像平常一样向最终用户授予执行过程的权限。
不要忘记,对程序的任何更改都需要重新应用签名。
您可以master通过复制证书来避免移动该过程,如 Solomon Rutzky 在安全轻松地使用高级权限而不将其授予任何人:服务器级别中所述。
为了避免密码管理问题,请使用 Erland Sommarskog在存储过程中的打包权限中描述的方法。这使用一次性随机密码,不需要在部署环境中存储或以其他方式管理。每次签署过程时都会完全重新生成证书和登录名。您可以在此处找到他的示例脚本。