通过证书在10个步骤中确保使用证书对Ansible进行安全的WinRM访问

Ansible正成为当今最重要的配置管理工具之一,如果不是最重要的话。Ansible是一个方便的工具(在大多数情况下是免费的),允许DevOps工程师和系统工程师/管理员以幂等的、基于基础设施即代码的方式构建和维护跨所有环境的基础设施。但是,将其配置为通过WinRM与Windows通信可能是一个挑战。

与许多其他基础设施组件一样,Ansible可以在Windows主机上部署和维护配置状态。Ansible通过WinRM连接到这些Windows主机,尽管他们正在尝试使用SSH

当你为Ansible配置WinRM时,你有几个不同的选项,涵盖了从设置的简易性到安全性影响的范围。很多人选择简便的方法;使用HTTP进行基本身份验证。尽管你省去了涉及证书的额外工作,但除非必须,否则在网络上传输未加密的数据永远不是一个好主意。

在这篇文章中,您将学习如何使用基于证书的身份验证来设置 WinRM 以便 Ansible 使用自签名证书与其通信。

您将在本教程中学到的设置不仅适用于 Ansible 作为客户端。这种基于证书的身份验证同样适用于其他像其他 Windows 主机这样的 WinRM 客户端。

假设

本文将是基于教程的步骤说明。如果您打算按照这里提出的步骤配置 WinRM 以供 Ansible 使用,教程将假设:

  • 您已在 Linux 主机上安装了 Ansible。
  • 您已经根据您的 Windows 主机设置了 Ansible 清单。
  • 您有一个用于管理的 Windows Server 2016 或更高版本。本教程中使用的某些 cmdlet 在较旧版本的 Windows 上无法使用。
  • Windows 主机不在域中。尽管示例是在非域加入的主机上执行的,但这种配置在域加入的主机上仍然可以工作。
  • 您有 RDP 或控制台访问权限到 Windows 主机,并且以本地管理员身份登录。
  • 您熟悉 PowerShell。几乎所有示例都将使用 PowerShell 代码在 Windows 主机上进行更改。

每个部分都将使用依赖于上一个部分的代码片段。一些片段将引用先前定义的变量。如果您计划完全按照此代码进行操作,请确保保持您的控制台处于打开状态。

如果您只想要代码而不需要所有的解释,请随意下载此 GitHub Gist。

为 Ansible 启用 WinRM 的 PowerShell 远程控制

虽然所有运行 Windows Server 2016 或更新版本的服务器都已启用了 PowerShell 远程控制,但确认一下总是一个好主意。

在要管理的 Windows 主机上,以管理员身份打开 PowerShell 控制台,并运行以下代码片段。此代码片段确保 WinRM 服务已启动,并设置为在系统引导时自动启动。

Set-Service -Name "WinRM" -StartupType Automatic
Start-Service -Name "WinRM"

接下来,确保已启用 PowerShell 远程控制,首先检查是否有任何活动会话配置。如果没有,则确保它没有任何可用的监听器。Ansible 的 WinRM 必须至少有一个监听器。如果其中任何一个条件返回空值,请运行 Enable-PSRemoting

if (-not (Get-PSSessionConfiguration) -or (-not (Get-ChildItem WSMan:\localhost\Listener))) {
    ## 使用 SkipNetworkProfileCheck 以便在 Windows 防火墙公共配置文件上可用
    ## 使用 Force 以避免提示是否确定
    Enable-PSRemoting -SkipNetworkProfileCheck -Force
}
Configuring WinRM for Ansible (a listener) via the winrm command

启用基于证书的认证

默认情况下,WinRM 未配置为基于证书的身份验证。您必须通过配置 WSMan 来启用此功能,如下所示。

#region 启用基于证书的身份验证
Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true
#endregion

创建本地用户帐户

要使用基于证书的 WinRM 进行 Ansible 身份验证,您必须将本地用户帐户“映射”到证书。您可以使用本地管理员帐户执行此操作,但最好创建一个特定帐户以便管理更加方便。

以下代码片段将为 WinRM for Ansible 创建一个名为 ansibletestuser 的本地用户帐户,密码为 p@$$w0rd12(如果不存在的话)。为确保其始终处于活动状态,该帐户的密码将永不过期。

