目录
介绍
老实说,我对写这篇文章有两种想法。本网站主要面向开发人员。这篇文章并不是关于编码的。它与Kerberos、服务主体名称(SPN)、SQL Server和一些AD有关。所以不是典型的CodeProject文章。
但是,几乎任何编写过连接到SQL Server的客户端的开发人员都接触过SQL Server的安全方面。在现代应用中,这意味着集成安全。如果某处配置错误,连接安全性就会失败。当这种情况发生时,可能很难诊断,特别是如果你对基本原理没有很好的理解。
本文旨在解释如何在客户端和服务器之间设置连接安全性的基础知识,因为这是开发人员的重要考虑因素。这样,如果您使用SQL后端开发和部署客户端应用程序,并得到可怕的“无法生成SSPI上下文”,您将有足够的知识以结构化的方式调查问题,而不是疯狂地在谷歌上搜索错误并获得无数不适用于您的问题的不同解决方案。
需要明确的是,实现数据库安全性、授予表权限、执行权限等过程不在本文的讨论范围之内。这属于系统管理。
背景
在现代客户端-服务器系统(如SQL Server)中,有两种主要方法可以实现安全性。第一种是使用特定于应用程序的登录名,并让应用程序基于此执行身份验证。对于SQL Server,这些是在数据库实例本身中创建和维护的旧式SQL登录名。在现代安装中,默认情况下禁用此功能,因为此类帐户存在许多问题。
- 它们不是集中管理的。甚至实例也有自己的帐户和密码集合。此外,当用户离开网站时,这些不会自动失效。
- 它们不适用于可能需要外部资源的代码,这意味着如果SQL查询使用存储过程,例如与文件或其他资源交互,则没有很好的方法来管理它,因为SQL登录不是操作系统登录。
- 由于它们的性质,它们通常存储在连接字符串、设置文件或其他类似内容中,这意味着它们比OS登录名或Active Directory登录名更容易受到攻击。
除了支持旧版应用程序外,最好禁用这些登录名。如果确实需要它们,则称为“混合模式安全性”。
Windows集成安全性要好得多,因为它遵循现有的用户管理范例,即用户在组中,以及组被授予资源权限。个人用户将受到标准企业访问控制程序的约束。
Windows使用两种协议来保护客户端和服务器之间的通道:NTLM和Kerberos。
NTLM与Kerberos
NTLM是域环境中用于安全的旧协议。它很方便,但不如Kerberos安全。Kerberos otoh要安全得多,但配置起来可能很麻烦,因为它需要您非常精确地定义谁可以代表谁做什么。
打个比方,NTLM相当于CEO的私人秘书能够代表CEO打电话给HR给自己大幅加薪,而HR则对“老板让我告诉你......”感到满意。
使用Kerberos,秘书必须证明她最近从CEO那里获得了订单,而HR将能够验证订单以及秘书是否已正式获得委派该特定类型订单的权限。
使用哪种协议取决于系统配置。可以通过GPO、本地安全策略或注册表操作在域中的各个级别启用/禁用NTLM。如果查看安全选项GPO 的参考,则可以看到所有可能的NTLM相关设置。
现在,假设允许NTLM和Kerberos,则SQL Server仍将使用Kerberos进行集成安全连接。如果不能,则将回退到NTLM。为了使SQL Server能够使用Kerberos,需要在Active Directory中配置正确的服务主体名称。
检查使用的身份验证机制
验证使用哪种方法很简单,因为当您连接时,可以查询该信息。为了进行测试,我使用 Github上提供的SqlServer PowerShell 模块,该模块是连接到SQL Server的标准方法。
用于确定连接类型的查询如下:
SELECT auth_scheme FROM sys.dm_exec_connections WHERE session_id=@@SPID
因此,在PowerShell中,使用该SqlServer模块,脚本将如下所示:
ipmo SqlServer
Invoke-Sqlcmd -Query "SELECT auth_scheme _
FROM sys.dm_exec_connections WHERE session_id=@@SPID" -ServerInstance <INSTANCE_NAME>
这将导致如下结果:
服务主体名称
解释Kerberos的细节超出了本文的范围。如果你想了解更多关于Kerberos的信息,我推荐这本书。
撇开所有细节不谈,Kerberos是围绕通信双方都有办法安全地验证彼此身份的概念构建的。这意味着客户端需要能够知道它所连接到的服务的身份。服务本身可以作为“本地系统”(具有计算机对象的标识)运行,也可以作为常规用户帐户或托管服务帐户运行。
服务主体名称(SPN)用于将该服务与Active Directory对象相关联,以便客户端知道它正在与谁通信。如果没有SPN,客户端将无法弄清楚这一点,因为它本质上只是连接到套接字,而不知道谁在侦听该套接字的身份。通过查询Active Directory,客户端可以了解它应该与谁交谈,并适当地设置安全性。
这些SPN的配置是在它正在使用的标识的AD对象的servicePrincipalName属性中完成的。在这个例子中,您可以看到SE-WINDEV021服务器上的 Microsoft SQL Server MSSQLSvc正在使用网络上计算机本身的标识。
您还可以使用以下SetSPN命令进行验证:
因此,将在客户端的标识和计算机对象的标识之间建立通信通道安全性。如果将SQL Server配置为使用常规用户帐户的标识(例如(假设)SQLSVC01)运行,则SPN MSSQLSv/SE-WINDEV01...将列在SQLSVC01用户帐户的servicePrincipalName属性下。
SPN的确切格式如下(来自Microsoft):
MSSQLSvc/fqdn | 当使用TCP以外的协议时,默认实例的提供程序生成的默认SPN。 |
MSSQLSvc/fqdn:port | 使用TCP时提供程序生成的默认SPN。 |
MSSQLSvc/fqdn:InstanceName | 当使用TCP以外的协议时,命名实例的提供程序生成的默认SPN。 |
现在,您已经知道SPN应该位于何处以及它们应该是什么样子,接下来的问题是:谁创建了它们?
自动创建SPN
在某些情况下,它们可以由服务本身自动创建。例如,当SQL Server启动时,作为启动序列的一部分,它将尝试为您注册SPN。文档提到,如果服务帐户是LocalSystem或NetworkService,或者明确授予了正确的权限,则会执行此操作。这是不正确的,而且非常令人困惑。
注册SPN需要修改servicePrincipalName属性的权限。由于它涉及服务使用其标识的对象的属性,因此对象安全性需要对SELF主体具有特殊权限。默认情况下,此设置在标准目录中配置。
需要注意的是,授予的访问权限是针对已验证的servicePrincipalName属性写入。根据Microsoft的说法,在此上下文中验证意味着“要求系统在将值写入DS对象上的属性之前执行超出架构要求的值检查或验证。这可确保为属性输入的值符合所需的语义,在值的合法范围内,或者经过一些其他特殊检查,而这些检查对于对属性的简单低级写入不会执行"。
这在某种程度上是有道理的,因为对象只有在具有有效语法时才能添加SPN。但是,问题在于语法被定义为“符合要设置的计算机的 DNS主机名的SPN属性”。请记住,与其他服务不同,SQL支持多个实例,这用SPN的语法表示的。
SQL的默认实例具有MSSQLSv/SE-WINDEV01.contoso.com形式的SPN。具有实例名称SQLSANDBOX的命名实例具MSSQLSv/SE-WINDEV01.contoso.com:SQLSANDBOX有形式的SPN。
此格式违反了已验证写入的DNS主机名要求。因此,与文档相反,SPN记录将仅自动为默认实例创建,因为它没有附加实例名称。因此,命名实例安全性回退到NTLM,即使使用应根据文档注册自身的服务帐户也是如此。
您可以通过允许SELF Write servicePrincipalName权限来解决此问题。
如果执行此操作,SQL Server将启动并能够成功注册SPN。
如果你然后做一个连接测试,你会得到这样的结果:
手动配置SPN
在不允许启用SELF注册的安全配置或无法选择的情况下,对服务标识的AD对象具有管理权限的任何人都可以通过SetSPN实用程序手动执行此操作。
有一个警告,不要注册重复的SPN,因此我在Active Directory中手动删除了它们,然后使用SetSPN。
在自动配置和手动配置之间进行选择的注意事项
决定哪种方法最好取决于公司策略以及基础结构的管理方式。
就确保注册正确而言,允许SELF更新servicePrincipalName属性无疑是最简单的方法。但是,它确实需要修改AD对象安全性,并且由于各种原因,许多管理员对此类事情持谨慎态度(AD配置本身可能受到繁琐的更改控制,可能需要冗长的测试过程或文档更新,公司可能不允许管理员进行更改,或者他们可能只是偏执狂)。
另外,如果我们说实话,添加SELF权限需要直接在对象本身或它所在的容器OU上完成。鉴于计算机和用户不会位于同一OU,并且您不希望所有主体都能够注册SPN,这可能很乏味。可悲的是,无法创建用于设置此权限的组,因为如果这样做,则组内的所有对象都可以设置彼此的SPN。
允许主体设置自己的SPN的权限可能是一件危险的事情,因为该主体可以通过注册一堆错误的SPN来对Active Directory执行拒绝服务(DoS)攻击。
正是由于这些原因,大多数环境都选择手动注册。但是,如果这样做,则建议修复SQL Server服务的端口号,以便已注册的SPN的端口号与端口号匹配。
默认情况下,SQL Server的任何命名实例都使用动态端口。文档指出,为了使用动态端口,应将动态端口设置为0。但是,如果您检查,那么您将看到以下内容:
发生的情况是,在创建实例时,SQL会将一个随机的高端口号作为动态端口。您会注意到,它将在服务重启和服务器重启期间重用此端口。那么,你可能会问,为什么叫动态端口呢?偷偷摸摸的是,它会尝试始终重用这个端口,但如果它碰巧正在使用中,它只会选择一个新的并开始使用它。这是很少发生的事情之一,以至于你被哄骗相信它总是一样的。
在SQL Server服务主体没有自动更新SPN的权限的情况下,这种情况的结果是SPN记录将不再与实际运行的服务匹配。发生这种情况时,SQL客户端将不再能够设置Kerberos会话,并且要么回退到NTLM,要么在禁用NTLM时失败。
因此,如果遇到无法进行自动SPN注册所需的配置的情况,请确保不使用动态端口,而是使用固定端口。这就像获取当前动态端口号并将其保存在TCP端口字段而不是动态端口字段中一样简单。请务必将动态端口字段留空,以确保服务不会同时使用动态端口。
请注意,如果其他进程碰巧采用该高端口,则SQL服务将不会启动。但这要好得多,因为它很容易观察和诊断。有时使用的一种技巧是使用在tcp协议中分配给不再存在的公司或软件的端口号,或者已知在您的网络或服务器上不存在。
孤立SPN的陷阱
如果不允许自动注册SPN,则可能会出现严重问题。顺便说一句,这是导致我研究SPN并撰写本文的问题。
假设您有一个SQL Server启动并运行,并且出于某种原因或其他原因(移动到生产环境、安全审核等),您必须更改SQL服务的用户帐户。例如,从LocalSystem到托管服务帐户,并禁用自动注册。如果原始帐户已将SPN注册到自身,则切换到其他服务主体不会更新服务主体名称。SPN是孤立的。
当这种情况发生时,事情就会匆忙停止工作。这比根本没有SPN要糟糕得多。如果没有SPN,相互身份验证将回退到NTLM。但是,如果存在错误的SPN,则不会发生这种情况。客户端将尝试设置一个安全通道,以便与它认为用于运行SQL Server的用户主体进行通信,但失败。
疑难解答提示和技巧
如果遇到相互身份验证和/或Kerberos问题,可以通过以下方式进行故障排除:
- 从SQL Server日志开始,查看SPN是否自动生成。如果没有,请根据实例名称和端口号检查实例应该存在哪两个SPN。
- 使用powershell Test-Connection cmdlet验证网络连接。
- 检查端口号是动态的还是固定的。
- 使用SetSPN查询这两个SPN并验证它们是否注册到正确的Active Directory主体。
- 如果不确定客户端本身,可以使用 SSPIClient测试应用程序来测试客户端是否可以设置安全连接。
- 如果要在SQL Server本身上运行诊断,可以使用适用于SQL Server的Kerberos配置管理器。
结论
通过这篇文章,我希望已经解释了足够的基础知识,让你有一个基本的了解。综上所述:
- SPN用于将用户主体与计算机上运行的服务相关联,以便使用Kerberos在客户端和服务之间建立安全通道。
- SPN可以自动注册,也可以手动注册。如果无法自动创建它们,则对服务主体的每次更改都需要手动更新SPN注册。
- 当SPN不存在时,无法使用Kerberos,并且相互身份验证将回退到NTLM或完全失败,具体取决于通过GPO、本地安全策略或注册表进行的安全配置。
- 错误的SPN记录将导致身份验证失败。
https://www.codeproject.com/Articles/5360561/Understanding-Service-Principal-Names-for-SQL-Serv