這篇文章介紹如何使用 paramiko與其他設備建立SSH連線,不透過密碼而是使用公私鑰加密的方式避免密碼洩漏的風險,同時提高安全性。此外,Paramiko 也可以用來進行檔案傳輸,非常適合用在實驗室自動化上。假如我想要遠端控制波形產生器與待測物,就可以利用 paramiko同時連線兩台機器,並且利用自動化腳本同時控制這兩台機器,實現 Automation Lab的應用場合。{alertInfo}
目錄
前言
在遠端開發的時候常常會需要用 SSH連線到伺服器或者其他設備上,傳統且常用的方式就是透過 IP或者 port連接設備,然後在輸入使用者名稱與密碼。這種作法除了安全性比較低之外,一個終端機視窗只能與一台機器進行連線,如果現在想要同時連結很多設備,或者想要用腳本進行自動化實驗設備控制的時候,要怎麼做呢?手動連線到兩台設備上並且同時輸入指令手動控制是不切實際的,只要是手動就一定會有人為誤差,而且一直盯著電腦螢幕去觀察或控制兩台設備也不是什麼聰明的作法。這時候 paramiko就派上用場了。
Paramiko是 python上一個開源SSH連線專案,利用他可以在 python中建立一個 SSH連線物件,然後用這個物件就可以執行終端指令的操作,檔案讀寫,或者連線本身參數控制。
解鎖遠端設備連線之後,身為開發者的我們就可以去設計這兩台機器之間的工作模式,像是設計一個狀態機來控制波形產生器與代測物,一邊同時命令波形產生器輸出波形,另一邊同時叫代測物去量測訊號。或者一邊是示波器,另一邊是代測物,當代測物啟動的時候叫示波器去量測訊號。如此一來就打通同時控制兩台機器的能力。讀者就可以準備大展身手!
SSH 是什麼?以及SSH 的必要性
SSH 的全名是 Secure Shell Protocol,是一種加密的網路傳輸協定,可在不安全的網路中為網路服務提供安全的傳輸環境(wikipedia - SSH)。SSH是被使用最多也相對安全的加密資訊傳輸機制,透過非對稱加密的方式互相交換公鑰,在利用自己的金鑰加密再傳輸資料回去的方式來確認連線者的身分。但一樣是SSH, 如果只用密碼登入伺服器,他的安全性會低非常多。這個影片詳細且鉅細靡遺的說明並且時做了傳說中的中間人攻擊到底是什麼。
簡單來說,中間人攻擊就是夾在你跟伺服器中間,你傳送的所有資料都被中間人攔截,然後駭客竄改訊息之後再傳送給伺服器。那你可能會問,我不要把資料傳給中間人就好了,或者我避開中間人就好。但實際上駭客哪有這麼笨,他會把自己的訊號偽裝成機場網路、公開 Wifi,或者偽 2G基地台來引誘你連線,當你一連線就上鉤了。他可以從中擷取你的任何資料,並把他解封包之後再傳送到你的目的地。
這也是為什麼很多 Youtuber接到業配都是Nxxd VPN, Shxxk VPN都拿這個當賣點,因為如果你的設備使用 VPN加密,所有傳出裝置的訊息都會經過加密,即使中間人擷取到你的資訊,他沒有解碼VPN的對應金就沒辦法破解你的訊息。可惜的是筆者沒有接到業配,我也想要有業外收入啊!
SSH 透過公私鑰連線是目前最主流且相對安全的方式,但是比起帳號密碼,設置公私鑰的難度對使用者來說並不容易,除了要建立公私鑰對之外,還要把公鑰放到要連線的伺服器上才完成 SSH連線的配置。
這個影片也講解了為什麼用SSH密碼登入不安全的原因,也說明SSH建立連線的步驟。為什麼SSH密碼登錄不安全(簡中),因為步驟複雜,有興趣深入研究的朋友可以前往深入理解。
筆者在這裡先整理出 password less的公私鑰連線方式,也可以參考這個影片詳細的演示如何設定 SSH不需密碼登入:
Linux/Mac Tutorial: SSH Key-Based Authentication - How to SSH Without a Password
- 首先在電腦上產生自己的公私鑰對(public private key pair),使用
ssh-keygen -t ed25519 -C "Johnson's Macbook"
這裡-t ed25519
告訴 ssh-keygen工具產生一對使用 ed25519演算法產生的公私鑰對,-C "Johnson's Macbook"
是選用的註解,可以方便辨識這組公私鑰是誰的或者給誰用的。
接著系統會問你金鑰的儲存路徑,預設是/Users/johnson/.ssh/id_ed25519
(這裡johnson會替換成你的使用者名稱),如果想存在指定路徑則在這一步輸入路徑,我把他存在桌面,並且我這組金鑰對想給github作使用,所以我取名叫做 id_ed25519_github。
但要注意的是,公私鑰的位置其實會影響到 SSH連線的,SSH 本身不特別設定都會從預設資料夾中找私鑰來跟遠端伺服器連線。但如果你本身已經有生成過金鑰,系統會提示你是否要覆蓋原本的金鑰檔案。如果你很清楚現在資料夾內的鑰匙在哪裡被使用而且很有信心的話,可以覆蓋。或者先將原有的金鑰備份下來。刪除金鑰是不可逆的,也無法重新生成一模一樣的金鑰,所以這裡要特別謹慎才行。- 放置公鑰到遠端伺服器。
將剛剛產生的公鑰透過線上SSH(用密碼登入)或線下(如:USB)的方式放到伺服器端,然後把自己的公鑰加入authorized_keys內。
用指令cat id_ed25519.pub >> authorized_keys
把 id_ed25519.pub的內容加入到檔案authorized_keys中。
這步很重要,SSH 連線會讀取authorized_keys檔案的內容,去找要連線的客戶端公鑰有沒有在這放檔案內,有找到才可以進行下一步,沒有的話就會直接拒絕連線。這也是建立 password less連線的第一步。
拒絕連線的樣子:
- 關閉密碼登入。依照需求可以設定/.ssh/config 下的連線方式將密碼登入關閉,但若關閉密碼登入後,公鑰又不幸遺失,或者電腦重置而重新產生金鑰的話,就只能在伺服器端用鍵盤登入才有辦法連線。
把 PreferredAuthentications 從password設定成 publickey就可以預設使用公鑰登入。 - 測試連線,設定好後只要輸入使用者名稱與伺服器位址,不需輸入密碼就可以自動連線。
Paramiko 是什麼?
paramiko 是一個很受歡迎的SSH連線程式庫,他是python自動化的第一步,也是一個靠開源社群驅動的函式庫。雖然筆者是在工作機會上才第一次知道這個函式庫。另外還有一些建立在paramiko上的程式庫,一個叫做Fabric,也有另一個針對網路連線用的Netmiko。只不過這兩種筆者都還沒空使用過,如果有經驗的讀者也歡迎在留言區分享他們兩個跟paramiko的差異!
paramiko 在我搜尋網路與其他code base後,發現很常被用在自動化測試還有自動流程上。前一章節提到如何設定SSH免密碼登入,為的就是在用paramiko的時候不一定隨時都有人員在場監控設備,想像一個場景:
現在工廠需要自動化測試我的產品,一台電腦連了10台設備,我不可能一次輸入十台設備的密碼,或者這是24小時壓力測試的環境,找一個人顧在那邊輸入密碼也不太實際。
因此設定好金鑰連線就很重要,不僅免除輸入密碼的麻煩也提供更高的安全性。但讀者或許會疑問,不是說每一次產生的金鑰對都不一樣嗎?難道我每次換設備都要換金鑰,要去改authorized_keys?
解法很簡單,讓他們都用同一把金鑰不就好了嗎!(雖然這樣安全性就...降低了,但是這個解法很適合用在大量生產測試的地方。除了工廠本身就有門禁,降低實體安全與人員的漏洞之外,這點安全性損失跟工廠效率的權衡,肯定是效率勝出。
paramiko的設定方式
要建立一個paramiko連線,首先要完成上述流程設定好 SSH金鑰的步驟,然後建立SSHClient物件。這裡要注意預設的allow_agent以及look_for_keys設定都是True,這讓筆者想要嘗試使用同一個port但使用不同金鑰進行連線的時候出了問題。allow_agent會允許一個ssh agent自動執行,會自動處理ssh連線的失敗,導致我查不出真正問題的原因,另一個looks_for_keys則是當你指定的私鑰無法與Server建立連線的同時,他會在同一個資料夾下搜索其他金鑰並進行連線。這兩個貼心的設定害我以為Library故障了,明明不同金鑰對卻還能建立連線...
這兩個參數的詳細說明可以參考:Paramiko SSHClient Documentation。
from pathlib import Path from paramiko import ( SSHClient, AutoAddPolicy, ) def create_ssh_client(host, ssh_port, username, private_key_path): client = SSHClient() client.set_missing_host_key_policy(AutoAddPolicy()) port = ssh_port try: client.connect( hostname=host, port=port, username=username, key_filename = str(private_key_path), allow_agent=False, look_for_keys=False, ) except Exception as e: print(f"Unexpect error when create ssh client: {e}") return None return client
別忘了設定好連線主機的一些基礎資訊,如使用者名稱,IP位址以及私鑰檔案路徑。
PRIVATE_KEY_PATH = Path("~/.ssh/id_ed25519").expanduser() HOSTNAME = "192.168.1.153" USERNAME = "johnson"
讓我們在主程式中使用它:
比對一下連線到主機的終端機視窗還有python的腳本運行結果:if __name__ == "__main__": # Example usage ssh_port = 22 # Replace with your SSH port ssh_client = create_ssh_client(HOSTNAME, ssh_port, USERNAME, PRIVATE_KEY_PATH) if ssh_client: print("SSH client created successfully.") stdout = ssh_client.exec_command("ls -l") print(f"Command output: {stdout[1].read().decode()}") ssh_client.close() else: print("Failed to create SSH client.")
腳本運行結果:
這兩個圖可以知道執行 shell指令的結果是相同的。如此一來我們就可以在腳本中執行 shell指令,包括危險的指令如 rm -rf *把所有資料夾清空...
執行權限是依照 USERNAME的權限,所以使用這種開發的時候要注意指令的使用還有權限。
使用scp 函式庫傳輸檔案
Paramiko 本身可以支援單一檔案傳輸,可以建立一個 SFTP物件然後透過這個物件來傳送或接收檔案。但python的優點就是站在巨人肩膀上,已經有人做出基於paramiko的scp功能,並在PyPi上上架,我們就用他的心血結晶來開發:scp 0.15.0 有個缺點就是這個 library的文件沒有很齊全,所以使用可能需要追蹤他的原始碼才知道一些attribute的功能。
首先引入scp函式庫並建立一個SCPClient物件:
然後在main中使用他,延續上一部分的main:from scp import SCPClient def create_scp_client(ssh_client): try: scp_client = SCPClient(ssh_client.get_transport()) return scp_client except Exception as e: print(f"Failed to create SCP client: {e}") return None
local_file.txt,是我們要上傳的檔案,可以跟腳本放在同一個資料夾或者指定一個路徑。但scp並不支援Path物件傳入,使用的時候要先把他主換成字串型態。(好像聽說python不知道哪一個版本之後Path跟Str可以互相轉換,但我不確定)。同理,remote_file.txt是這個檔案被複製到遠端之後的名字,也可以是檔名或路徑。如果沒有指定資料夾則預設會放在 home folder。scp = create_scp_client(ssh_client) scp.put("local_file.txt", "remote_file.txt")
總結
這篇文章簡單的說明如何設定 SSH免密碼登入,還有實際演示 SSH金鑰產生與登入的步驟,接著接續到 paramiko函式庫的使用。
利用 paramiko 可以建立穩定且強大的遠端連線,讓開發者可以依靠這個遠端連線來作自動化測試開發,搭配上 scp可以組成強大的檔案管理系統,傳輸檔案與控制設備兩者都同時兼得。
參考資料
- Paramiko official documentation
- scp - pypi
- ssh -o PreferredAuthentications: What's the difference between "password" and "keyboard-interactive"?
- How to copy a complete directory recursively to a remote server in Python (paramiko) using SCP or SSH?
- Welcome to Fabric
- Netmiko - Multi-vendor library to simplify CLI connections to network devices
- 放置公鑰到遠端伺服器。
張貼留言
留個言吧