Windows 中的所有进程与线程都是以某个用户的身份运行的。交互式应用程序一般会使用当前登录用户的身份;而在后台运行的 Windows 服务则需要手动指定服务帐户。

Active Directory 中的服务帐户

Active Directory 环境中的 Windows 服务可以通过多种方式建立身份:

身份委托

如果服务应用程序支持,则可以使用身份委托。服务应用程序通过 AD 支持的 Kerberos 等认证协议,将最终用户登录到服务器,以最终用户的身份执行请求所需的服务逻辑。

对于 AD 环境中响应实际用户请求的 C/S 架构服务,这是最正确的身份认证方案。这种方案真正实现了端到端、细粒度的访问控制与审计能力。如果面向 Windows 环境开发服务应用程序,则应当采用该方案。

典型应用案例包括 SQL Server, IIS 等服务的 Windows 集成认证能力。文件共享等内置服务当然也是使用了这种方案。在 Linux 等其它平台运行的 PostgreSQL, Samba 等服务也可以通过 GSSAPI 协议实现相同功能。

然而,这需要服务应用程序的显式支持,若所需的服务没有经过相关的设计与实现,则无法使用这种方案。另外,如果服务是完全不接收用户输入的维护类应用程序(如自动备份服务),则没有登录用户的机会。这些情况下,只能使用专用的服务帐户运行服务。

使用计算机帐户

AD 中除了用户有帐户之外,计算机本身也有帐户。计算机帐户与用户帐户一样,可以登录到域,参与访问控制,并访问网络上的资源。计算机帐户的密码轮替等维护操作是由相关的计算机自动处理的,无需管理员关注。

要让服务以计算机帐户身份运行并参与访问控制,只需配置服务使用 Local System 或 Network Service 运行。几种内置服务帐户的默认配置如下:

服务帐户 本地权限 网络身份
Local System 管理员权限 计算机帐户
Network Service 标准权限 计算机帐户
Local Service 标准权限 匿名*

* 在现代 AD 环境中匿名身份一般是禁止登录到网络中的

如果坚持一机一服务的实践,则使用计算机身份运行服务简单方便,是一种值得考虑的选择。然而如果同一台计算机运行多个服务,则使用计算机帐户不利于各个服务间的访问隔离。此时可以考虑使用 AD 提供的组托管服务帐户功能。

使用组托管服务帐户 (gMSA)

简单地理解,组托管服务帐户就是允许为每台计算机分配多个计算机帐户的机制。组托管服务帐户既具备计算机帐户自动维护的优势,又解决了单机托管多个服务时的身份隔离问题。组托管服务帐户还允许不同计算机上的多个服务实例使用同一个帐户登录,避免了服务器集群中独立管理各个服务实例所用帐户的可用性问题。

组托管服务帐户需要 Windows Server 2012 或更高的 Forest Schema 版本。

组托管服务帐户是一种安全、灵活、便利的服务帐户形式,是运行服务时推荐选用的方案。可惜 Microsoft 文档在这方面的文档并不十分清晰,故本文后续将单独讲解组托管服务帐户的原理与使用。

使用用户帐户

除了使用平台提供的服务帐户之外,还可以自行开立一个用户帐户来运行服务。使用用户帐户运行服务有诸多劣势,故通常不推荐使用。例如:

  • 用户帐户需要使用可通过键盘输入的密码,这大大降低了其密码的熵。用户帐户密码熵最高 (94127) 仅为托管服务帐户密码熵 (216 × 256) 的 20%.
  • 若允许多个管理员使用该帐户对服务进行管理,则 Windows 内置的审计能力被掩盖,无法追溯到实际用户。
  • 用户帐户的登录范围更广,且默认支持交互式登录,使用不当很容易被用于提权与横向移动。故需要严控相关用户帐户的访问,这是额外的管理开销。
  • 需要手动执行密码轮替等维护操作,相当麻烦。

为了协助客户减少这种配置、改善安全姿态,2024 年底推出的 Windows Server 2025 提供了内置的迁移功能,以便客户顺利地从这种手动管理的服务帐户迁移到由 AD 自动管理的服务帐户类型。

