LoginSignup
4
1

More than 1 year has passed since last update.

Ansible から Azure 上 の Windows Server 2019 へ Certificate 認証で接続してコントロールする

Last updated at Posted at 2022-03-16

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.事前準備

事前準備として、以下を実施します。

  1. Certificate 認証で、ローカルユーザーにマッピングする為の証明書の作成
  2. 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 等で接続し以下を設定します。

  1. WinRM等の有効化
  2. 証明書とローカルアカウントの紐付け
  3. 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/等)については、適宜自分の環境へ置き換えてください。

inventory.yml
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
demo-win.yml
- hosts: win
  gather_facts: false
  tasks:
        - win_shell: "{{ item }}"
          with_items:
            - 'hostname'
            - |
              $date = Get-date
              $date
          register: command_results
        - debug: var=command_results.results
ansible.cfg
[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.参考ドキュメント

以下参考ドキュメント

7.さいごに

  • 今回は、本当に久しぶりの Ansible ということもあり、公式ドキュメントや、過去に書いた自分の記事等を参照しながらと、手探りでの検証となりました。。。

  • また、以前よりも、情報が増えたとはいえ、未だに Ansible * Windows の事例は、やはり少ないな〜という印象でした。

  • なお、今回初めて、Windows Server への Certificate 認証を試してみて
    事前準備として証明書ペアの作成と、その後のローカルユーザーとのペアリング等、慣れないと分かりづらい部分も多く(公式ドキュメントの通りではありますが...)、敷居が高く感じましたが、設定後は、Linuxと同様に、インベントリファイルへ証明書の配置場所のみの記載で良い(ユーザー名やパスワードの記載が不要)というのは、コード管理等のセキュリティ上の観点からも良さそうと感じました。

注意
Ansible 公式ドキュメントの認証オプションへ記載の通り、
Certificate 認証については、ローカルユーザーのみ対応となっており、ドメインユーザーでの利用はできません。

  • その他、Azure Cloudshell 上で Ansible がデフォルトで利用可能なのは便利でした。

以上です。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1