0.はじめに
かなり久しぶりに Ansible を利用して Windows Server を触る機会がありましたので、備忘も兼ねて記事にします。
具体的には、Azure Cloudshell 上 の Ansible より、Azure 上の Windows Server 2019 へ WinRM( https:5986 port )経由で接続し、win_shell モジュールを利用してホスト名や、簡易な powershell を実行するまでを紹介します。
また、WinRM の認証方法として、Certificate を利用します。
本記事の実行結果等は執筆時点(2022年3月16日)での内容となります
検証用途での実行環境として便宜上、Azure Cloudshell を利用しています。
利用にあたっては、セキュリティ要件等十分にご留意ください。
1.前提条件
- コントロール対象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.事前準備
事前準備として、以下を実施します。
- Certificate 認証で、ローカルユーザーにマッピングする為の証明書の作成
- Windows Server(コントロール対象 VM )側での各種設定
3.1.Certificate認証で、ローカルユーザーにマッピングする為の証明書の作成
はじめに、Azure Cloudshell 上より、Ansible 公式ドキュメントを参考に
OpenSSL を利用して Certificate 認証で利用する証明書のペアを作成します。
注意
マッピング対象のユーザー名azureuser
は実環境のものへ置き換えてください
# 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(コントロール対象VM)側での各種設定
次に Azure 上の Windows Server へ RDP 等で接続し以下を設定します。
- WinRM等の有効化
- 証明書とローカルアカウントの紐付け
- WinRMのcertificate認証を有効化
3.2.1. WinRM等の有効化
Ansible 公式ドキュメント
に則り、https( port:5896 )で WinRM 接続可能なように、Windows Server上 の Powershell(以降 Powershell は全て Run as administrator
: 管理者モードで起動) より Ansible 設定用ConfigureRemotingForAnsible.ps1
を実行します。
$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 へアップロードし、Desktop 上で ZIP ファイルを解凍しておきます。
-
C:\Users\azureuser\Desktop\pem\
へ証明書のペア(cert_key.pem
,cert.pem
)を配置
次に、以下 powershell を実行し、証明書ペアのインポート、ローカルアカウントとのマッピングを行います。(Ansible 公式ドキュメント)
注意
マッピング対象のユーザー名azureuser
とパスワードP@ssword1234
は実環境のものへ置き換えてください
$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 の Certificate 認証を有効化
最後に powershell で WinRM の Certificate 認証を有効化し、WinRM の Listener 及び設定状況を確認します。
以上で 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 等Sample コード
今回の検証で利用する Playbook 等は以下の通りです。
Windows Server 2019 へ WinRM( https:5986 port )経由で接続し
認証方法として、Certificate を利用するインベントリファイルと
win_shell モジュールを利用してホスト名や、簡易な powershell を実行するサンプルです。
注意
YOUR_VM_IP_ADDRESS
(Windows 対象 VM のグローバルIPアドレス)及びYOUR_CERT_FULL_PATH
(例: /home/yourname/.ssh/等)については、適宜自分の環境へ置き換えてください。
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/
7.さいごに
-
今回は、本当に久しぶりの Ansible ということもあり、公式ドキュメントや、過去に書いた自分の記事等を参照しながらと、手探りでの検証となりました。。。
-
また、以前よりも、情報が増えたとはいえ、未だに Ansible * Windows の事例は、やはり少ないな〜という印象でした。
-
なお、今回初めて、Windows Server への Certificate 認証を試してみて
事前準備として証明書ペアの作成と、その後のローカルユーザーとのペアリング等、慣れないと分かりづらい部分も多く(公式ドキュメントの通りではありますが...)、敷居が高く感じましたが、設定後は、Linuxと同様に、インベントリファイルへ証明書の配置場所のみの記載で良い(ユーザー名やパスワードの記載が不要)というのは、コード管理等のセキュリティ上の観点からも良さそうと感じました。
注意
Ansible 公式ドキュメントの認証オプションへ記載の通り、
Certificate 認証については、ローカルユーザーのみ対応となっており、ドメインユーザーでの利用はできません。
- その他、Azure Cloudshell 上で Ansible がデフォルトで利用可能なのは便利でした。
以上です。