$testUserAccountName = 'ansibletestuser'
$testUserAccountPassword = (ConvertTo-SecureString -String 'p@$$w0rd12' -AsPlainText -Force)
if (-not (Get-LocalUser -Name $testUserAccountName -ErrorAction Ignore)) {
    $newUserParams = @{
        Name                 = $testUserAccountName
        AccountNeverExpires  = $true
        PasswordNeverExpires = $true
        Password             = $testUserAccountPassword
    }
    $null = New-LocalUser @newUserParams
}

创建客户端证书

为了安全地使用 WinRM for Ansible,必须拥有两个证书;一个客户端证书和一个服务器证书。

您可以使用同一证书用于客户端/服务器,但我尝试此方法时遇到了问题。在 Ansible 文档和许多其他来源中,您会找到使用 PowerShell 的 New-SelfSignedCert cmdlet 生成客户端证书的说明。尽管此方法可能有效,但我无法轻松地使其正常工作。

要为 Ansible 的 WinRM 创建客户端证书,您必须创建一个私钥和一个公钥。首先通过 SSH 登录到 Ansible 主机,并运行以下 openssl 命令。此命令将在名为 cert_key.pem 的文件中创建一个私钥,并创建一个名为 cert.pem 的公钥。密钥用途将是客户端身份验证 (1.3.6.1.4.1.311.20.2.3),“映射”到您之前创建的本地用户帐户,名为 ansibletestuser

## 这是使用 Ansible 服务器生成的公钥:
cat > openssl.conf << EOL
distinguished_name = req_distinguished_name
[req_distinguished_name]
[v3_req_client]
extendedKeyUsage = clientAuth
subjectAltName = otherName:1.3.6.1.4.1.311.20.2.3;UTF8:ansibletestuser@localhost
EOL
export OPENSSL_CONF=openssl.conf
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -out cert.pem -outform PEM -keyout cert_key.pem -subj "/CN=ansibletestuser" -extensions v3_req_client
rm openssl.conf 

离 Ansible WinRM 身份验证又近了一步!

导入客户端证书

一旦您为 Ansible 的 WinRM 创建了客户端证书,您将需要将其导入到 Windows 主机上的两个证书存储中,以使 Ansible 上的 WinRM 正常工作。为此,首先将 cert.pem 公钥传输到 Windows 主机。下面的示例假定该密钥存在于 C:\cert.pem

一旦您在 Windows 主机上拥有了公共证书,按照下面所示使用 Import-Certificate 将其导入到 受信任的根证书颁发机构受信任的用户 证书存储中。

$pubKeyFilePath = 'C:\cert.pem'

## 将公钥导入到受信任的根证书颁发机构和受信任的用户
$null = Import-Certificate -FilePath $pubKeyFilePath -CertStoreLocation 'Cert:\LocalMachine\Root'
$null = Import-Certificate -FilePath $pubKeyFilePath -CertStoreLocation 'Cert:\LocalMachine\TrustedPeople'

创建服务器证书

为 Ansible 的 WinRM 需要定义一个具有服务器身份验证密钥用途的证书。此证书将存储在 Windows 主机的 LocalMachine\My 证书存储中。使用下面的代码片段创建自签名证书。

$hostname = hostname
$serverCert = New-SelfSignedCertificate -DnsName $hostName -CertStoreLocation 'Cert:\LocalMachine\My'

创建 Ansible WinRM 监听器

一旦您创建了两个证书,现在您必须在 Windows 主机上创建一个 WinRM 监听器。此监听器开始在端口 5986 上监听传入连接。创建后,此监听器接受传入连接,并将尝试使用上面创建的服务器证书对数据进行加密。

PowerShell 远程使用此 WinRM 监听器作为传输一旦进行了身份验证。

在下面的代码片段中,您可以看到一个很好的示例,检查是否存在现有的 HTTPS 监听器。如果找不到使用上面创建的服务器证书的监听器,它将创建一个新的。

## 查找所有 HTTPS 监听器
$httpsListeners = Get-ChildItem -Path WSMan:\localhost\Listener\ | where-object { $_.Keys -match 'Transport=HTTPS' }

