SSH,全名為 Secure Shell,是一種網路協定,用於安全地遠端登入網路上的其他電腦。我相信大多數後端開發人員都熟悉 SSH。用於登入伺服器的常見 shell 工具,例如 Xshell、SecureCRT 和 iTerm2,都是基於 SSH 協定。在 Golang 中,crypto/ssh
套件提供了實作 SSH 用戶端的功能。在本文中,我們將詳細說明如何使用 Golang 實作 SSH 用戶端。
建立 SSH 用戶端組態
首先,我們需要設定 SSH 用戶端連線到伺服器的參數。基本組態包括 username
、authentication method
和 host key callback
。以下是一個程式碼片段範例:
package main
import "golang.org/x/crypto/ssh"
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
}
在此組態中,使用者名稱設定為 "username",密碼設定為 "password"。ssh.Password
函數用於建立密碼驗證方法。每次連線到新主機時,都會呼叫 HostKeyCallback
函數,它用於驗證伺服器的主機金鑰。在此範例中,使用 ssh.InsecureIgnoreHostKey
,這表示接受任何主機金鑰。不建議在生產環境中使用此方法,因為不驗證主機金鑰會造成安全風險。
連線到 SSH 伺服器
ssh.Dial
函數用於連線到遠端 SSH 伺服器。它需要三個參數:網路類型(通常為 "tcp")、伺服器的位址和連接埠,以及先前建立的組態物件。以下是一個程式碼片段範例:
package main
import (
"golang.org/x/crypto/ssh"
"log"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
}
使用 IP 位址 192.168.3.111 連線到伺服器,連接埠為 22(SSH 協定的預設連接埠)。如果連線失敗,將會傳回錯誤,您可以使用 log.Fatal
列印錯誤並結束程式。
建立 SSH 工作階段
一旦建立 SSH 連線,您就可以建立 SSH 工作階段以與伺服器通訊。以下是一個程式碼片段範例:
package main
import (
"golang.org/x/crypto/ssh"
"log"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
}
使用 client.NewSession
函數建立新的工作階段,如果失敗,將會傳回錯誤。使用 defer
關鍵字確保在操作完成後關閉工作階段。
建立虛擬終端機
SSH 協定允許建立虛擬終端機,它會模擬真實終端機的行為,並允許互動式命令,例如 shell 或文字編輯器。以下是一個程式碼片段範例:
package main
import (
"golang.org/x/crypto/ssh"
"log"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 停用回顯
ssh.TTY_OP_ISPEED: 14400, // 輸入速度 = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // 輸出速度 = 14.4kbaud
}
if err := session.RequestPty("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
}
使用 session.RequestPty
函數請求虛擬終端機。它需要四個參數:終端機類型(在此範例中為 "xterm")、終端機的寬度和高度,以及用於設定終端機模式的 map。在此 map 中,ssh.ECHO
設定為 0,這會停用回顯,而輸入和輸出速度設定為 14.4 kbaud。
設定輸入和輸出
指定遠端 shell 的標準輸入和輸出。以下是一個程式碼片段範例:
package main
import (
"golang.org/x/crypto/ssh"
"log"
"os"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 停用回顯
ssh.TTY_OP_ISPEED: 14400, // 輸入速度 = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // 輸出速度 = 14.4kbaud
}
if err := session.RequestPty("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
//設定輸入和輸出
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
}
啟動遠端 Shell
遠端 shell 現在已準備就緒,您可以使用 session.Shell
函數啟動預設 shell,或使用 session.Run
函數執行特定命令。以下是一個程式碼片段範例:
package main
import (
"golang.org/x/crypto/ssh"
"log"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 停用回顯
ssh.TTY_OP_ISPEED: 14400, // 輸入速度 = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // 輸出速度 = 14.4kbaud
}
if err := session.RequestPty("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
// 設定輸入和輸出
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
if err := session.Shell(); err != nil {
log.Fatal("failed to start shell: ", err)
}
}
啟動預設 shell,如果啟動失敗,將會傳回錯誤。
等待工作階段結束
使用 session.Wait
函數封鎖直到工作階段結束。以下是一個程式碼片段範例:
package main
import (
"golang.org/x/crypto/ssh"
"log"
"os"
)
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 停用回顯
ssh.TTY_OP_ISPEED: 14400, // 輸入速度 = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // 輸出速度 = 14.4kbaud
}
if err := session.RequestPty("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
//設定輸入和輸出
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
if err := session.Shell(); err != nil {
log.Fatal("failed to start shell: ", err)
}
err = session.Wait()
if err != nil {
log.Fatal("Failed to run: " + err.Error())
}
}
這樣一來,您就完成了基本 SSH 用戶端的實作。此用戶端可以連線到 SSH 伺服器、啟動遠端 shell,並等待工作階段結束。
總結
本文中實作的 SSH 用戶端僅提供最基本的功能。若要建立功能豐富且使用者友善的 SSH 用戶端,您需要注意許多細節,例如錯誤處理、重新連線、逾時、訊號處理等等。