使用Ansible通过证书认证连接和控制位于Azure上的Windows Server 2019
首先
最近有机会使用 Ansible 并触摸 Windows Server,所以为了备忘起见,我决定写一篇文章。具体来说,我将使用 Azure Cloudshell 上的 Ansible 通过 WinRM(通过https:5986端口)连接到 Azure 上的 Windows Server 2019,并介绍如何使用 win_shell 模块执行主机名和简单的 PowerShell。此外,我将使用证书作为 WinRM 的身份验证方法。
在使用之前,请务必注意安全要求等。
前提条件
-
- コントロール対象VM等は以下の通りAzure上に作成済
Windows (Windows Server 2019 Datacenter)
パブリック IP アドレス付与
ユーザーはazureuser、パスワードはP@ssword1234
Azure NSG( network security group )にて Ansible 実行環境より、対象 VM へ TCP:5986 の接続を許可
curl ifconfig.io 等で送信元のグローバル IP を取得し、事前に接続可能なように設定
Ansible 及び OpenSSL 実行環境は、Azure Cloudshell を利用
Ansible や、pywinrm 等のインストールについては、この記事では触れません
WinRM の認証方法として、Certificate を利用
2. 运行环境
$ ansible --version
ansible 2.10.2
config file = None
configured module search path = ['/home/takuya/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /opt/ansible/lib/python3.7/site-packages/ansible
executable location = /opt/ansible/bin/ansible
python version = 3.7.3 (default, Jul 25 2020, 13:03:44) [GCC 8.3.0]
$
$ openssl version
OpenSSL 1.1.1d 10 Sep 2019
$
3.预先准备
在事前准备阶段,我们将进行以下操作。
- 在Windows Server(受控虛擬機)上進行各種設定,以創建用於將憑證映射到本地用戶的認證證書。
3.1. 创建用于将证书映射到本地用户的证明书,以进行认证。
首先,在Azure Cloud Shell中,根据Ansible官方文档的参考,使用OpenSSL创建用于证书认证的证书对。
# Set the name of the local user that will have the key mapped to
USERNAME="azureuser"
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:$USERNAME@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=$USERNAME" -extensions v3_req_client
rm openssl.conf
# 証明書のペア(`cert_key.pem`,`cert.pem`)が作成されていることを確認
ls -altr
# 次の Windows Server 側でのマッピングで利用するため、pem ファイルをZIPで圧縮して、Cloudshell からダウンロードしておく
zip pem.zip *.pem
$ USERNAME="azureuser"
$
$ 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:$USERNAME@localhost
> EOL
$
$ export OPENSSL_CONF=openssl.conf
takuya@Azure:~/wwwwork$ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -out cert.pem -outform PEM -keyout cert_key.pem -subj "/CN=$USERNAME" -extensions v3_req_client
Generating a RSA private key
...............+++++
............+++++
writing new private key to 'cert_key.pem'
-----
$ rm openssl.conf
$ ls -altr
total 16
drwxr-xr-x 39 takuya takuya 4096 Mar 16 03:29 ..
-rw-r--r-- 1 takuya takuya 1099 Mar 16 03:38 cert.pem
-rw------- 1 takuya takuya 1708 Mar 16 03:38 cert_key.pem
drwxr-xr-x 2 takuya takuya 4096 Mar 16 03:38 .
$ zip pem.zip *.pem
adding: cert_key.pem (deflated 23%)
adding: cert.pem (deflated 25%)
$
3.2 在 Windows Server(受控制的虚拟机)上进行各种设置。
下一步,您可以使用RDP等方式,连接到Azure上的Windows Server,并进行以下设置。
-
- 启用WinRM
-
- 关联证书和本地账户
- 启用WinRM的证书认证
3.2.1. 激活WinRM等
根据 Ansible 官方文档,在 Windows Server 上的 Powershell(以下简称 Powershell,必须以管理员模式运行)中运行 ConfigureRemotingForAnsible.ps1,以使其能通过 https(端口:5896)进行 WinRM 连接可用。
$url = "https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:temp\ConfigureRemotingForAnsible.ps1"
(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)
powershell.exe -ExecutionPolicy ByPass -File $file
winrm enumerate winrm/config/Listener
3.2.2. 证明书与本地账户的关联
接下来,将已创建的证书配对 ZIP 文件pem.zip上传至Windows Server,并在桌面上解压ZIP文件。
C:\Users\azureuser\Desktop\pem\へ証明書のペア(cert_key.pem,cert.pem)を配置
接下来,运行以下 PowerShell 命令,对证书对进行导入,并进行与本地帐户的映射。(Ansible 官方文档)
$cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import("C:\Users\azureuser\Desktop\pem\cert.pem")
$store_name = [System.Security.Cryptography.X509Certificates.StoreName]::Root
$store_location = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
$store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $store_name, $store_location
$store.Open("MaxAllowed")
$store.Add($cert)
$store.Close()
$cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import("C:\Users\azureuser\Desktop\pem\cert.pem")
$store_name = [System.Security.Cryptography.X509Certificates.StoreName]::TrustedPeople
$store_location = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
$store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $store_name, $store_location
$store.Open("MaxAllowed")
$store.Add($cert)
$store.Close()
$username = "azureuser"
$password = ConvertTo-SecureString -String "P@ssword1234" -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $password
$thumbprint = (Get-ChildItem -Path cert:\LocalMachine\root | Where-Object { $_.Subject -eq "CN=$username" }).Thumbprint
New-Item -Path WSMan:\localhost\ClientCertificate `
-Subject "$username@localhost" `
-URI * `
-Issuer $thumbprint `
-Credential $credential `
-Force
PS C:\Users\azureuser> $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
>> $cert.Import("C:\Users\azureuser\Desktop\pem\cert.pem")
>> $store_name = [System.Security.Cryptography.X509Certificates.StoreName]::Root
>> $store_location = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
>> $store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $store_name, $store_location
>> $store.Open("MaxAllowed")
>> $store.Add($cert)
>> $store.Close()
>>
PS C:\Users\azureuser>
PS C:\Users\azureuser>
>> $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
>> $cert.Import("C:\Users\azureuser\Desktop\pem\cert.pem")
>> $store_name = [System.Security.Cryptography.X509Certificates.StoreName]::TrustedPeople
>> $store_location = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
>> $store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $store_name, $store_location
>> $store.Open("MaxAllowed")
>> $store.Add($cert)
>> $store.Close()
PS C:\Users\azureuser>
PS C:\Users\azureuser> $username = "azureuser"
>> $password = ConvertTo-SecureString -String "P@ssword1234" -AsPlainText -Force
>> $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $password
PS C:\Users\azureuser>
PS C:\Users\azureuser> $thumbprint = (Get-ChildItem -Path cert:\LocalMachine\root | Where-Object { $_.Subject -eq "CN=$username" }).Thumbprint
>> New-Item -Path WSMan:\localhost\ClientCertificate `
>> -Subject "$username@localhost" `
>> -URI * `
>> -Issuer $thumbprint `
>> -Credential $credential `
>> -Force
>>
WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\ClientCertificate
Type Keys Name
---- ---- ----
Container {URI=*, Issuer=F6652B7E0DD09FB81... ClientCertificate_1870013346
3.2.3. 启用 WinRM 的证书认证。
最后,通过PowerShell启用WinRM的证书认证并确认WinRM的监听器和配置情况。至此,Windows Server的准备工作完成。
winrm set winrm/config/service/Auth '@{Certificate="true"}'
winrm enumerate winrm/config/Listener
winrm get winrm/config
PS C:\Users\azureuser> winrm set winrm/config/service/Auth '@{Certificate="true"}'
Auth
Basic = true
Kerberos = true
Negotiate = true
Certificate = true
CredSSP = false
CbtHardeningLevel = Relaxed
PS C:\Users\azureuser> winrm enumerate winrm/config/Listener
Listener
Address = *
Transport = HTTP
Port = 5985
Hostname
Enabled = true
URLPrefix = wsman
CertificateThumbprint
ListeningOn = 127.0.0.1, 172.18.0.4, ::1, fe80::10de:125d:29e4:eb5a%6
Listener
Address = *
Transport = HTTPS
Port = 5986
Hostname = win001
Enabled = true
URLPrefix = wsman
CertificateThumbprint = 624371D7DBFE6B80FB902C379155B84E2AD2FBCA
ListeningOn = 127.0.0.1, 172.18.0.4, ::1, fe80::10de:125d:29e4:eb5a%6
PS C:\Users\azureuser> winrm get winrm/config
Config
MaxEnvelopeSizekb = 500
MaxTimeoutms = 60000
MaxBatchItems = 32000
MaxProviderRequests = 4294967295
Client
NetworkDelayms = 5000
URLPrefix = wsman
AllowUnencrypted = false
Auth
Basic = true
Digest = true
Kerberos = true
Negotiate = true
Certificate = true
CredSSP = false
DefaultPorts
HTTP = 5985
HTTPS = 5986
TrustedHosts
Service
RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
MaxConcurrentOperations = 4294967295
MaxConcurrentOperationsPerUser = 1500
EnumerationTimeoutms = 240000
MaxConnections = 300
MaxPacketRetrievalTimeSeconds = 120
AllowUnencrypted = false
Auth
Basic = true
Kerberos = true
Negotiate = true
Certificate = true
CredSSP = false
CbtHardeningLevel = Relaxed
DefaultPorts
HTTP = 5985
HTTPS = 5986
IPv4Filter = *
IPv6Filter = *
EnableCompatibilityHttpListener = false
EnableCompatibilityHttpsListener = false
CertificateThumbprint
AllowRemoteAccess = true
Winrs
AllowRemoteShellAccess = true
IdleTimeout = 7200000
MaxConcurrentUsers = 2147483647
MaxShellRunTime = 2147483647
MaxProcessesPerShell = 2147483647
MaxMemoryPerShellMB = 2147483647
MaxShellsPerUser = 2147483647
PS C:\Users\azureuser>
4. Ansible Playbook及其他示例代码
本次验证中使用的Playbook等如下所示。
通过WinRM(使用https:5986端口)连接到Windows Server 2019,在认证方法上,使用证书作为库存文件,并利用win_shell模块执行主机名和简单的powershell命令的示例。
win:
hosts:
win001:
ansible_host: YOUR_VM_IP_ADDRESS
vars:
ansible_winrm_server_cert_validation: ignore
ansible_connection: winrm
# Below are the settings for the certificate
ansible_winrm_cert_pem: /YOUR_CERT_FULL_PATH/cert.pem
ansible_winrm_cert_key_pem: /YOUR_CERT_FULL_PATH/cert_key.pem
ansible_winrm_transport: certificate
# Below are the settings for the ntlm
# ansible_winrm_transport: ntlm
# ansible_user: YOUR_VM_USERNAME
# ansible_password: YOUR_VM_PASSWORD
# ansible_winrm_scheme: https
# ansible_port: 5986
- hosts: win
gather_facts: false
tasks:
- win_shell: "{{ item }}"
with_items:
- 'hostname'
- |
$date = Get-date
$date
register: command_results
- debug: var=command_results.results
[defaults]
host_key_checking=false
validate_certs=False
log_path=./ansible.log
5. 执行结果(示例)
在 Azure Cloudshell 中,准备示例代码并创建了已配对的证书文件(’cert_key.pem’,’cert.pem’),将它们放置在 Ansible 的清单文件 inventory.yml 中的 YOUR_CERT_FULL_PATH(例如:/home/yourname/.ssh/等)位置。
以下是从Cloudshell中执行ansible-playbook -i inventory.yml demo-win.yml的示例结果。
$ansible-playbook -i inventory.yml demo-win.yml
PLAY [win] **************************************************************************************************************************************************************************************
TASK [win_shell] ********************************************************************************************************************************************************************************
changed: [win001] => (item=hostname)
changed: [win001] => (item=$date = Get-date
$date
)
TASK [debug] ************************************************************************************************************************************************************************************
ok: [win001] => {
"command_results.results": [
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "hostname",
"delta": "0:00:00.421907",
"end": "2022-03-17 02:11:15.905737",
"failed": false,
"item": "hostname",
"rc": 0,
"start": "2022-03-17 02:11:15.483829",
"stderr": "",
"stderr_lines": [],
"stdout": "win001\r\n",
"stdout_lines": [
"win001"
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "$date = Get-date\n$date",
"delta": "0:00:00.421894",
"end": "2022-03-17 02:11:18.123249",
"failed": false,
"item": "$date = Get-date\n$date\n",
"rc": 0,
"start": "2022-03-17 02:11:17.701354",
"stderr": "",
"stderr_lines": [],
"stdout": "\r\nThursday, March 17, 2022 2:11:18 AM\r\n\r\n\r\n",
"stdout_lines": [
"",
"Thursday, March 17, 2022 2:11:18 AM",
"",
""
]
}
]
}
PLAY RECAP **************************************************************************************************************************************************************************************
win001 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$
6. 参考文件
以下是参考文件
-
- https://docs.ansible.com/ansible/2.9_ja/installation_guide/intro_installation.html
-
- https://docs.ansible.com/ansible/2.9_ja/user_guide/windows_setup.html
-
- https://docs.ansible.com/ansible/2.9_ja/user_guide/windows_winrm.html
-
- https://docs.ansible.com/ansible/2.9_ja/user_guide/windows_usage.html
-
- https://docs.microsoft.com/ja-jp/troubleshoot/windows-client/system-management-components/errors-when-you-run-winrm-commands
- https://docs.microsoft.com/ja-jp/azure/developer/ansible/
最后
今回は、本当に久しぶりの Ansible ということもあり、公式ドキュメントや、過去に書いた自分の記事等を参照しながらと、手探りでの検証となりました。。。
また、以前よりも、情報が増えたとはいえ、未だに Ansible * Windows の事例は、やはり少ないな〜という印象でした。
なお、今回初めて、Windows Server への Certificate 認証を試してみて
事前準備として証明書ペアの作成と、その後のローカルユーザーとのペアリング等、慣れないと分かりづらい部分も多く(公式ドキュメントの通りではありますが…)、敷居が高く感じましたが、設定後は、Linuxと同様に、インベントリファイルへ証明書の配置場所のみの記載で良い(ユーザー名やパスワードの記載が不要)というのは、コード管理等のセキュリティ上の観点からも良さそうと感じました。
- その他、Azure Cloudshell 上で Ansible がデフォルトで利用可能なのは便利でした。
以上就是。