趣味の棚

自分の興味のあることを好きなだけ

【Ansible】ignore_errorsとfailed_whenを両方書いた時の挙動

はじめに

タイトルの通り。両方指定した場合、どのように動作するのか気になったので検証した。

ignore_errorsとfailed_whenについて

  • ignore_errors タスクがエラーになっても無視し処理を継続する。
  • failed_when 条件式を記述し、条件を満たした時にタスクの結果に関わらずステータスをfailedにする。

環境

面倒なので全部。

root@84212b237d2f:~/ansible# ansible --version
ansible [core 2.17.6]
  config file = /root/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.11/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.11.10 (main, Nov 12 2024, 06:03:56) [GCC 12.2.0] (/usr/local/bin/python3.11)
  jinja version = 3.1.4
  libyaml = True

検証用Playbook

---
- hosts: localhost
  connection: local
  gather_facts: false

  vars:
    error: "true" # failed_whenをコントールする用の変数

  tasks:
    - name: failed
      ansible.builtin.command: 
        cmd: echo "hoge"            # 実行に成功するコマンド
      failed_when: error  == "true" # 変数"error"が"true"ならfailedとなるように指定
      ignore_errors: true           # ignore_errorsを有効化
      register: res_echo            # コマンドの出力表示用
    
    - name: debug
      ansible.builtin.debug: 
        msg: "{{ res_echo.stdout_lines }}"

実行

root@84212b237d2f:~/ansible# ansible-playbook blog_test/ignore_when.yml 

PLAY [localhost] ******************************************************************************************************

TASK [failed] *********************************************************************************************************
fatal: [localhost]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/local/bin/python3.11"}, "changed": true, "cmd": ["echo", "hoge"], "delta": "0:00:00.003392", "end": "2024-12-04 02:47:13.959671", "failed_when_result": true, "msg": "", "rc": 0, "start": "2024-12-04 02:47:13.956279", "stderr": "", "stderr_lines": [], "stdout": "hoge", "stdout_lines": ["hoge"]}
...ignoring

TASK [debug] **********************************************************************************************************
ok: [localhost] => {
    "msg": [
        "hoge"
    ]
}

PLAY RECAP ************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1  

failed_whenがヒットするため該当のタスクはfailedとなる。 しかしignore_errorsが有効化されているため処理は継続される。
タスクがfailedとなっている理由はコマンドの失敗ではなくfailed_whenであるためコマンドの実行は成功している。なので後続のdebugモジュールではecho "hoge"の結果を格納した変数から値を取り出せている。

おわりに

ignore_errorsfailed_whenの両方を有効にした際の挙動について知ることができた。
ignore_errorsはモジュールのエラーとfailed_whenのエラーを区別せず、すべてのエラーを無視するディレクティブであることが分かった。

疑問点

主旨とズレてる気がするので折りたたむ(クリックで展開) 疑問というより、不思議に思ったのは検証用Playbookの実行結果でchangedが1になっている点。
コマンドを実行するタスクにてchanged: trueとなっているのでここのchangedがカウントされていそう。

  • ignore_errorsを外して実行してみる。
root@84212b237d2f:~/ansible# ansible-playbook blog_test/ignore_when.yml 

PLAY [localhost] ******************************************************************************************************

TASK [failed] *********************************************************************************************************
fatal: [localhost]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/local/bin/python3.11"}, "changed": true, "cmd": ["echo", "hoge"], "delta": "0:00:00.003515", "end": "2024-12-04 03:05:26.835896", "failed_when_result": true, "msg": "", "rc": 0, "start": "2024-12-04 03:05:26.832381", "stderr": "", "stderr_lines": [], "stdout": "hoge", "stdout_lines": ["hoge"]}

PLAY RECAP ************************************************************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

stdoutを見る分にはちゃんとコマンド実行できてそうだし、changed:trueとなっているのにRECAPのchangedは1にならない。

  • failed_whenを外し、実行するコマンドを実行不可なものに変更してみる。
root@84212b237d2f:~/ansible# ansible-playbook blog_test/ignore_when.yml 

PLAY [localhost] ******************************************************************************************************

TASK [failed] *********************************************************************************************************
fatal: [localhost]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/local/bin/python3.11"}, "changed": false, "cmd": "foo", "msg": "[Errno 2] No such file or directory: b'foo'", "rc": 2, "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
...ignoring

TASK [debug] **********************************************************************************************************
ok: [localhost] => {
    "msg": []
}

PLAY RECAP ************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1   

そもそもコマンド実行でこけるのでstdoutが空なのは当然、changed: falseになっているためRECAPのchangedも0なのは納得がいく。

failed_whenignore_errorsが組み合わさることであのchangedが出てきたのかな。
ここから更に深堀する元気は今はないのでここまでにする。(気力が出てきたら追記するかも)

参考

docs.ansible.com

docs.ansible.com

AnsibleからWindows操作する時の認証あれこれ番外編 (SSH接続)

はじめに

AnsibleからWindows操作する時の認証あれこれの番外編です。

基本的な説明は以前書いたので時間に余裕のある方はまずはそちらから。
Part1 (Basic認証)
Part2 (Certificate認証)
Part3 (Kerberos認証)
Part4 (NTLM認証)
Part5 (CredSSP認証)

認証方式

AnsibleからWindowsに接続するには通常WinRMを用いる。
そのWinRMでは以下のような認証方式が選択できた。
(公式ドキュメントより)

Option Local Accounts Active Directory Accounts Credential Delegation HTTP Encryption
Basic Yes No No No
Certificate Yes No No No
Kerberos No Yes Yes Yes
NTLM Yes Yes No Yes
CredSSP Yes Yes Yes Yes