但部分情况下又不得不使用这种方案。一个典型的场景是,原本设计作为单用户执行的图形应用程序,通过服务包装等方式强行作为服务运行。由于这种应用程序需要交互式登录到图形界面进行配置,而配置又随登录帐户存储,故必须使用服务帐户交互式登录到服务器上完成配置。普通用户帐户是唯一支持这种用法的方案。

组托管服务帐户

以下详细介绍 AD 中组托管服务帐户的原理与使用。

原理

组托管服务帐户的基本操作概念如下:

  1. 管理员在域控制器上创建需要的组托管服务帐户
  2. 域控制器为组托管服务帐户生成一个密码
  3. 管理员配置哪些帐户可以获取该组托管服务帐户的密码
  4. 管理员在应用服务器上部署服务,并指定服务使用组托管服务帐户登录
  5. 服务运行时,操作系统使用计算机帐户,从域控制器中读取组托管服务账户的密码,并使用该密码登录帐户,运行服务
  6. 域控制器定期重新生成密码,实现轮替

其中第 5 步实现了由计算机帐户到组托管服务帐户的转化。这个过程对应用程序是不可见的。为了便于理解,可以把域控制器看成一个通过计算机帐户密码解锁的密码管理器,操作系统使用计算机帐户鉴权后,从密码管理器中获取服务帐户的密码完成登录。

组托管服务帐户的密码并非随机生成的,而是由一个仅域控制器知晓的根密钥,根据时间等信息加盐后,通过密钥派生算法计算得到的。这是因为 AD 是一个多活的分布式数据库,且需要容忍分区离线。随机的实现在这种环境中会引发多个副本中密码不一致的问题,故需要采用具有确定性的算法来规避。

首次使用

前面提到组托管服务帐户的密码是由一个根密钥派生而来的。这个密钥默认不存在,在首次创建组托管服务帐户前需要单独配置。这一操作在域的整个生命周期内仅需执行一次。

以 Domain Admins 或 Enterprise Admins 的身份登录到任一运行 Windows Server 2012 或更高版本的可写的域控制器,运行以下 PowerShell 命令创建根密钥:

1
Add-KdsRootKey -EffectiveImmediately

AD 将额外设置 10 小时的等待期,以避免在其它域控制器完成根密钥的同步前使用。为了保证服务稳定,建议按推荐策略,在等待期满后继续操作。如果希望跳过等待或调整等待时长,则可以改为运行

1
2
# 设置生效时间为 10 小时前,即等待期在现在结束
Add-KdsRootKey –EffectiveTime ((Get-Date).AddHours(-10))

创建组托管服务帐户

创建好根密钥且等待期满后,便可开始创建组托管服务帐户。以 Domain Admins 或具备 msDS-GroupManagedServiceAccount 对象写入权限的身份登录到任一运行 Windows Server 2012 或更高版本的可写的域控制器,运行以下 PowerShell 命令创建组托管服务帐户:

1
New-ADServiceAccount -Name "accountName" -DnsHostName "accountName.domainName.tld" -KerberosEncryptionType AES128,AES256

各参数含义如下:

  • Name
    服务帐户的名称

  • DNSHostName
    采用域名格式的名称

  • KerberosEncryptionType
    使用 Kerberos 协议登录帐户时可用的对称加密算法。现代 AD 环境都支持 AES128,AES256, 如因兼容性原因有必要额外支持 RC4 时,可指定 RC4,AES128,AES256

更多的参数信息可阅读 New-ADServiceAccount 命令的文档

如果后续需要移除该帐户,则可执行:

1
Uninstall-AdServiceAccount "accountName"

配置密码权限

运行以下命令设置可获取组托管服务帐户密码的帐户:

1
Set-ADServiceAccount "accountName" -PrincipalsAllowedToRetrieveManagedPassword Computer1$,Computer2$,User1,User2,Group1,Group2

其中 PrincipalsAllowedToRetrieveManagedPassword 参数指定允许获取托管服务帐户密码的帐户列表。可以直接指定一个或多个帐户;或者为了简化管理,也可以指定一个或多个 AD 安全组,并将需要读取密码的帐户添加到组内。注意计算机帐户的尾缀 $. 允许获取密码的一般只应有允许服务的计算机帐户或包含这些帐户的安全组。

