AnsibleでWindows Serverを操作するには

Ansibleを使ってWindows Serverを操作してみたので、その時のメモを引っ張り出して作成いたしました。
内容は、AnsibleでWindows Serverを操作するための準備作業です。(`win_ping`を使った動作確認をします)

「Ansibleでサーバを管理してみたい」とお考えの場合、WindowsはLinuxほど簡単に行かない部分があることをご認識いただけたらと思います。

[公式ドキュメント](https://docs.ansible.com/ansible/latest/user_guide/windows.html)を参考としております。最新の情報などは、こちらから確認できると思います。
また、初期設定等が行える公式スクリプト[(ConfigureRemotingForAnsible.ps1)](https://github.com/ansible/ansible/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1)が用意されていますが、こちらの記事では使用しておりません。
（ちなみに、Linuxの場合はsshが通ればほぼ使えるはず）
Linuxの前提条件

 - ssh接続が可能
 - Python 2 (version 2.6以上) or Python 3 (version 3.5以上)




# 検証環境
- Control node：Amazon Linux
 - ansible： 2.9.6
- Managed node：Windows Server 2012

# AnsibleでWindows Serverを管理するための前提条件
以下の条件を満たす必要があります。

- Windows Server
 - 2008, 2008R2, 2012, 2012R2, 2016, 2019
- PowerShell
 - 3.0以上
- .NET
 - 4.0以上

```powershell:各種バージョン確認
# Windows Serverバージョン
PS> wmic os get caption

# PowerShellバージョン
PS> $PSVersionTable

# .NETバージョン
PS> reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"
```


Windows Server 2008/2008R2の場合、標準のPowershell、.Netのバージョンは条件を満たしてないため、別途バージョンアップが必要になります。
Windows Server 2012以降の場合、標準のPowerShell、.NETのバージョンは条件を満たしています。
調べると、Windows 2008/2008R2は`2020/01`でサポート終了しているみたいですね。
2008でAnsibleを動かしたいということはほぼないと思いますが、そういった場合はまずはサポート対象のOSに切り替えたほうがgoodだと思いました。

# Windows Server設定
Windows Serverの設定を行っていきます。

AnsibleでWindowsを操作するためには、WinRMを設定する必要があります。
 > WinRMとは、Windows Remote Management(Windows リモート管理)

```powershell:WinRMの状態確認
PS> Get-Service | Where-Object { $_.Name -eq "WinRM" } | ft -AutoSize

Status  Name  DisplayName
------  ----  -----------
Running WinRM Windows Remote Management (WS-Management)
```
Windows Serverはデフォルトで起動(Running)していたため、サービス起動設定は不要でした。

続いて、WinRMのListnerの設定を行います。
接続方法として`HTTP(5985)`と、`HTTPS(5986)`を使用した場合がありますので、必要に応じて確認ください。

## HTTP接続編(5985)

### WinRM設定
Windows Server 2012は、デフォルトでHTTPのListenerが設定されていました。
5985ポートでHTTPのListen設定が確認できます。


```powershell:WinRM設定確認
PS> winrm enumerate winrm/config/Listener

Listener
    Address = *
    Transport = HTTP
    Port = 5985
    Hostname
    Enabled = true
    URLPrefix = wsman
    CertificateThumbprint
    ListeningOn = 10.8.0.10, 127.0.0.1, ::1, fe80::5efe:10.8.0.10%14, fe80::917:f75:5272:4a68%12
```

次に、認証方法を確認します。



```powershell:WinRM設定確認
PS> winrm get winrm/config/service

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 = false
        Kerberos = true
        Negotiate = true
        Certificate = false
        CredSSP = false
        CbtHardeningLevel = Relaxed
    DefaultPorts
        HTTP = 5985
        HTTPS = 5986
    IPv4Filter = *
    IPv6Filter = *
    EnableCompatibilityHttpListener = false
    EnableCompatibilityHttpsListener = false
    CertificateThumbprint
    AllowRemoteAccess = true
```

HTTPの場合は、Basic認証の許可、平文の許可をする必要があります。

### Basic認証の許可、平文の許可
```powershell:HTTPにおけるWinRM設定変更
# Basic認証の許可
PS> winrm set winrm/config/service/auth '@{Basic="true"}'
Basic = true

# 平文の許可
PS> winrm set winrm/config/service '@{AllowUnencrypted="true"}'
AllowUnencrypted = true
```

### Ansible実行
それではAnsibleからWindows Serverへの動作確認をします。
インベントリの内容は下記です。

```:Inventoryファイル
# file: hosts
[windows_server]
10.8.0.10

[windows_server:vars]
ansible_user=Administrator
ansible_password=password
ansible_ssh_port=5985
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
```

```terminal:win_pingの実行
$ ansible windows_server -i hosts -m win_ping

10.8.0.10 | UNREACHABLE! => {
    "changed": false,
    "msg": "plaintext: HTTPConnectionPool(host='10.8.0.10', port=5985): Max retries exceeded with url: /wsman (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x7feafc148bd0>, 'Connection to 10.8.0.10 timed out. (connect timeout=30)'))",
    "unreachable": true
}
```

うまくいきません...。`-v`オプションで詳細な出力をしてみたいと思います。

```terminal:win_pingの実行
$ ansible windows_server -i hosts -m win_ping -vvv

(略)
Pipelining is enabled.
<10.8.0.10> ESTABLISH WINRM CONNECTION FOR USER: Administrator on PORT 5985 TO 10.8.0.10
10.8.0.10 | UNREACHABLE! => {
    "changed": false,
    "msg": "plaintext: HTTPConnectionPool(host='10.8.0.10', port=5985): Max retries exceeded with url: /wsman (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x7f88e4a48cd0>, 'Connection to 10.8.0.10 timed out. (connect timeout=30)'))",
    "unreachable": true
}
```
`<10.8.0.10> ESTABLISH WINRM CONNECTION FOR USER: Administrator on PORT 5985 TO 10.8.0.10`
こちらでログが止まります。コネクションが張れないようですね。
接続のタイムアウトだと予想し、FireWall設定を見直しました。

今回の環境(AWS上のWindows Server 2012)では、デフォルト設定されていた以下のFireWallを変更しました。

 - DisplayName : Windows Remote Management (HTTP-In) 　(注)Profileが`Public`のもの
  - `RemoteIP`を`LocalSubnet`→`Any`へ変更

<img width="1712" alt="スクリーンショット 2020-04-22 2.24.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591840/9e5e9873-028d-c4ac-ec1d-720a480cc550.png">

```powershell:FireWallのルール確認
PS> netsh advfirewall firewall show rule name="Windows Remote Management (HTTP-In)"

Rule Name:                            Windows Remote Management (HTTP-In)
----------------------------------------------------------------------
Enabled:                              Yes
Direction:                            In
Profiles:                             Public
Grouping:                             Windows Remote Management
LocalIP:                              Any
RemoteIP:                             LocalSubnet
Protocol:                             TCP
LocalPort:                            5985
RemotePort:                           Any
Edge traversal:                       No
Action:                               Allow
```
それでは再度実行に移ります。

```terminal:win_pingの実行
$ ansible windows_server -i hosts -m win_ping

10.8.0.10 | SUCCESS => {
    "changed": alse,
    "ping": "pong"
}
```

成功しました。


## HTTPS接続編(5986)
Windows Serverで自己証明書を作成したいと思います。

参考
[公的に署名された証明書なしで WinRM サービスまたは HTTPS を構成する](https://help.f-secure.com/product.html?business/radar/3.0/ja/task_8772A6A76D994406B4809EB264EB51EE-3.0-ja)
[New-SelfSignedCertificate (Windows Server 2016)](https://docs.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=win10-ps)
[New-SelfSignedCertificate (Windows Server 2012)](https://docs.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=winserver2012-ps)



###自己証明書作成
HTTPS用の自己証明書を作成します。

```powershell:自己証明書作成(New-SelfSignedCertificate)
PS> $cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "HOSTNAME"
```

上記コマンドで作成することができますが、証明書の有効期限が1年になります。
 有効期限を延長して作りたい場合はオプション(`-NotAfter`)を付与ください。
※ただし `New-SelfSignedCertificate` の `-NotAfter`オプションが使用できるのは、Windows Server 2016からでした。Windows Server 2012でやりたい場合は、`Certreq`を使用しましょう。（2016のドキュメントを見ていたため、結構ハマってました..）

```powershell:自己証明書作成(certreq)
PS> C:\Windows\System32\certreq.exe -new cert.txt
```
cert.txtファイルに記載するパラメータで指定できるみたいです。

 - `ValidityPeriodUnits`
 - `ValidityPeriod`

```cert.txtのサンプル
; cert.txt
[NewRequest]

; At least one value must be set in this section
Subject = "CN=HOSTNAME"
KeyLength = 2048
Exportable = TRUE
KeyAlgorithm = RSA
MachineKeySet = true
RequestType = Cert
ValidityPeriodUnits = 1
ValidityPeriod = days

[EnhancedKeyUsageExtension] 
OID=1.3.6.1.5.5.7.3.1
OID=1.3.6.1.5.5.7.3.2
```

作成した自己証明書を確認します。

```powershell:自己証明書確認
PS> Get-Childitem -Path Cert:\LocalMachine\My -Recurse

    Directory: Microsoft.PowerShell.Security\Certificate::LocalMachine\My


Thumbprint                                Subject
----------                                -------
2B7B061E276307484DC5D1D133697DF98C62B5B7  CN=HOSTNAME

```
ちなみに、下記コマンドのように、`fl`を付与すると有効期限などの値が確認できます。

```powershell:自己証明書確認
PS> Get-Childitem -Path Cert:\LocalMachine\My -Recurse | fl

Subject      : CN=HOSTNAME
Issuer       : CN=HOSTNAME
Thumbprint   : ****************************************
FriendlyName :
NotBefore    : 5/03/2020 4:14:16 PM
NotAfter     : 5/03/2020 4:24:16 PM
Extensions   : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid}
```

### WinRM設定
HTTPS (5986) の設定を行います。

```powershell:HTTPSのWinRM設定
PS> New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $cert.Thumbprint -Force

   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Listener

Type            Keys                                Name
----            ----                                ----
Container       {Transport=HTTPS, Address=*}        Listener_1305953032
```

certreqで自己証明書を作成した場合は、コマンドの`$cert.Thumbprint`部分を`"{自己証明書確認で確認したThumbprint}"` に変更して下さい。

```powershell:WinRM設定確認
PS> winrm enumerate winrm/config/Listener
Listener
    Address = *
    Transport = HTTPS
    Port = 5986
    Hostname
    Enabled = true
    URLPrefix = wsman
    CertificateThumbprint = 3A7B061E276307484DC5D1D133697DF98C62B5B7
    ListeningOn = 10.8.0.10, 127.0.0.1, ::1, fe80::5efe:10.8.0.10%14, fe80::68a5:c889:ade8:b530%12
```
設定したHTTPSが確認できました。

### Basic認証の許可
HTTPSの場合も Basic認証を`true`とする必要がありました。
平文の許可は`false`でも問題ありません。

```powershell:HTTPSにおけるWinRM設定変更
# Basic認証の許可
PS> winrm set winrm/config/service/auth '@{Basic="true"}'
Basic = true
```

### FireWall設定
必要に応じてFireWallの設定をします。
以下コマンドでFireWallルールを追加することが可能です。今回は複雑な設定はしておりません。

```powershell:FireWallの設定
PS> New-NetFirewallRule -DisplayName "Windows Remote Management (HTTPS-In)" -Name "Windows Remote Management (HTTPS-In)" -Profile Any -LocalPort 5986 -Protocol TCP

Name                  : Windows Remote Management (HTTPS-In)
DisplayName           : Windows Remote Management (HTTPS-In)
Description           :
DisplayGroup          :
Group                 :
Enabled               : True
Profile               : Any
Platform              : {}
Direction             : Inbound
Action                : Allow
EdgeTraversalPolicy   : Block
LooseSourceMapping    : False
LocalOnlyMapping      : False
Owner                 :
PrimaryStatus         : OK
Status                : The rule was parsed successfully from the store. (65536)
EnforcementStatus     : NotApplicable
PolicyStoreSource     : PersistentStore
PolicyStoreSourceType : Local
```
### Ansible実行
それではAnsibleからWindows Serverへの動作確認をしましょう。
インベントリの内容を変更しておきます。

```:Inventoryファイル
# file: hosts
(略)
[windows_server:vars]
ansible_ssh_port=5986
```

```terminal:win_pingの実行
$ ansible windows_server -i hosts -m win_ping

10.8.0.10 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
```
できました。

これでHTTP/HTTPSどちらでも`win_ping`の動作確認ができました！
以上、最後まで読んでいただきありがとうございました！！