てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] AnsibleをバージョンアップしたらCisco IOSに対するPlaybookが認証エラーになった原因と対処

はじめに

先日、Ansible 4系から 6系にバージョンアップしたら、今まで正常に動いていた Cisco IOS に対して処理する Playbook が動かなくなる現象に出会いました。

以下のように、認証エラーです。

fatal: [sw01]: FAILED! => {"changed": false, "msg": "Failed to authenticate: Authentication failed."}

認証情報は変えてないので不思議でした。

Ansible 4系は ansible-core としては 2.11 系、Ansible 6系は 2.13 系です。この間で仕様変更的になことが起きたようです。

調べてみると、どうやら、変数として ansible_password を設定していても、デフォルトの秘密鍵 ~/.ssh/id_rsa を見つけてそれを利用しているためのようでした(ペアの公開鍵は対象に未設定)。

以下の条件で再現しました。

  • ansible.netcommon コレクション 2.4.0 以上
  • ansible-core 2.12.9 以上 or ansible-core 2.13.4 以上
  • コントロールノードに ~/.ssh/id_rsa あり。ただし、ペアの公開鍵はターゲットノードに仕込んでいない
  • paramiko 使用

おそらく、Cisco IOS に限らない現象だと思います。

以下、調べたことなどをまとめます。

基本前提環境

  • paramiko インストール済み
  • ansible-pylibssh は未インストール
  • Python 3.9
  • RHEL 8.6

再現

再現を試みた環境やファイルについてです。

環境

~/.ssh/id_rsa がある状態です。

ただし、この秘密鍵のペアの公開鍵は、Ansible の接続対象の機器には仕込んでいない状況です。

ファイル類

イベントリファイル

インベントリファイルは以下のようなものを利用しました。

inventory.ini

[ios]
ios01 ansible_host=192.168.1.11

変数定義ファイル

インベントリファイルで定義した ios グループの変数は以下の通りです。

認証には公開鍵認証方式ではなく、パスワード認証(ansible_password変数)を利用する意図です。

group_vars/ios.yml

---
ansible_network_os: cisco.ios.ios
ansible_connection: ansible.netcommon.network_cli
ansible_user: dummy_user
ansible_password: dummy_password    # パスワード認証を利用したい

Playbook

実行する Playbook は、show コマンドを実行するだけの簡単なものです。

ios_show.yml

---
- hosts: ios01
  gather_facts: false
  tasks:
    - name: show version
      cisco.ios.ios_command:
        commands:
          - show version

実行

ansible-core 2.11 系のころは正常に実行できましたが、今回は

  • ansible-core 2.13.5
  • ansible.netcommon コレクション 4.0.0
  • cisco.ios コレクション 4.0.0

の環境で実行します。

$ ansible-playbook -i inventory.ini ios_show.yml 

PLAY [ios01] *************************************************************************************

TASK [show version] ******************************************************************************
[WARNING]: ansible-pylibssh not installed, falling back to paramiko
fatal: [ios01]: FAILED! => {"changed": false, "msg": "Failed to authenticate: Authentication failed."}

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

認証エラー Failed to authenticate: Authentication failed. となってしまいました。

なお、~/.ssh/id_rsa を適当な名前に変更したら OK になりました。

NG条件まとめ

色々試していくと、冒頭にも書いた通り、以下の条件でNGとなりました。

  • ansible.netcommon コレクション 2.4.0 以上
  • ansible-core 2.12.9 以上 or ansible-core 2.13.4 以上
  • コントロールノードに ~/.ssh/id_rsa あり。ただし、ペアの公開鍵はターゲットノードに仕込んでいない
  • paramiko 使用

対処

ansible-core や ansible.netcommon コレクションのバージョンはそのまま、かつパスワード認証のままにしても、対処方法はいくつか考えらます。

対処1: デフォルトの秘密鍵を探さないように指定

今回は、デフォルトの秘密鍵(今回は ~/.ssh/id_rsa)を探して見つかって、それを使ったがターゲットノードにペアの公開鍵がないくて認証エラーという状態でした。

パスワード認証を利用したい場合は、秘密鍵を探さないように指定できます。PARAMIKO_LOOK_FOR_KEYS という設定項目を利用します。

ansible.cfg で指定する場合

[paramiko_connection]
look_for_keys = False

環境変数で指定する場合

ANSIBLE_PARAMIKO_LOOK_FOR_KEYS=False

対処2: pylibssh を利用する

ネットワークモジュールが長らく内部的に利用している SSH クライアントライブラリとして、paramiko があります。

最近は pylibsshPythonのパッケージ名としてはansible-pylibssh)というもの新しいものもあります。登場した経緯は Ansible の公式ブログの記事に書かれています。

今回は、paramiko から pylibssh を利用するように変更したら、パスワード認証のまま認証が通るようになりました。

手順は以下の通りです。

  1. pylibssh のインストール

    pip でインストールする。

     pip install ansible-pylibssh
    
  2. pylibssh を利用するように指定

    group_vars/ios.yml に以下を追加する。 yaml ansible_network_cli_ssh_type: libssh なお、ansible.netcommon コレクション 3.0.0 以上では、デフォルトの ansible_network_cli_ssh_type: auto の場合でも pylibssh がインストールされていれば pylibssh を優先で使う仕様にになりました。そのため、厳密には ansible.netcommon コレクション 3.0.0 以上では、明示的な指定は必要ありません。

    他にも、環境変数で指定する方法もあります。詳細は ansible.netcommon.network_cli コネクションプラグインのドキュメントssh_type パラメーターの欄を参照してください。

環境によっては、pylibssh を利用するようにしたことで別の現象が発生することもあるので、これはこれで確認が必要そうです。

参考

ansble.netcommon 4.0.0 時点の実装として、password の指定があって private_key_file の指定がない場合は鍵を探さない、という実装は残っていそうだが、詳細は追えていない。これに頼らないほうが無難そう。

おまけ: 試行錯誤ツイート