Certificatesを使用してAnsibleのWinRMをセキュアにする方法(10ステップ)

Ansibleは、今日の最も一般的な設定管理ツールの1つとなっています。Ansibleは、DevOpsエンジニアやシステムエンジニア/管理者が全ての環境でインフラストラクチャを適用・維持するための便利な(そしてほとんどの場合無料の)ツールです。ただし、AnsibleをWindowsとWinRM経由で連携する設定は、課題となるかもしれません。

他のインフラストラクチャコンポーネントと同様に、AnsibleはWindowsホストに対して設定状態を展開・維持することができます。AnsibleはWinRM経由でこれらのWindowsホストに接続しますが、SSHを試験的に使用している場合もあります

AnsibleのWinRMの設定では、セットアップの容易さやセキュリティ上の影響など、いくつかの異なるオプションがあります。多くの人々は簡単な方法を選択します。HTTPを用いた基本認証。証明書に関連する追加作業を省くことはできますが、必要がない限り、ネットワーク上で暗号化されていないデータを送信するのは良いアイデアではありません。

この記事では、自己署名証明書を使用した証明書ベースの認証を使用して、AnsibleがWinRmと通信できるようにする方法について学びます。

このチュートリアルで学ぶセットアップは、Ansibleクライアントに限らず、他のWindowsホストなどのWinRmクライアントにも同様に適用できます。

前提条件

この記事はチュートリアル形式の手順書です。WinRmをAnsibleに設定するためにここで提示された手順に従う意図がある場合、以下の前提条件があるものとします。

  • LinuxホストにAnsibleがすでにインストールされていること。
  • Windowsホストに基づいたAnsibleインベントリが設定されていること。
  • 管理するためのWindows Server 2016以降のサーバーがあること。このチュートリアルで使用される一部のコマンドレットは、古いバージョンのWindowsでは動作しません。
  • Windowsホストがドメインに属していないこと。例はドメインに参加していないホストで実行されたものですが、この構成はドメインに参加しているホストでも動作するはずです
  • WindowsホストにRDPまたはコンソールアクセスがあり、ローカル管理者としてログインしていること。
  • 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には、少なくとも1つのリスナーが必要です。これらの条件のいずれかが何も返さない場合は、Enable-PSRemotingを実行してください。

if (-not (Get-PSSessionConfiguration) -or (-not (Get-ChildItem WSMan:\localhost\Listener))) {
    ## SkipNetworkProfileCheckを使用して、Windowsファイアウォールのパブリックプロファイルでも利用できるようにします
    ## Sureかどうかを確認せずに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

ローカルユーザーアカウントの作成

Ansibleの認証には証明書ベースのWinRMを使用するため、ローカルユーザーアカウントを証明書に「マップ」する必要があります。これにはローカル管理者アカウントを使用することもできますが、管理を容易にするために特定のアカウントを作成することをお勧めします。

次のコードスニペットは、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
}

クライアント証明書の作成

Ansible用のWinRm(安全に)では、クライアント証明書とサーバー証明書の2つの証明書が必要です。

クライアント/サーバーの両方に同じ証明書を使用することもできますが、このアプローチでは問題が発生する可能性があります。Ansibleのドキュメントや他の多くの情報源では、PowerShellのNew-SelfSignedCertコマンドレットを使用してクライアント証明書を生成する方法が説明されています。この方法でも動作するかもしれませんが、私は簡単に動作させることができませんでした。

WinRM for Ansibleのクライアント証明書を作成するには、プライベートキーとパブリックキーを作成する必要があります。まず、AnsibleホストにSSHで接続し、以下の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用クライアント証明書を作成したら、WinRMが動作するWindowsホストの2つの証明書ストアにインポートする必要があります。そのためには、まず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' }

## リスナーが定義されていないか、サーバー証明書と構成されているリスナーがない場合、
## コンピュータのホスト名をSubjectとして持つ新しいリスナーを作成します
## そして、サーバー証明書にバインドします。
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のためにWinRmが実行するすべてのアクティビティは、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

AnsibleでUser Account Control(UAC)を使用してWinRMを許可する

証明書をマップするためにローカルアカウントを使用する場合、LocalAccountTokenFilterPolicyを1に設定する必要があります。これにより、UACが邪魔にならないようにします。 LocalAccountTokenFilterPolicyはすべてのローカルアカウント(ドメインアカウントではない)に適用され、ネットワークログオンがトークンの制限部分になるため、Windowsはそれを管理者として認識しないため、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/