ただ第1回でも書いたようにWindowsSSHで接続されることを全くサポートしていないわけではない。
正式に採用されている方式とは言えないがAnsibleのドキュメントにもSSHでの接続方式が記載されている。
ドキュメント
だったのだが、最新のAnsible-core 2.18からはWindowsへのSSH接続を公式にサポートされたらしい。

While Ansible could use the SSH connection plugin with Windows nodes since Ansible 2.8, official support was only added in version 2.18.

ドキュメントより
Google先生による日本語訳

Ansible 2.8 以降、Ansible は Windows ノードで SSH 接続プラグインを使用できましたが、公式サポートはバージョン 2.18 でのみ追加されました。

今回はこのSSHを使った接続を試してみる。

ansible-core 2.18の用意

とりあえずpipでインストール。

pip install ansible-core==2.18.0

ちゃんとバージョンが2.18か確かめる。

# ansible --version
ansible [core 2.18.0]
  config file = /root/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.11/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.11.10 (main, Oct 19 2024, 04:02:01) [GCC 12.2.0] (/usr/local/bin/python3.11)
  jinja version = 3.1.4
  libyaml = True

ちゃんとなってる。

Windows側の設定

ほとんどのWindowsにはデフォルトでSSHサーバがインストールされていないのでインストールする。
インストール方法はAnsibleのドキュメントにあるのでそれを参考にする。

OpenSSHサーバのインストールとFWの穴あけは以下でできる。

Get-WindowsCapability -Name OpenSSH.Server* -Online |
    Add-WindowsCapability -Online
Set-Service -Name sshd -StartupType Automatic -Status Running

$firewallParams = @{
    Name        = 'sshd-Server-In-TCP'
    DisplayName = 'Inbound rule for OpenSSH Server (sshd) on TCP port 22'
    Action      = 'Allow'
    Direction   = 'Inbound'
    Enabled     = 'True'  # This is not a boolean but an enum
    Profile     = 'Any'
    Protocol    = 'TCP'
    LocalPort   = 22
}
New-NetFirewallRule @firewallParams

$shellParams = @{
    Path         = 'HKLM:\SOFTWARE\OpenSSH'
    Name         = 'DefaultShell'
    Value        = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
    PropertyType = 'String'
    Force        = $true
}
New-ItemProperty @shellParams

インストール後ちゃんとOpenSSHサーバが動いてるか確認する。
起動確認ヨシ!

FWにSSH用の穴が開いてるかも確認しておく。
TCP/22番が開いてることが確認できた。

以上でOpenSSHのインストールから起動、FWの穴あけまでできたのでこれでSSH接続が可能になった。
では普通にSSH接続してみる。

$ ssh administrator@192.0.2.2
administrator@192.0.2.2's password: 

administrator@WIN-hogefuga C:\Users\Administrator>

できた。

※ansible-core 2.18がリリースされる前に書いてた部分があったけど、リリース後に書き直した。前のやつも供養するために残しておく。

前のやつ

まずはWindowsにOpenSSHをインストールする。
Windows Server 2025だとデフォルトで入ってるらしいが、今使っているWindowsは2022なので入っていない。
Windows OpenSSH インストールとかで調べるといろいろ出てくる。
いろいろ出てくるがトップにはMSのDocが出てくると思うのでそちらを参照(公式情報より優先する情報などないのだ)

OpenSSHのインストール 参考Link OSのバージョンによって差異があるため使用しているバージョンに合わせる。
管理者権限で起動したPowerShellで下記コマンドを実行。

Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'

これによって下記のような出力になることを確認する。

PS C:\Users\Administrator> Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'

Name  : OpenSSH.Client~~~~0.0.1.0
State : Installed

Name  : OpenSSH.Server~~~~0.0.1.0
State : NotPresent

OpenSSHのクライアントはインストールされているが、Serverはインストールされていないことが分かる。 Serverのインストールは下記コマンドで行う。

Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

インストールが成功すると次の出力がある。

PS C:\Users\Administrator> Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

Path          :
Online        : True
RestartNeeded : False

あとはこのSSHサーバを起動してFWに穴をあけるだけ。
以下のコマンドを実行。

# Start the sshd service
Start-Service sshd

# OPTIONAL but recommended:
Set-Service -Name sshd -StartupType 'Automatic'

# Confirm the Firewall rule is configured. It should be created automatically by setup. Run the following to verify
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
    Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
    New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
} else {
    Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
}

これはSSHサーバのサービスの起動、自動起動設定。FWのルールの作成を行っている。
では上記のコマンドによって作成されたものを見に行こう。
まずはサービス。
画像のようにOpenSSH Serverというサービスが存在し、自動起動も有効になっていることが確認できる。

続いてFW。
SSH用のルールが作成されていることが分かる。
一応ポートも確認する。

ちゃんとTCP/22が解放されてることが確認できた。

Ansibleから接続

普通にSSH接続できることは確認できたのでAnsibleから接続してみる。
使用したインベントリファイルは以下。

[win]
target_windows ansible_host=192.0.2.2

[win:vars]
ansible_connection=ssh
ansible_shell_type=cmd
ansible_user=Administrator
ansible_password="test1234@"

いざいざ

ansible -i inventory.ini win -m win_ping
target_windows | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

できた。
そりゃそうだけどSSHでもwin_pingでいけるのね。

ちなみに接続に使用したシェルにcmdを指定しているが、これはpowershellを指定しても接続できる。

ansible_shell_type=powershell

なおansible_shell_typeを何も指定しないで接続しようとするとエラーになった。

