天天看點

一次“詭異”的 Ansible 密碼問題排查,最後真相?

作者:LinkSLA智能運維管家

背景

在做大批量運維的時候,DBA 需要掌握和使用 ansible。今天有同僚使用 ansible 遇到了一個奇怪現象,和我交流了一下,我發現這個現象很奇怪,也很有趣,我認為是個 BUG,于是排查,之後有了這篇文章。

現象

同時使用的是 ansible2.7.8,我使用的是最新版 python3.11+ ansible2.14,我們都複現了這個現象。這個問題簡而言之是,使用比較特殊的密碼組合作為主機密碼,ansible 在使用上會遇到問題,導緻正确的密碼而無法連接配接。

以下是我的環境:

角色 hostname IP 使用的密碼
ansible server 192-168-199-121 192.168.199.121 不涉及
有問題的受控機 192-168-199-99 192.168.199.99 1#fander
沒問題的受控機 192-168-199-131 192.168.199.131 Root-123
[root@192-168-199-121 ~]# cat /etc/ansible/hosts
[fander_vm]
192.168.199.99 ansible_user=root ansible_ssh_pass="1#fander"


[root@192-168-199-121 ~]# ansible 192.168.199.99 -m ping
192.168.199.99 | UNREACHABLE! => {
    "changed": false,
    "msg": "Invalid/incorrect password: Permission denied, please try again.",
    "unreachable": true
}           

同僚疑問1——特殊密碼,ansible無法連接配接會報錯

同時設定了一個形如”1#xxxxxx”的密碼,使用 ssh [email protected] 連接配接是完全沒有問題的,但使用 ansible 連接配接則會報錯,”Invalid/incorrect password: Permission denied, please try again.” 無法連接配接。這個密碼經過我的研究,是有規律,1位到任意長度的數字 + ‘#’ + 任意長度字元,ansible 會報錯。

也就是,以下密碼會報錯

  • 1#fander
  • 12#fander

以下密碼不會報錯

  • 1a#fander
  • 1@fander

同僚疑問2——此特殊密碼,ansible無法連接配接會報錯,但有時卻能連接配接不報錯

排查過程

1. ansible -vvvvv 排查

-v參數可以讓 ansible 輸出 debug 日志,v 越多越詳細。當然了,可能并不需要五個 v,我不知道要打多少個 v 時,我就把 v 打滿。( mysqlbinlog 我打 3個 v )

一次“詭異”的 Ansible 密碼問題排查,最後真相?

如圖紅圈和橫線,這兩處很關鍵。

紅圈告訴我,我在執行 ansible 時,其實底層調用的是 sshpass 和 ssh。

橫線這一處,報有個檔案不存在,我對比了執行正常連接配接的機器(192.168.199.131),是不會報這個的,是以此處屬于異常,需要排查。

這些日志輸出其實一團糟,可以粘貼到 notepad++,用以下方法美化輸出。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

粘貼到 notepad++ 隻有兩行,通過替換 \r\n 為換行符即可。

首先,我先把 \r\n 替換為 fanderissb

然後,再以擴充模式,替換回來

一次“詭異”的 Ansible 密碼問題排查,最後真相?

現在就比較好閱讀了,這一處就是不正常的。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

經過研究,這些 debug 日志其實來源于我前面紅圈的指令行 sshpass xxx 的輸出。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

查閱資料和整理,原理大概是這樣的:

一次“詭異”的 Ansible 密碼問題排查,最後真相?

正确連接配接是長這個樣子的,指令結果是輸出”/root\n”,前面的 0 是指令 $? 的傳回碼,0 代表執行正常。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

而我們密碼有問題的伺服器傳回碼是 5,輸出是空

一次“詭異”的 Ansible 密碼問題排查,最後真相?
sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s \

-o StrictHostKeyChecking=no -o 'User="root"' -o ConnectTimeout=10 \

-o 'ControlPath="/root/.ansible/cp/e2f9f7759b"' 192.168.199.99 \

'/bin/sh -c '"'"'echo ~root && sleep 0'"'"''           

在這句指令中,ControlPersist=60s 是 ssh 的參數,代表建立一個長連結,保持 60 秒,這個長連結就是建立在 ControlPath=/root/.ansible/cp/e2f9f7759b 的路徑下。

實際上,就是這個 ControlPersist,可以解答同僚疑問2——此特殊密碼,ansible無法連接配接會報錯,但有時卻能連接配接。

為了友善解釋,我把 ansible 伺服器的這個 ControlPersist 調大到 600 秒。