## 如果根本没有定义监听器,或者没有配置要与之一起工作的监听器
## 使用所创建的服务器证书,创建一个新的监听器,主题为计算机的主机名
## 并绑定到服务器证书。
if ((-not $httpsListeners) -or -not (@($httpsListeners).where( { $_.CertificateThumbprint -ne $serverCert.Thumbprint }))) {
    $newWsmanParams = @{
        ResourceUri = 'winrm/config/Listener'
        SelectorSet = @{ Transport = "HTTPS"; Address = "*" }
        ValueSet    = @{ Hostname = $hostName; CertificateThumbprint = $serverCert.Thumbprint }
        # UseSSL = $true
    }
    $null = New-WSManInstance @newWsmanParams
}

将客户端证书映射到本地用户帐户

下一步是确保当 Ansible 使用服务器证书连接到 Windows 主机时,然后将执行所有指令作为本地用户。在这种情况下,由 Ansible 执行的所有活动都将使用本地用户帐户ansibletestuser

$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $testUserAccountName, $testUserAccountPassword

## 查找在 Ansible 主机上创建的客户端证书的证书指纹
$ansibleCert = Get-ChildItem -Path 'Cert:\LocalMachine\Root' | Where-Object {$_.Subject -eq 'CN=ansibletestuser'}

$params = @{
	Path = 'WSMan:\localhost\ClientCertificate'
	Subject = "$testUserAccountName@localhost"
	URI = '*'
	Issuer = $ansibleCert.Thumbprint
  Credential = $credential
	Force = $true
}
New-Item @params

允许 WinRM 用于 Ansible 与用户账户控制(UAC)

如果使用本地账户映射证书,还必须将 LocalAccountTokenFilterPolicy 设置为 1,以确保 UAC 不会产生影响。 LocalAccountTokenFilterPolicy 适用于所有本地账户(非域账户),导致您的网络登录成为令牌的有限部分。这将阻止它作为管理员登录,因为默认情况下 WinRM 要求用户是本地管理员。

通过设置 LocalAccountTokenFilterPolicy,您告诉 Windows 不为本地账户的网络登录创建有限令牌,并使用其完整令牌。

$newItemParams = @{
    Path         = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
    Name         = 'LocalAccountTokenFilterPolicy'
    Value        = 1
    PropertyType = 'DWORD'
    Force        = $true
}
$null = New-ItemProperty @newItemParams

在 Windows 防火墙上打开端口 5986

WinRM over HTTPS 使用端口 5986。如果启用了 Windows 防火墙,必须打开此端口。您可以通过运行以下 PowerShell 代码片段来执行此操作。此代码片段过于宽松,允许所有计算机使用它。如果想要进一步锁定,请确保使用 LocalAddress 参数并指定 Ansible 的 IP。

#region 确保防火墙上打开了 WinRM 5986
 $ruleDisplayName = 'Windows Remote Management (HTTPS-In)'
 if (-not (Get-NetFirewallRule -DisplayName $ruleDisplayName -ErrorAction Ignore)) {
     $newRuleParams = @{
         DisplayName   = $ruleDisplayName
         Direction     = 'Inbound'
         LocalPort     = 5986
         RemoteAddress = 'Any'
         Protocol      = 'TCP'
         Action        = 'Allow'
         Enabled       = 'True'
         Group         = 'Windows Remote Management'
     }
     $null = New-NetFirewallRule @newRuleParams
 }
 #endregion

将本地用户添加到管理员组

您可能会问为什么本教程没有早些将本地用户添加到管理员组。原因是,当您尝试将客户端证书映射到用户时,用户帐户不能是本地管理员。

运行以下代码片段将ansibletestuser本地用户帐户添加到管理员组。

## 将本地用户添加到管理员组。 如果此步骤没有执行,则 Ansible 会显示“AccessDenied”错误
Get-LocalUser -Name $testUserAccountName | Add-LocalGroupMember -Group 'Administrators'

结论

如果您按照所示的每个步骤进行操作,现在应该能够针对 Windows 主机执行 Ansible 命令。 使用win_shell模块执行测试。 如果此模块成功,则已成功配置了 Ansible 和基于证书的身份验证!

Source:
https://adamtheautomator.com/winrm-for-ansible/