windows_target | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to create temporary directory. In some cases, you may have been able to authenticate and did not have permissions on the target directory. Consider changing the remote tmp path in ansible.cfg to a path rooted in \"/tmp\", for more error information use -vvv. Failed command was: ( umask 77 && mkdir -p \"` echo ~/.ansible/tmp `\"&& mkdir \"` echo ~/.ansible/tmp/ansible-tmp-1731718863.4125307-35-190132706112419 `\" && echo ansible-tmp-1731718863.4125307-35-190132706112419=\"` echo ~/.ansible/tmp/ansible-tmp-1731718863.4125307-35-190132706112419 `\" ), exited with result 1",
    "unreachable": true
}

ちゃんとした理由がないのでデバッグモードで再実行。
ansible -i inventory.ini win -m win_ping -vvv 出力の一部抜粋。

/bin/sh : The term '/bin/sh' is not recognized as the name of a cmdlet, function, script file, or 
operable program. Check the spelling of the name, or if a path was included, verify that the path 

シェルの指定がないと/bin/shが選択されるみたい。
で、Windowsには/bin/shなんてないためエラーになってる感じ。

まぁansible_shell_typeを指定しろとはドキュメントにも書いてあるので「書いてね」以上の話はない。

まとめ

今回はWindowsSSHで接続してみた。
正式に採用されているものではないため使用する機会はなさそうだけど、Windows Server2025ではデフォルトでOpenSSHが入ってたりするから今後は状況が変わる可能性があるかしら。
これを書いている間にリリースされたAnsible-core 2.18からSSHがサポートされたり少し状況は動いたが、これから実際の現場で対WindowsSSHを使うことはあるのかしら。。。
強いて言うならAnsible側にpywinrmとかの追加パッケージが不要なことがあるかもしれないが、別に入れればいいだけの話だからメリットでもないか。

おまけ

以下はAnsible全く関係ないっす。
最近Windows Server 2025の一般提供が開始された。
どこかでも書いたがWindows Server 2025ではデフォルトでOpenSSHサーバが入っているらしい。
軽く確認してみる。
ここより適当なisoファイルをDLして試す。
インストール作業は主題ではないのでスキップ。

まずはサービスとしてOpenSSHサーバがあるか確認する。
起動されていないがサービスとしては存在することが分かる。
スクショ撮ってないが普通に起動できる。

続いてFW。
ちゃんとSSH用のルールが定義されていた。

では接続。

root@06d06a351b4f:~/ansible# ssh administrator@192.0.2.3
administrator@192.0.2.3's password:

Microsoft Windows [Version 10.0.26100.1742]
(c) Microsoft Corporation. All rights reserved.

administrator@WIN-foo C:\Users\Administrator>

入れた。

OSのバージョンを確認してみる。(cmdは使いづらいのでpowershellに切り替えた)

administrator@WIN-foo C:\Users\Administrator>powershell                                                                     Windows PowerShell                                                                                                                  Copyright (C) Microsoft Corporation. All rights reserved.                                                                           

Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows

PS C:\Users\Administrator> Get-ComputerInfo | Select OsName, OSDisplayVersion, OsBuildNumber

OsName                                              OSDisplayVersion OsBuildNumber
------                                              ---------------- -------------
Microsoft Windows Server 2025 Datacenter Evaluation 24H2             26100        


PS C:\Users\Administrator> 

SSHでログインし、バージョン確認的なことができたので満足。

おまけのまとめ

2025以前も特別難しい手順はなかったが、Windows Server 2025では既にあるサービスを起動するだけなのでセットアップは更に楽になった。
WinRMのセットアップと比べると断然楽なので、今後2025以降が主流になればAnsibleでWindows接続するときはSSHな時代も来るのかもしれない。

【Ansible】KVMのダイナミックインベントリを使う

はじめに

Linux仮想マシンを構築できるKVM。最近はVMwareのあれこれで注目度も上がっている気がする。
そんなKVMにも、Ansibleのダイナミックインベントリ用プラグイン公開されている。

今回はそのプラグインを使ってKVMVMをダイナミックインベントリで読み込み接続するまでをやる。

私がKVMのダイナミックインベントリを使えるようにするまでに経験したエラーとその対処方は最後に説明する。

VMの構築

KVMでのVM構築はXMLのテンプレートを用意すればどんな設定でも可能だと思う。今回はそこらへんが躓きポイントとならないようになるべく平易な構築を行いたい。
と言うことで今回はvirt-installコマンドを使ってVMを構築する。
構築に使用したコマンドは以下。

virt-install --virt-type kvm --location /path/iso_file --os-type linux --os-variant rhel9.0 --ram 4086 --vcpus 4 --disk /path/kvm/images/ansible_target.qcow2,size=20 --name ansible_target --network bridge=br0 --hvm --noautoconsole --initrd-inject /path/rhel_kickstart.cfg --extra-args "inst.ks=file:/rhel_kickstart.cfg ksdevice=enp1s0 onboot=yes"

インストール作業が手間だったためkickstartを使用しているが特別な設定は行っていない。

kickstartで使用したファイルも一応記載しておく。<クリックで展開>(適当にインストールしたRHELから引っ張ったものなので最適化されていないが)

# Generated by Anaconda 34.25.4.9
# Generated by pykickstart v3.32
#version=RHEL9
text

%addon com_redhat_kdump --enable --reserve-mb='auto'

%end

# Keyboard layouts
keyboard --xlayouts='us'
# System language
lang en_US.UTF-8

%packages
@^server-product-environment

%end

# Run the Setup Agent on first boot
firstboot --enable

# Generated using Blivet version 3.6.0
ignoredisk --only-use=vda
autopart
# Partition clearing information
clearpart --none --initlabel

# System timezone
timezone Asia/Tokyo --utc