[root@192-168-199-121 ~]# cat /etc/ansible/ansible.cfg

[defaults]

host_key_checking = False




[ssh_connection]

ssh_args = -C -o ControlMaster=auto -o ControlPersist=600           

首先,我登入遠端伺服器,先把密碼改回正常密碼。

[root@192-168-199-99 ~]# echo "Root-123"|passwd --stdin root

Changing password for user root.

passwd: all authentication tokens updated successfully.           

接着,我配置好 ansible 伺服器的 hosts 檔案。

[root@192-168-199-121 ~]# cat /etc/ansible/hosts

[fander_vm]

192.168.199.99 ansible_user=root ansible_ssh_pass="Root-123"           

然後,開始測試。完全沒有問題,能連通。

[root@192-168-199-121 ~]# ansible 192.168.199.99 -m ping

192.168.199.99 | SUCCESS => {

    "ansible_facts": {

        "discovered_interpreter_python": "/usr/bin/python"

    },

    "changed": false,

    "ping": "pong"

}           

執行 ps -ef 能觀察到,我們第一次連接配接完後,ssh 并沒有斷開,有一個背景執行的長連結,他實際上是一個多路複用的 socket 連接配接,後續我們再連遠端伺服器時就是複用他,不需要重新驗證密碼。

[root@192-168-199-121 ~]# ps -ef|grep ssh
root        860      1  0 17:18 ?        00:00:00 /usr/sbin/sshd -D
root        945    860  0 17:18 ?        00:00:04 sshd: root@pts/0,pts/1
root       9220      1  0 22:40 ?        00:00:00 ssh: /root/.ansible/cp/e2f9f7759b [mux]
root       9267   1009  0 22:41 pts/0    00:00:00 grep --color=auto ssh

           

這個時候,我把遠端的伺服器密碼修改為有問題的密碼

[root@192-168-199-99 ~]# echo "1#fander"|passwd --stdin root

Changing password for user root.

passwd: all authentication tokens updated successfully.           

此時,我的 ansible 伺服器的 hosts 配置裡仍然用的舊密碼

[root@192-168-199-121 ~]# cat /etc/ansible/hosts

[fander_vm]

192.168.199.99 ansible_user=root ansible_ssh_pass="Root-123"           

密碼是錯誤的,那麼我還能連嗎?答案是——能。這就是連接配接複用,不需要重新驗證密碼,直接複用前面的 socket 連接配接。

是以,這時你通過 ansible,密碼亂輸或者不輸密碼都能連。

[root@192-168-199-121 ~]# cat /etc/ansible/hosts

[fander_vm]

192.168.199.99 ansible_user=root   #我這裡直接去掉了密碼




[root@192-168-199-121 ~]# date;ansible 192.168.199.99 -m shell -a "ls"

Sun Nov 13 22:49:15 CST 2022   #我複用這個連結的時間

192.168.199.99 | CHANGED | rc=0 >>

anaconda-ks.cfg           

那麼這個長連結是建立後的 600 秒自動銷毀嗎?(提醒,前面我修改的ControlPersist=600s)

答案——否。因為我在建立連接配接後,中途複用過這個連接配接通道,時間是 22:49:15,是以他消失時間不是建立時間 22:40:28 + 10 分鐘,而是 22:49:15 + 10分鐘,也就是 22:59:15。

[root@192-168-199-121 cp]# pwd

/root/.ansible/cp




[root@192-168-199-121 cp]# stat e2f9f7759b;date 

  File: ‘e2f9f7759b’

  Size: 0                 Blocks: 0          IO Block: 4096   socket

Device: fd00h/64768d        Inode: 68415916    Links: 1

Access: (0600/srw-------)  Uid: (    0/    root)   Gid: (    0/    root)

Access: 2022-11-13 22:40:28.680577986 +0800

Modify: 2022-11-13 22:40:28.615577988 +0800

Change: 2022-11-13 22:40:28.615577988 +0800

 Birth: -

Sun Nov 13 22:59:00 CST 2022




[root@192-168-199-121 cp]# stat e2f9f7759b;date 

stat: cannot stat ‘e2f9f7759b’: No such file or directory

Sun Nov 13 22:59:30 CST 2022




# 超過22:59:15,socket消失了。           

那同僚的疑問2,就可以解釋了,有問題的密碼依然不能通過 ansible 連接配接,能連接配接的假象是因為曾經用正确的密碼建立過長連結(這個是 ssh 的參數功能,不是 ansible),後面連接配接時複用了此連接配接,沒有使用密碼認證,是以也就不會報錯了。待 ControlPersist 逾時後,socket 銷毀,有問題的密碼連接配接就開始報錯了。