注意将帐户添加到安全组后,需要注销并重新登录相关帐户,新的群组从属才会生效。对于计算机帐户而言,这一般意味着重新启动,或等待一段较长时间后令牌过期。

相应地,可以用 Get-ADServiceAccount 指定获取这一配置:

1
Get-ADServiceAccount "accountName" -Properties PrincipalsAllowedToRetrieveManagedPassword

配置 SPN

如果你的服务需要通过 Windows 集成认证能力接受最终用户的登录请求 (例如开头的身份委托方案),则需要给服务帐户配置 SPN. SPN 是 Kerberos 协议中客户端指定目标服务所用的标识符,包括服务类型、服务域名、实例名、端口等服务特征,其地位可以类比 TLS 协议中的域名。

有关 Kerberos 协议与 SPN 的详情不在本文介绍范围内。若你的服务无需通过 Windows 集成认证能力接受最终用户的登录请求,则 SPN 与你无关,可以跳过这一步;否则需参照服务的开发或部署文档等资料,使用以下指令进行配置。

1
2
3
4
5
6
7
8
# 添加
Set-ADServiceAccount "accountName" -ServicePrincipalNames @{Add=spn1,spn2,spn3}
# 移除
Set-ADServiceAccount "accountName" -ServicePrincipalNames @{Remove=spn1,spn2,spn3}
# 重新设置
Set-ADServiceAccount "accountName" -ServicePrincipalNames @{Replace=spn1,spn2,spn3}
# 清空
Set-ADServiceAccount "accountName" -ServicePrincipalNames $null

同样可以使用 Get-ADServiceAccount 指定获取这一配置:

1
Get-ADServiceAccount "accountName" -Properties ServicePrincipalNames

配置帐户访问权限

服务帐户创建好后,便可以正常为帐户配置 ACL 了。注意组托管服务帐户实质上是计算机帐户,故在引用时需要添加尾缀的 $. 以文件系统的 ACL 配置为例:

另外注意如果使用图形界面配置 ACL, 则计算机帐户与服务帐户默认不在搜索筛选范围内,需要单独勾选:

配置服务

接下来便可以登录到应用服务器上,对服务进行配置。在此之前请确认已将应用服务器的计算机帐户添加至允许读取服务帐户密码的名单内或名单包含的组内。如果使用组来管理,还请另外确认已经重新启动计算机,使群组变更生效。

在 PowerShell 中通过 sc.exe 来创建服务或修改既有服务。以创建新服务为例:

1
sc.exe create "serviceName" start= auto binpath= "\`"C:\Absolute Path\To\Exe\`" " obj= DOMAIN\accountName$ displayname= "Display Name"

注意以下几点:

  • 务必完整输入 sc.exe 的扩展名。这是因为 sc 在默认配置下是 PowerShell Set-Content 指令的别称
  • 参数 = 后面的空格是必要的

各参数含义如下:

  • 服务名 (示例中的 serviceName)
    必须在本地设备上唯一

  • start
    服务启动方式。除自动启动 (auto) 外,还可指定 demand, disabled, delayed-auto

  • binpath
    服务应用程序的绝对路径。出于安全的最佳实践,其取值应当使用引号包裹起来。这在 PowerShell 中会产生奇怪的转义语法:最外层的一对引号 "" 是标记 PowerShell 字符串的引号;内层的 \`" 代表被 sc.exe 接收的一个引号,其中 \ 是 sc 接收的引号转义符号,` 是 PowerShell 需要的转义后续引号的符号," 是参数取值内实际的引号。

  • obj
    登录服务使用的服务帐户。建议使用 NetBIOS 格式 (NETBIOSDOMAIN/account) 指定。注意组托管服务帐户尾缀的 $.

  • displayname
    服务的显示名称,可选。

接下来就可以使用 sc.exe 或其它工具来启动服务了。

如果需要查看或编辑该帐户用户配置文件中的内容 (如 %APPDATA%), 其路径在 %SystemDrive%\Users\accountName$\. 注意你需要有管理权限才能访问其它用户的目录。

延伸阅读