# Root password
rootpw --iscrypted hoge
user --groups=wheel --name=holtit --password=fuga --iscrypted --gecos="holtit"


firewall --disabled

selinux --disabled

reboot

VM側の手順

VMが作成できたので必要な設定を行う。
まずはゲストエージェントを入れる。こいつを入れることで外部からコマンド叩いてゲストOSの情報を取得したりできるようになる。

qemu-guest-agentのインストール

エージェントのインストールを行う。
RHEL系の場合
dnf -y install qemu-guest-agent
Debian系の場合
apt -y install qemu-guest-agent
パッケージ名が同じなので分かりやすい。

エージェントの起動

qemu-guest-agentはサービスとして動作するため起動する。

systemctl start qemu-guest-agent

無事起動できればOK。

systemctl status qemu-guest-agent
● qemu-guest-agent.service - QEMU Guest Agent
     Loaded: loaded (/usr/lib/systemd/system/qemu-guest-agent.service; enabled; preset: enabled)
     Active: active (running) since Tue 2024-11-05 21:56:02 JST; 18min ago
   Main PID: 4894 (qemu-ga)
      Tasks: 2 (limit: 23084)
     Memory: 3.3M
        CPU: 332ms
     CGroup: /system.slice/qemu-guest-agent.service

Ansible側の設定、準備

KVM側の設定が終わったのでようやくAnsibleの話。

community.libvirtのインストール

KVMVMをダイナミックインベントリで扱うためにはcommunity.libvirt.libvirtを使用する。
そのため環境にcommunity.libvirtが入っていない場合はインストールする必要がある。

ansible-galaxy collection install community.libvirt

インストール完了or既にしてある場合はちゃんとインストールができているか確認する。

ansible-galaxy collection list | grep libvirt
community.libvirt                        1.3.0  

ちゃんとできてる。

ライブラリ等のインストール

正直全部を書くことは面倒難しいので私の環境で追加で入れたものだけ列挙する。

# Pythonライブラリ
libvirt-python
# その他パッケージ
libvirt-dev
pkg-config

ダイナミックインベントリの作成

いよいよダイナミックインベントリを作成する。
と言っても内容はこれだけ。

---
plugin: community.libvirt.libvirt
uri: 'qemu+ssh://holtit@kvm-server/system'

今回、Ansibleを実行するマシンとKVMを動かしているマシンは別なので、uriでは上記のような指定になっている。
では読み込めるか見てみよう。 インベントリの内容を確認するには以下のコマンドを実行。

ansible-inventory -i inventory.yml --list