我們繼續排查同僚的疑問1。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

根據我前面整理的原理,我标記1、2、3、4數字的這幾步,我按這個順序從下往上開始一一排除。

其實,本來應該從最頂上的 4 開始排查的,但 4 需要閱讀源碼,是以我從簡單的 1 開始排查。

2. 排查 ssh

首先,我測試标記為 1 的步驟,手敲這個密碼,ssh 是否認正常。結果是連接配接正常。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

3. 排查 sshpass

然後,測試标記為 2 的步驟,測試 sshpass 傳輸密碼是否正常的。結果也是連接配接正常。

[root@192-168-199-99 ~]# sshpass -p "1#fander" ssh  -C -o ControlMaster=auto -o ControlPersist=60s \

-o StrictHostKeyChecking=no -o 'User="root"' -o ConnectTimeout=10 \

-o 'ControlPath="/root/.ansible/cp/e2f9f7759b"' 192.168.199.99 \

'/bin/sh -c '"'"'echo ~root && sleep 0'"'"''

/root           

這裡,我調整了原指令,因為原指令用的是 sshpass -d10,這個檔案描述符檔案我不知道如何制造,是以我改為用 sshpass -p 來測試。我去掉了 -vvv ,因為如果一切正常,我不需要刷屏的 debug 日志。

4. 使用 paramiko 連接配接方式輔助排查

标記為 3 的步驟,我不知道如何測試,但我知道 ansible 除了 ssh 連接配接,還有一種叫 paramiko 的連接配接方式,他是舊版 ansible 的預設連接配接方式,他比較低效,他不使用 ControlPersist,也就不會建立 Control socket,而之前我們連接配接報錯時,日志的關鍵資訊就是Control socket "/root/.ansible/cp/e2f9f7759b" does not exist

當然了,他也不會使用 sshpass 和不會把密碼寫入那個數字為 10 的檔案描述符。是以,如果我能在 paramiko 的連接配接方式能複現報錯,那麼就和标記為 3 的步驟無關。

[root@192-168-199-121 inventory]# cat /etc/ansible/ansible.cfg

[defaults]

host_key_checking = False

callback_whitelist = timer

transport = paramiko           
一次“詭異”的 Ansible 密碼問題排查,最後真相?

結果是,我使用 paramiko 的連接配接方式,也能複現連接配接報錯。

他這個 python 抛出異常非常好,終于讓我知道為什麼密碼會錯誤了,原來傳進去的密碼不是”1#fander”,而是”1”。也就是問題肯定不在标記為 3 的步驟,而是标記為 4 的步驟。

根據報錯,我在 auth_handler.py 檔案裡打了兩個 print。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

我們再看看輸出。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

那麼,标記為 1、2、3 的步驟我們都排除了,問題出在标記為 4 的步驟,也就是現在的問題是:

為什麼 ansible 傳入給 sshpass 和 ssh 的密碼不正确,應該傳入”1#fander”,但最終傳入”1” ?

5. 排查 ansible -k

我們再來做個測試,不使用 ansible hosts 檔案傳遞密碼,使用 -k 參數手敲密碼。發現一切正常。

6. 水落石出,是 ansible 的 hosts 設定問題

那問題完全能定位出來了,這個疑似 bug,不是 ssh 也不是 sshpass 的問題,而是 ansible 的問題。并且,我們能确定,這個和 ansible 讀取解析 /etc/ansible/hosts 檔案有關。

經過我查閱資料,ansible 讀取解析 /etc/ansible/hosts 相關的代碼在這個路徑下,ini.py 檔案。

cd /usr/local/python3/lib/python3.11/site-packages/

cd ansible_core-2.14.0-py3.11.egg/ansible/plugins/inventory

less ini.py           

通過閱讀注釋,發現這個不是 bug,而是官方知道的問題,是以屬于一個坑。

一次“詭異”的 Ansible 密碼問題排查,最後真相?

最後

我經常閱讀源碼都是通過閱讀注釋就解決的,這很有趣,适合我這種萌新 coder。現在是 2022 年 11 月 13 日的 23:56 分,由于能力和時間的關系,我就寫到這裡了。大家應該看懂了解決辦法,請大家避免這個坑,這個坑不單止針對密碼,在 hosts 檔案的所有變量設定都應該這麼做(上圖橫線)。有興趣深入研究的同學可以繼續看看源代碼。

來源:本文轉自公衆号芬達的資料庫學習筆記,點選檢視原文。

繼續閱讀