SQL Server CLR 集成第一部分:安全性






4.77/5 (13投票s)
了解 SQL Server 和 CLR 安全模型如何协同工作以确保您的数据库安全。
SQL CLR
本文是我计划撰写的系列文章中的第一篇,旨在探讨 SQL Server 2005 托管的公共语言运行时 (CLR)。本篇将重点介绍 CLR 的安全模型。后续文章将讨论整个 CLR 的性能和稳定性、托管代码与 T-SQL 的性能以及 SQL CLR 的一般最佳实践。
人们对 SQL CLR 既感到兴奋又有所警惕。能够以开发人员编写 Windows 或 Web 应用程序的相同语言创建 SQL Server 对象,这真是太棒了。但是,由于它是新功能,并且我们的数据“守护者”(DBA)不完全理解其含义,您可能会发现无法利用 SQL Server 2005 中这个强大的新功能。
但是,微软并不是唯一一家将 CLR 集成到其数据库平台的数据库供应商。Oracle (http://www.oracle.com/technology/oramag/oracle/05-may/o35briefs.html) 和其他供应商已经宣布,他们已经添加了相同的功能,或者计划在未来添加。所以,在我看来,我们需要了解这项新功能的含义,以确定我们能从中获得哪些好处,如果有的话。
SQL CLR 并非可怕之物,但它需要得到尊重并妥善使用。T-SQL 仍是基于集合的数据操作之王,并且在短期内不会消失。了解您的工具并适当地应用它们。我希望通过本文提供对 CLR 的良好理解,在本系列文章结束时,您将知道何时何地使用它,以及何时应将其搁置。
我选择安全作为我的第一个主题,因为我希望首先消除关于 CLR 对 SQL Server“危险”的任何误解。确实,如果使用不当,它可能会很危险,但在我完成本文后,您应该知道并理解如何正确使用它。
SQL CLR 涉及两个安全级别:SQL Server 安全性和 CLR 的代码访问安全性 (CAS)。
SQL Server 安全性
SQL Server 对 CLR 的第一个安全限制是 CLR 默认禁用。可以注册程序集并创建对象,但不能执行它们。任何尝试调用/执行 SQL CLR 对象的尝试都将收到以下响应
Execution of user code in the .NET Framework is disabled. Enable
"clr enabled" configuration option.
要启用 CLR,请使用已授予 ALTER SETTINGS
权限的帐户(sysadmin、serveradmin 或明确授予权限的帐户)运行以下脚本
--enable the clr
EXEC sp_configure 'show advanced options' , '1';
go
reconfigure;
go
EXEC sp_configure 'clr enabled' , '1'
go
reconfigure;
或者,您可以使用 SQL Server 外围应用配置工具修改功能,但您仍然必须拥有 ALTER SETTINGS
权限。
接下来,SQL Server 管理 CLR 对象权限的方式与管理 T-SQL 对象权限的方式相同。一旦创建了帐户,就可以授予它 CLR 对象上以下权限集中的任意组合:
CREATE ASSEMBLY
– 添加新程序集的权限EXECUTE
– 从代码调用/调用特定 CLR 对象的权限。此权限在执行时检查。REFERENCES
– 在创建新的 T-SQL 或 CLR 对象时引用特定 CLR 对象的权限。此权限在编译/创建时检查。SELECT
– 调用表值函数并返回结果的权限。
此外,以下权限间接影响创建对象时可执行的操作
INSERT
、SELECT
、UPDATE
、DELETE
– 对表、视图执行指定方法的权限。这类似于REFERENCES
,权限在编译时对照对象的拥有者/创建者进行检查。因此,只要调用者对特定对象具有EXECUTE
权限,这些权限就不会在执行时检查。
以下是一些当用户被授予指定权限时可以执行的操作示例
--create an assembly when CREATE ASSMEBLY permissions have been granted
CREATE ASSEMBLY myAssembly
AUTHORIZATION dbo
FROM 'C:\projects\mySolution\myProject\bin\Release\myAssembly.dll'
WITH PERMISSION_SET = SAFE
GO
--call a clr function when EXECUTE permission has been granted
DECLARE @ret INT
SET @ret = dbo.clrfunction_add_numbers(3, 7)
--call a table-valued function when SELECT has been granted
SELECT * FROM dbo.clrfunction_string_to_table
('key1|key2|key3|key4|key5', '|')
托管 CLR
在深入探讨 CLR 的安全特性之前,我想花点时间讨论一下托管 CLR 的含义——这应该有助于更好地理解 CLR 内置的安全性。
微软将 CLR 实现为 COM 服务器;该实现位于名为 MSCorWks.dll 的文件中。它位于 Windows\Microsoft.Net\Framework\{version} 文件夹中。当应用程序想要托管 CLR 时,它使用一个名为 ICLRRuntimeHost 的 COM 接口,该接口定义在一个名为 MSCorEE.h 的非托管 C++ 头文件中。
要加载 CLR,托管应用程序会调用 CorBindToRuntimeEx,它会返回一个指向 ICLRRuntimeHost 的指针。此时,宿主就可以使用接口方法来控制哪些类/成员可以被加载和执行。该接口还定义了允许宿主控制内存、线程和程序集加载等的方法。
我将在下一篇关于 CLR 性能的文章中详细讨论这一点,但您可以参考 SDK 文档或 MSCorEE.h 文件本身。在此,我要感谢 Wintellect 的 Jeffery Richter 在其著作《CLR via C#》(Microsoft Press)中对 CLR 托管的解释。欲了解更多信息,请参阅他的著作。如果您想了解比一章更详细的信息,Jeffery 建议您阅读《Customizing the Microsoft.NET Framework Common Language Runtime》(Microsoft Press)。
CLR 安全性
SQL CLR 安全性的一些方面模糊了安全性与稳定性之间的界限。某些安全功能的原因是为了在 CLR 上强制执行“不造成伤害”的策略。这是为了防止 CLR 导致 SQL Server 不稳定。这些功能中的第一个是宿主保护属性 (HPA)。
主机保护属性
宿主保护属性(HPA)继承自 System.Attribute,并定义了声明 HPA 的 API 所执行的操作类型。当诸如 SQL Server 这样的宿主应用程序加载 CLR 时,它可以检查这些属性并定义它不允许的功能。SQL Server 不允许以下 HPA
ExternalProcessMgmt(外部进程管理)
ExternalThreading(外部线程)
MayLeakOnAbort(可能在中止时泄露)
SecurityInfrastructure(安全基础设施)
SelfAffectingProcessMgmnt(自我影响进程管理)
SelfAffectingThreading(自我影响线程)
SharedState(共享状态)
同步
UI
正如我之前提到的,这些限制既是为了稳定性,也是为了安全性。例如,您不希望执行打开消息框并阻塞用户响应的代码。有关所有不允许的类型和成员的完整列表,请参阅 MSDN。
还有其他 HPA 未完全禁用,但它们根据调用 CREATE ASSEMBLY
时使用的 PERMISSION_SET
子句的值而受到限制(稍后将详细介绍)。
代码访问安全
CLR 定义了一种称为代码访问安全 (CAS) 的安全模型,它根据代码本身的身份限制权限。CAS 定义了四个安全级别
- 企业版
- Machine
- 用户
- 宿主
如果您曾使用过 .NET Framework 配置工具,您可能熟悉前三个级别。它们允许您在每个不同的级别定义安全策略。默认情况下,所有安装在本地计算机上的代码都以 FullTrust 运行,这意味着对可以加载的程序集或可以执行的方法没有限制。但第四个级别,Host,由托管 CLR 的应用程序定义,用户无法配置。
SQL Server 使用主机策略,除其他外,根据其声明的 HPA 限制对类和方法的访问。如果我想创建/安装一个新程序集,我会运行以下脚本
--create assembly
CREATE ASSEMBLY myAssembly
AUTHORIZATION dbo
FROM 'C:\projects\mySolution\myProject\bin\Release\myAssembly.dll'
WITH PERMISSION_SET = SAFE
GO
授予此新程序集的权限基于 PERMISSION_SET 的值。PERMISSION_SET 可能有三个可能的值之一
SAFE
:与 T-SQL 相同的权限。仅限于内部数据访问。EXTERNAL_ACCESS
:允许在 SQL Server 服务帐户(默认服务帐户 = 本地系统)下访问文件系统、注册表、环境变量等外部资源。UNSAFE
:与 CAS“FullTrust”权限集相同。这意味着 CLR 不会检查权限。与EXTERNAL_ACCESS
类似,对外部资源的访问默认将在 SQL Server 服务帐户的上下文中运行。
有关 SQL Server CAS 主机策略允许/不允许哪些 HPA 的详细信息,请参阅 MSDN。
如果您创建的对象访问的类型或方法在创建程序集时未被 PERMISSION_SET
子句允许,您将能够创建程序集并创建使用 CLR 方法/函数的对象。但是当您尝试引用新对象时,它将失败并出现以下 System.SecurityException
错误
在执行用户定义的例程或聚合 'clrfunction_getHostName'
期间发生了 .NET Framework 错误
System.Security.SecurityException: Request for the permission of type
'System.Net.DnsPermission, System, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089' failed.
System.Security.SecurityException:
at System.Security.CodeAccessSecurityEngine.Check(Object demand,
StackCrawlMark& stackMark, Boolean isPermSet)
at System.Security.CodeAccessPermission.Demand()
at System.Net.Dns.GetHostName()
at UserDefinedFunctions.ExternalAccessMethod()
如果看到此消息,则表示您需要将程序集与 EXTERNAL_ACCESS
或 UNSAFE
作为 PERMISSION_SET
值一起创建。在创建具有 EXTERNAL_ACCESS
或 UNSAFE
的程序集之前,必须存在两个条件
- 数据库必须由管理员(sysadmin 固定服务器角色的成员)标记为
TRUSTWORTHY
,或者程序集已用证书或非对称密钥签名 - 创建程序集的用户必须具有
EXTERNAL ACCESS ASSEMBLY
或UNSAFE ASSEMBLY
权限
否则,当您尝试创建程序集时,将收到以下消息
CREATE ASSEMBLY for assembly '%' failed because assembly '%' is not
authorized for PERMISSION_SET = EXTERNAL_ACCESS. The assembly is authorized
when either of the following is true: the database owner (DBO) has EXTERNAL
ACCESS ASSEMBLY permission and the database has the TRUSTWORTHY database
property on; or the assembly is signed with a certificate or an asymmetric
key that has a corresponding login with EXTERNAL ACCESS ASSEMBLY permission.
关于创建证书或非对称密钥的讨论超出了本文的范围,但要将您的数据库标记为 TRUSTWORTHY
,sysadmin 角色的成员可以运行以下脚本
ALTER DATABASE MaxPreps_v2 SET TRUSTWORTHY ON
GO
请务必确保您对需要 EXTERNAL_ACCESS
或 UNSAFE
方法的使用是合理的。在您诉诸于从 SQL Server 内部运行此类代码之前,您应该了解它如何影响您的系统和应用程序。问自己相同的功能是否可以作为外部进程而不是从 SQL Server 进程空间内部实现。另一方面,所有这一切的好处是,您知道已经建立了制衡机制来保护您的数据库免受运行潜在危险代码的影响。
验证
SQL Server 要求程序集必须是可验证的类型安全才能以 SAFE
或 EXTERNAL_ACCESS
运行。非类型安全的托管代码是使用 /unsafe 编译器开关编译的代码。类型安全代码(不要与 PERMISSION_SET = SAFE
混淆)保证在自己的地址空间内执行所有内存访问操作,并且不会影响其他正在运行应用程序的稳定性。当创建的程序集指定 SAFE
或 EXTERNAL_ACCESS
时,SQL Server 会在创建程序集时验证该程序集是否类型安全。您可以使用 .Net SDK 中包含的 PEVerify.exe 执行相同的验证过程。
执行上下文
默认情况下,在 SQL Server 中,CLR 对象在调用者会话的执行上下文中运行。如果标记为 EXTERNAL_ACCESS
或 UNSAFE
的程序集尝试访问 SQL Server 外部的资源,则执行上下文将是 SQL Server 服务帐户。默认情况下,SQL Server 安装在 LocalService 帐户下。
然而,有两个级别的模拟可用:SQL Server 的 EXECUTE AS
命令和 WindowsIdentity.Impersonate()
方法。
- EXECUTE AS – 允许用户更改 SQL Server 执行上下文。这要求对要模拟的帐户授予 IMPERSONATE 权限,以便执行模拟的用户使用。
EXECUTE AS USER = 'SOMEDOMAIN\someuser' SELECT * FROM dbo.clrfunction_string_to_table('key1|key2|key3', '|') REVERT
WindowsIdentity.Impersonate()
– 如果当前会话的用户帐户是使用 Windows 身份验证进行身份验证的,那么为了访问外部资源,CLR 对象可以指定它希望使用 Windows 用户帐户的执行上下文而不是 SQL Server 服务帐户。请注意,这会阻止您使用当前上下文访问内部 SQL Server 资源。这意味着您的对象将被要求像外部客户端一样访问本地数据。因此,一旦您完成对外部资源的操作,就应该恢复到原始上下文。WindowsImpersonationContext executionContext = null; try { //impersonate the current execution context WindowsIdentity callerId = SqlContext.WindowsIdentity; if (callerId != null) { executionContext = callerId.Impersonate(); //do some work } } catch(Exception ex) { //handle exception } finally { if (executionContext != null) executionContext.Undo(); }
因此,在使用 WindowsIdentiy.Impersonate() 时,请记住以下几点:
- 模拟仅用于外部操作,因此您的程序集必须使用
EXTERNAL_ACCESS
或UNSAFE
创建。 - 如果在退出前未恢复,将抛出异常。
- 在模拟期间,本地数据访问(在连接字符串中使用“Context Connection=yes”)将被拒绝。必须像客户端是使用“标准”连接字符串的外部客户端一样建立连接。
结论
总而言之,保护 SQL CLR 对象的额外工作量并不比保护 T-SQL 对象多,SQL Server 安全对象模型保持不变。然而,正如您所见,在幕后,为了以安全的方式启用这项强大的新功能,付出了大量努力。我希望本文能阐明集成 CLR 在 SQL Server 中的工作方式。请继续关注我下一篇关于 CLR 性能和稳定性的文章。