出力が長すぎるので一部割愛する。

            "ansible_target": {
                "ansible_connection": "community.libvirt.libvirt_qemu",
                "ansible_libvirt_uri": "qemu+ssh://holtit@home-server/system",
                "guest_info": {
                    "os.id": "rhel",
                    "os.name": "Red Hat Enterprise Linux",
                    "os.pretty-name": "Red Hat Enterprise Linux 9.4 (Plow)",
                    "os.version": "9.4 (Plow)",
                    "os.version-id": "9.4",
                },
                "info": {
                    "cpuTime_ns": 37860000000,
                    "maxMem_kb": 4184064,
                    "memory_kb": 4184064,
                    "nrVirtCpu": 4,
                    "state": "running",
                    "state_number": 1
                },
                "interface_addresses": {
                    "enp1s0": {
                        "addrs": [
                            {
                                "addr": "192.168.50.34",
                                "prefix": 24,
                                "type": 0
                            },

ホスト名やIPアドレスなどが取得できていることが分かる。
あとはこれをインベントリとして使用するだけ。

ダイナミックインベントリを使用したAnsible接続

ansible -i inventory.yml ansible_target -m ping
ansible_target | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.9"
    },
    "changed": false,
    "ping": "pong"
}

はい成功。

スムーズにできたように書いたが苦戦しまくって2回ぐらい諦めた。
以下に私の環境で発生し、そして解決できたエラーを記載する。

躓いたエラー集

qemu-guest-agentサービスが起動できないとき

現段階ではゲストOS側から一方的に接続を試みている段階。ホストOSと接続するための経路を用意してあげる必要がある。

具体的な手順としては以下のブロックをVMに追加する。

<channel type='unix'>
  <target type='virtio' name='org.qemu.guest_agent.0'/>
  <address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>

IDは自身の環境に合わせてほしい。

上記の設定が終わったらVMを再起動する。
私の環境ではVM内から発行するrebootコマンドではダメで、一度VMを完全に停止させる必要がありそう。

GUIからの確認で申し訳ないが、上記の設定がうまくいくとchannel欄が新たに追加される。

VMを起動すると以下の行が追加されるはず。

<target> type="virtio" name="org.qemu.guest_agent.0" state="connected"/>

connectedになってればよさげ。

再起動が終わったら再びqemu-guest-agentを起動しよう。

systemctl start qemu-guest-agent
systemctl status qemu-guest-agent
* qemu-guest-agent.service - QEMU Guest Agent
     Loaded: loaded (/lib/systemd/system/qemu-guest-agent.service; static)
     Active: active (running) since Tue 2024-11-05 20:23:57 JST; 12min ago
   Main PID: 492 (qemu-ga)
      Tasks: 2 (limit: 2293)
     Memory: 10.6M
        CPU: 153ms
     CGroup: /system.slice/qemu-guest-agent.service
             `-492 /usr/sbin/qemu-ga

無事起動できた。

Ansibleで接続した時Domain does not have required capabilitiesになるとき

ansible-inventory -i inventory.yml --listではVMの情報が表示できるのにいざansible -i inventory.yml ansible_target -m pingなどで接続すると失敗するケースがある。
エラー文は以下。

ansible -i kvm_inventory/inventory.yml ansible_target -m ping
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
ansible_target | UNREACHABLE! => {
    "changed": false,
    "msg": "Domain does not have required capabilities",
    "unreachable": true
}

解決法と言うかトラシューのやり方はターミナルに書いてある通り。
詳細な出力をさせてみる。(長いので抜粋)

ansible -i kvm_inventory/inventory.yml ansible_target -m ping -vvv

<ansible_target> REQUIRED CAPABILITIES MISSING: ['guest-exec', 'guest-exec-status', 'guest-file-close', 'guest-file-open', 'guest-file-read', 'guest-file-write']

重要なのはREQUIRED CAPABILITIES MISSINGここ。
qemu-guest-agentがサービスとして起動していてもすべての機能が有効になっているわけではない。
Ansibleで接続するためには上記のものが有効になっている必要があるようだ。

と言うことで有効化する。
※私が以後作業するのはRHEL9.4である。Debian系のやり方までは扱わない。
理由としては手元のDebian12はqemu-guest-agentをインストールしサービスを起動しただけでAnsible接続に必要なものがすべて有効化されていたためである。

以下のファイルを編集する。
/etc/sysconfig/qemu-ga
いろいろコメントで書かれているだろうが修正するのは1行だけ。
FILTER_RPC_ARGSの行。 あとはこの行にREQUIRED CAPABILITIES MISSINGで不足していると言われたものを追記するだけ。
私の環境では長々しく以下のようになった。※これで1行

FILTER_RPC_ARGS="--allow-rpcs=guest-sync-delimited,guest-sync,guest-ping,guest-get-time,guest-set-time,guest-info,guest-shutdown,guest-fsfreeze-status,guest-fsfreeze-freeze,guest-fsfreeze-freeze-list,guest-fsfreeze-thaw,guest-fstrim,guest-suspend-disk,guest-suspend-ram,guest-suspend-hybrid,guest-network-get-interfaces,guest-get-vcpus,guest-set-vcpus,guest-get-disks,guest-get-fsinfo,guest-set-user-password,guest-get-memory-blocks,guest-set-memory-blocks,guest-get-memory-block-info,guest-get-host-name,guest-get-users,guest-get-timezone,guest-get-osinfo,guest-get-devices,guest-ssh-get-authorized-keys,guest-ssh-add-authorized-keys,guest-ssh-remove-authorized-keys,guest-get-diskstats,guest-get-cpustats,guest-exec,guest-exec-status,guest-file-close,guest-file-open,guest-file-read,guest-file-write"

あとはサービスを再起動する。

systemctl restart qemu-guest-agent

これで完了。Ansibleから疎通テストしてみる。

ansible -i kvm_inventory/inventory.yml ansible_target -m ping 

ansible_target | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.9"
    },
    "changed": false,
    "ping": "pong"
}

成功!!

終わりに

今回はKVMのダイナミックインベントリを使ってみた。
KVMはそもそも構成要素が分かりづらく、そこにAnsibleやAnsibleのダイナミックインベントリが絡むと更に分からなくなった。 記事内で挙げたエラー2つは私がめちゃくちゃ悩んだものなので誰かの参考になれば幸いである。

これで自宅の仮想環境にKVMを選んだことを肯定する材料が増えた。 とても満足!!

AnsibleからWindows操作する時の認証あれこれPart5 (CredSSP認証編)

はじめに

AnsibleからWindows操作する時の認証あれこれの第5回です。

基本的な説明は以前書いたので時間に余裕のある方はまずはそちらから。
Part1 (Basic認証)
Part2 (Certificate認証)
Part3 (Kerberos認証)
Part4 (NTLM認証)
Part5 (CredSSP認証) <- いま

認証方式

いつもの。
AnsibleがWindowsに接続するために使用できる認証方式は以下の表の通りである。
(公式ドキュメントより)

Option Local Accounts Active Directory Accounts Credential Delegation HTTP Encryption
Basic Yes No No No
Certificate Yes No No No
Kerberos No Yes Yes Yes
NTLM Yes Yes No Yes
CredSSP Yes Yes Yes Yes

前回までに表の上から順にNTLM認証まで試した。
今回は最後に残ったCredSSP認証を試す。

検証環境

環境についてはWindows、Ansibleともに前回と設定は同じである。

WinRm側の設定

CredSSPを使った認証はデフォルトでは有効になっていないため有効化する。

PS C:\Users\Administrator> Enable-WSManCredSSP -Role Server -Force

cfg               : http://schemas.microsoft.com/wbem/wsman/1/config/service/auth
lang              : ja-JP
Basic             : false
Kerberos          : true
Negotiate         : true
Certificate       : false
CredSSP           : true
CbtHardeningLevel : Relaxed

Ansible側の準備及び設定

AnsibleでCredSSPを使った認証を使うためには追加でパッケージが必要らしいので追加する。

pip install pywinrm[credssp]

インベントリファイルは以下のようにした。

[win]
target_windows ansible_host=192.0.2.2

[win:vars]
ansible_connection=winrm
ansible_user=Administrator
ansible_password="test1234@"
ansible_winrm_server_cert_validation=ignore
ansible_winrm_transport=credssp

いざ実行

ansible -i inventory.ini win -m win_ping
target_windows | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

成功した。

今の通信が実際ににCredSSPによって行われたものなのかログから確認しよう。
さきほどのログイン時に発生したログは以下。
1回の認証で2つイベントが発生しているようだ。
CredSSPについて詳しくないから上手く説明できないが、認証情報を委任してセキュアにするプロトコルらしい。
そのため、クライアント(Ansible実行環境)からWindows認証情報を受け取り、受け取った認証情報で再度認証をかけている(?)
ここらへんはもう少しプロトコル自体に詳しくならないと書けない。

ログに表示されているプロトコルについて

前回の記事(NTLM)ではAuthentication Packageの欄に書かれているのが認証に使用したプロトコルであり、MICROSOFT_AUTHENTICATION_PACKAGE_V1_0はNTLMを指していると書いた。
しかし今回のCredSSPでやった時も同様MICROSOFT_AUTHENTICATION_PACKAGE_V1_0と書かれている。
参考 NTLMのログ 再掲 CredSSPのログ

両者ともMicrosoftプロトコルなのでこのように表示されるのは納得できるが、これでは認証に使われたプロトコルがNTLMなのかCredSSPなのか判別できない。
ログから認証方式を判別する方法はないのかな...

まとめ

今回はCredSSP認証を使って接続してみた。 設定に関して、NTLMと比べての相違点はAnsible側に追加パッケージが必要なだけなのでこちらも接続まで非常に簡単な方法であった。
NTLMやCredSSPへの知識が少ないためべらぼうに薄い記事になったが本来の目的であるAnsibleからの接続はできたので良しとする。

AnsibleからWindows操作する時の認証あれこれPart4 (NTLM認証編)

はじめに

AnsibleからWindows操作する時の認証あれこれの第4回です。

基本的な説明は以前書いたので時間に余裕のある方はまずはそちらから。
Part1 (Basic認証)
Part2 (Certificate認証)
Part3 (Kerberos認証)
Part4 (NTLM認証) <- いま
Part5 (CredSSP認証)

認証方式

いつもの。
AnsibleがWindowsに接続するために使用できる認証方式は以下の表の通りである。
(公式ドキュメントより)

Option Local Accounts Active Directory Accounts Credential Delegation HTTP Encryption
Basic Yes No No No
Certificate Yes No No No
Kerberos No Yes Yes Yes
NTLM Yes Yes No Yes
CredSSP Yes Yes Yes Yes

前回までに表の上から順にKerberos認証まで試した。
今回はNTLM認証を試す。

検証環境

環境についてはWindows、Ansibleともに前回と同じである。

WinRMの設定

WinRMの設定は特にない。
WinRMのNTLM認証はデフォルトで有効になっている。
と言うかPart1でやったHTTPSのリスナー設定及びFWの穴あけが終わってるなら他に設定いらない。

接続に使用するユーザについて

Ansibleの接続に使用するユーザはAdministratorを使用する。
なおNTLM認証はローカルユーザにもADユーザにも使用できるためそれぞれ検証を行う。

Ansibleから接続してみる。

まずは前回立てたAD DSにいるADユーザであるAdministratorを使用して接続してみる。
使用したインベントリファイルは以下。

[win]
target_windows ansible_host=EC2AMAZ-CD28KI2.holtit.internal

[win:vars]
ansible_connection=winrm
ansible_user=Administrator@HOLTIT.INTERNAL
ansible_password="test1234@"
ansible_winrm_server_cert_validation=ignore
ansible_winrm_transport=ntlm

Kerberos認証との相違点はansible_winrm_transport=ntlmだけ。
実行

ansible -i inventory.ini win -m win_ping
target_windows | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

普通に成功した。

本当にNTLMで認証してるのかこれでは分からないのでWindowsのログを見に行く。 ロギングの有効化は前回も参考にしたこちらの記事で qiita.com 有効化した後に再度Ansibleで接続しイベントビューアを見に行くと下記のログがあった。
Authentication Packageが認証方式らしい (By ChatGPT)
その欄シンプルにNTLMとか書いてあったら分かりやすいのだけど表示されてるのはMICROSOFT_AUTHENTICATION_PACKAGE_V1_0
これは何だと調べてみると、MSのLearnに説明ページがありました。(リンク)
分かりづらいけどこれはちゃんとNTLMを使った認証が行われていることが分かった。
->Part5で扱うCredSSPでも同様のログ表示であった。これだけでNTLMだと判断することはできないと思われる。
漠然とMSが作った認証方式ってことが分かるぐらい。

あまり重要ではない検証

ホストをFQDNではなくIPアドレスで指定してみる。

前回のKerberos認証の検証では、AnsibleのインベントリファイルでIPアドレスではなくAD ControllerのFQDNを指定した。IPアドレスの指定では接続できなかったからである。
今回はKerberos認証ではなくNTLMだが変わらずFQDNでの指定が必須なのか試した。
インベントリファイルのホスト指定は以下の通り。

target_windows ansible_host=192.0.2.2

実行

ansible -i inventory.ini win -m win_ping
target_windows | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

成功。
NTLM認証ではWindowsホストをIPアドレスで指定しても接続できることが分かった。

ローカルユーザ接続の前準備

ここまではADユーザに対して接続してみたが、NTLMはローカルユーザでも接続できる。
しかしAD Controllerとして構築したWindowsのユーザはすべてADユーザとなってしまい、ローカルユーザは存在しない。
わざわざ別でWindowsを立てるのも面倒なのでここはControllerを降格させる。

AD DSの構築と逆の手順を踏む。
[Server Manage]より。
進んでいくと警告が出るので、降格させて続行。
そのあとは良い感じに進めていくとAD DSを削除してControllerから降格できる。(Rebootされる)

再度ログインしてローカルユーザが存在するか確認する
ローカルにAdmin等のユーザがいることが確認できた。

ローカルユーザへ接続

ではこのローカルのユーザを使って接続していく。 接続に使用したインベントリファイル

[win]
target_windows ansible_host=192.0.2.2

[win:vars]
ansible_connection=winrm
ansible_user=Administrator
ansible_password="test1234@"
ansible_winrm_server_cert_validation=ignore
ansible_winrm_transport=ntlm

今回接続するのはローカルのユーザなためユーザ名の部分ではドメインを消している。
それ以外はADユーザの時と同じ。

接続してみる。

ansible -i inventory.ini win -m win_ping
target_windows | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

成功した。
ログはADユーザの時と同じだったため省略。

まとめ

今回はNTLM認証について検証した。 ADユーザにもローカルユーザにも使用できWindowsとAnsible(Linux)の両方で特別な設定がいらないため一番楽な方法だと思っている。
今回はADユーザ->ローカルユーザの順に説明したため分かりずらいが、WinRMの設定が何もされていないWindowsからの場合でも以下の工程だけで接続できる。
1. HTTPSリスナーの作成。
2. HTTPS用の穴をFWにあける。
これだけ。

環境によって選定する認証方式は変わるだろうが、少なくともAnsibleの対Windowsの動作検証が目的であれば一番最適な認証はNTLMなのではないか。

AnsibleからWindows操作する時の認証あれこれPart3 (kerberos認証編)

はじめに

AnsibleからWindows操作する時の認証あれこれの第3回です。

基本的な説明は以前書いたので時間に余裕のある方はまずはそちらから。
Part1 (Basic認証)
Part2 (Certificate認証)
Part3 (Kerberos 認証) <- いま
Part4 (NTLM認証)
Part5 (CredSSP認証)

認証方式

いつもの。
AnsibleがWindowsに接続するために使用できる認証方式は以下の表の通りである。
(公式ドキュメントより)

Option Local Accounts Active Directory Accounts Credential Delegation HTTP Encryption
Basic Yes No No No
Certificate Yes No No No
Kerberos No Yes Yes Yes
NTLM Yes Yes No Yes
CredSSP Yes Yes Yes Yes

前回と前々回でBasic認証とCertificate認証をやった。 今回はKerberos認証を試していく。

検証環境

環境についてはWindows、Ansibleともに前回と同じ。

WinRMの設定

WinRMの設定は特にない。 WinRMのkerberos認証はデフォルトで有効になっている。

AD構築

Kerberos認証は今まで試した認証方式と異なりローカルユーザには対応しておらず、ADユーザにだけ対応している。
そのためまずはAD DSを構築する。

一応手順の説明

各自持っているであろうAD環境でも良いのだが、検証のために汚れるのも嫌だと思うので新規で構築する。

AD DSのインストール

  1. [Server Manager]
  2. [Manage] -> [Add Roles and Features]
  3. [よきなとこまでNext] -> [Active Directory Domain Servicesにチェック] -> [Add Features] -> [Nextを押しまっくてInstallまで]

AD DSの設定

インストールが完了するとServer Managerの右上に警告が出る。[Post-deployment Configuration]
警告内の[Promote this server to a domain controller]をクリックしコントローラとして設定を行う。
自身をコントローラとするため[Add a new forest]を選択しドメインを定義。
あとは良しなにやってInstallまで進める。(基本デフォルトでいい。)
設定が終わると勝手にRebootされる。

以上でAD DSの設定終了。

接続に使用するユーザについて

第1回、第2回ではローカルのAdminを使用して接続を行ったが、WindowsをADのコントローラにしたことによりローカルユーザは消滅しADユーザとなった。
今回の検証ではADコントローラ構築に伴ってADに移動したAdminユーザを使用して接続する。

Ansible側の設定

Ansibleの設定と言うよりはkerberosの設定である。
まずは環境にkerberosを入れる。

インストールしたもの

  • sudo dnf install python3-devel krb5-devel
  • pip3 install pykerberos

kerberosの設定

編集するファイルは以下
/etc/krb5.conf
編集する箇所は以下 (変更点のみ抜粋)
ドメインおよびホスト名は各自環境に合わせること。

[libdefaults]
    default_realm = holtit.internal
[realms]
    HOLTIT.INTERNAL = {
        kdc = EC2AMAZ-hostname.holtit.internal
        admin_server = EC2AMAZ-hostname.holtit.internal
        default_domain = holtit.internal
    }

[domain_realm]
.holtit.internal = HOLTIT.INTERNAL
holtit.internal = HOLTIT.INTERNAL

FQDNを解決できるようにするため/etc/hostsに追記

192.0.2.2 EC2AMAZ-hostname.holtit.internal 

kinitでテスト

最初にAnsible関係なく接続できるか確認する。
とっても優しくないが、パスワード入力後エラーにならずプロンプトが返ってくれば成功。

$ kinit Administrator@HOLTIT.INTERNAL
Password for Administrator@HOLTIT.INTERNAL: 
$ 

Ansibleから接続

kerberos単体では接続できることが確認できたのでAnsibleから接続してみる。
書いたインベントリファイルは以下。ホストはIPで指定した。

[win]
target_windows ansible_host=192.0.2.2

[win:vars]
ansible_connection=winrm
ansible_user=Administrator@HOLTIT.INTERNAL
ansible_password="test1234@"
ansible_winrm_server_cert_validation=ignore
ansible_winrm_transport=kerberos

実行してみる。

ansible -i inventory.ini win -m win_ping
target_windows | UNREACHABLE! => {
    "changed": false,
    "msg": "kerberos: authGSSClientStep() failed: (('Unspecified GSS failure.  Minor code may provide more information', 851968), ('Server not found in Kerberos database', -1765328377))",
    "unreachable": true
}

失敗した。
そもそもADまで届いてなさそう。

インベントリファイルのホストの部分を変えてみる。

target_windows ansible_host=EC2AMAZ-hostname.holtit.internal

再度Ansibleを実行

ansible -i inventory.ini win -m win_ping
target_windows | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

成功。
AD ControllerはFQDNで指定しないといけんのかしら。

Windows側のログから確認してみる kerberosの認証ログはデフォルトでは残らないらしい。
こちらの記事を参考に監視を有効化した。
qiita.com

では再度Ansibleから接続してイベントビューワを見てみる。 ちゃんと見えた。(そりゃそう)

まとめ

毎回そうだけどAnsibleと言うよりWinRMだったりKerberosの設定である。(Ansibleで接続するために必要なことではあるが)
強いて言うならkinitコマンドでは成功したが、Ansibleからは接続できずインベントリファイルでFQDNを指定したらいけた辺りはAnsibleから接続する特有の詰まりポイントかもしれない。

次回はNTLMかな

AnsibleからWindows操作する時の認証あれこれPart2 (Certificate認証編)

はじめに

AnsibleからWindows操作する時の認証あれこれの第2回です。

基本的な説明は以前書いたので時間に余裕のある方はまずはそちらから。
Part1(Basic認証)
Part2(Certificate認証) <- いま
Part3(Kerberos 認証)
Part4(NTLM認証)
Part5 (CredSSP認証)

認証方式

前回のおさらい。
AnsibleがWindowsに接続するために使用できる認証方式は以下の表の通りである。
(公式ドキュメントより)

Option Local Accounts Active Directory Accounts Credential Delegation HTTP Encryption
Basic Yes No No No
Certificate Yes No No No
Kerberos No Yes Yes Yes
NTLM Yes Yes No Yes
CredSSP Yes Yes Yes Yes

前回はこのうちBasic認証について検証を行った。
今回は上から2つ目であるCertificate認証で接続をしてみる。

検証環境

環境についてはWindows、Ansibleともに前回と同じである。
なお前回の記事で設定変更したBasic認証及び非暗号化通信は共に無効化した状態に戻した。

WinRMの設定

今回は証明書を使用した認証を行うため証明書の準備が必要になる。
手順は公式ドキュメント に従う。
※日本語ドキュメント内にあるコードは一部バックスラッシュなどが消えていてコピペだと動作しないため注意。

証明書の発行

証明書はOpenSSLで発行したものでいいらしい。
Windowsに入れてもよかったが少し手順が面倒に見えたのでAnsibleのコントローラに使用しているRHELから発行することにした。
ドキュメントに従い以下を実施。

USERNAME="Administrator" # WinRMの接続に使用するユーザ名を指定
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

生成物はこちら

$ ls -1
cert.pem
cert_key.pem

これらのファイルをお好きな方法でWindowsに送る。
無駄にAnsibleで送ってみた

---
- hosts: win
  gather_facts: false

  vars:
    cert_file:
      - ./cert.pem
      - ./cert_key.pem

  tasks:
    - win_copy:
        src: "{{ item }}"
        dest: "%USERPROFILE%\\Desktop\\"
      loop: "{{ cert_file }}"

(別に鍵は送らなくてよかった...)

証明書の設定

OpenSSLで作成した証明書をWindowsにインポートする。
この時に証明書のファイルは絶対パスで指定すること。

Windows豆知識 Windowsでファイルの絶対パスを取得したい時に便利な機能。
Shiftキーを押下しながら対象を右クリックすると、メニューに「パスのコピー」が現れる。
これで対象の絶対パスクリップボードに保管できる。

$cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import("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()

インポートした証明書をユーザに紐づける。

$username = "Administrator"
$password = ConvertTo-SecureString -String test1234@ -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

最後に証明書での認証をWinRMで許可してやる。

winrm set winrm/config/service/Auth '@{Certificate="true"}'
Auth
    Basic = false
    Kerberos = true
    Negotiate = true
    Certificate = true
    CredSSP = false
    CbtHardeningLevel = Relaxed

接続確認

証明書での接続であるためAnsible側も認証方式の指定をしてやる。
インベントリファイルは以下のようになった。

[win]
target_windows

[win:vars]
ansible_connection=winrm
ansible_user=Administrator
ansible_winrm_transport=certificate
ansible_winrm_cert_pem=./cert.pem
ansible_winrm_cert_key_pem=./cert_key.pem
ansible_winrm_server_cert_validation=ignore

Basic認証と比べて大きく違うのはパスワードの指定がなくなったこと。
それに加えて証明書の指定が増えたこと。
※前回はポート番号やHTTPSの指定もしていたが、何も指定しない場合以下のようになるため今回以降は指定しない。
ポート番号:5986
プロトコルHTTPS

実食

ansible -i inventory.ini win -m win_ping
target_windows | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

成功した。

お片付け兼少し検証

さっきは成功したけど本当に証明書によって認証されてたのか疑問。
と言うことで証明書を削除して認証が失敗することを確認する。
Windowsから証明書を確認すると、さきほどインポートした証明書が確認できる。

ではこの証明書を消して再度Ansibleから接続してみよう。

ansible -i inventory.ini win -m win_ping
target_windows | UNREACHABLE! => {
    "changed": false,
    "msg": "certificate: the specified credentials were rejected by the server",
    "unreachable": true
}

ちゃんと失敗することが確認できた。

最後に証明書での認証を無効化してお片付け完了。
winrm set winrm/config/service/Auth '@{Certificate="false"}'

まとめ

今回は証明書を利用した認証について検証してみた。
証明書を準備してインポートしてと面倒な作業は多かったが、パスワードを直接指定して認証しているわけではないのでBasic認証に比べればセキュアなのかな?
と言ってもこの方式もBasic認証と同じくADユーザでは使えないので使用されるケースはいったいどんなケースなのか分かってない。

次回はKerberos認証