使用 Integrated Security 时,如何以编程方式检查给定的 AD 组托管服务帐户是否可以连接到 SQL 服务器?

How do I programmatically check whether a given AD Group Managed Service Account can connect to SQL Server when using Integrated Security?

在开始讨论正在发生的事情之前,我会为我的问题提供一些背景知识,希望能提供一些关于我正在尝试做的事情的见解。然后我将讨论我遇到的实际问题。

背景

我目前正在开发配置 Windows 服务、部署 SQL 服务器数据库、IIS 网站和 MSMQ 队列的 C# 应用程序,以设置企业 Web 应用程序。在 99% 的情况下,Web 应用程序安装在一台 Windows 服务器机器(2012 及以后)上,而数据库部署在单独的 Windows 服务器机器(2012 及以后)上负责 运行ning SQL 服务器(2012 及更高版本)。由于这是一个企业应用程序,我一直在研究 运行 在 组托管服务帐户 (GMSA) 下的服务和 IIS 应用程序池。对于那些偶然发现这个问题的好奇的灵魂,GMSA 是一种特殊的域帐户,可用于 运行 跨多个 Windows 服务器的服务和 IIS 应用程序池的密码完全由 Active Directory 管理(密码 strength/length,密码更改频率等...)。因此,对于为我们的客户管理此应用程序的域 administrator/IT 部门,使用 GMSA 解决了一些主要的难题。有关 GMSA 的更多信息,请参阅:https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/jj128431(v=ws.11)

手头的任务

鉴于我想让我们的客户 运行 使用 GMSA 的应用程序,我必须执行以下操作:

  1. 测试以确保 GMSA 配置为在本地计算机上使用
  2. 测试以确保 GMSA 可以访问 SQL 服务器
  3. 为 GMSA 配置SQL 服务器权限
  4. 部署 运行 Windows 服务和 IIS 应用程序池作为 GMSA

我试过的

通过 运行 宁以下 Powershell commandlet,我知道 GMSA 在 IIS Web 服务器和 SQL 服务器计算机上正确设置。 returns 如果计算机帐户可以访问 GMSA 的密码,则为真。

Test-ADServiceAccount -Identity myGMSA_svc

目前,我一直在努力找出#2。对于普通域用户帐户,我可以通过导入 Win32 API 的 LogonUser 函数、创建令牌句柄并使用 WindowsIdentity.Impersonate:

来模拟用户
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;


[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

public SafeHandle CreateSafeHandle(string domain, string username, string password)
{
    SafeTokenHandle safeTokenHandle;
    bool logonSuccess = LogonUser(username, domain, password, LOGON32.LOGON32_LOGON_INTERACTIVE, LOGON32.LOGON32_PROVIDER_DEFAULT, out safeTokenHandle);

    if (!logonSuccess)
    {
        int win32Error = Marshal.GetLastWin32Error();
        throw new Win32Exception(win32Error);
    }

    return safeTokenHandle;
}

private WindowsImpersonationContext ImpersonateUser(string domain, string username, string password)
{
    try
    {
        _impersonatedUserToken = _safeHandleFactory.CreateSafeHandle(domain, username, password);
        return WindowsIdentity.Impersonate(_impersonatedUserToken.DangerousGetHandle());
    }
    catch (Win32Exception e)
    {
        Log.Error(e);
        throw new Exception(e.Message, e);
    }
}


public bool CanConnect(string domain, string username, string password, string connectionString)
{
    try
    {
        using (WindowsImpersonationContext impersonatedUser = ImpersonateUser(domain, username, password))
        {
            var builder = new SqlConnectionStringBuilder(connectionString) { InitialCatalog = "master" };
            using (var connection = new SqlConnection(builder.ToString()))
            {
                connection.Open();
                using (var command = new SqlCommand(SqlProductVersionQuery, connection))
                using (var query = command.ExecuteReader())
                {
                    var databaseVersionStr = query.GetFieldValue<string>(0);
                    var databaseVersion = new Version(databaseVersionStr);
                }
            }
        }
        return true;
    }
    catch
    {
        return false;
    }
}

我尝试对 LogonUser 调用进行以下修改:

LogonUser(username, domain, password, LOGON32_LOGON_SERVICE, LOGON32.LOGON32_PROVIDER_DEFAULT, out safeTokenHandle);
LogonUser(username, domain, password, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_WINNT50, out safeTokenHandle);

无论哪种情况,我都会收到以下错误消息:

System.ComponentModel.Win32Exception (0x80004005): The user name or password is incorrect

这引出了我的实际问题:

使用 Integrated Security 时如何检查 GMSA 是否可以连接到 SQL 服务器?

如果我是对的,您需要检查给定用户是否有权访问 SQL 实例/数据库。如果是这种情况,您可以通过以下 SQL 查询来完成。

USE [master]

SELECT name 
FROM [sys].[server_principals]
WHERE name = N'domainName\userName';

您可以想出一个内联函数来接受用户名作为参数和 return 一个指示状态的布尔值。