Go語言網路程式設計:SSH連線
旨在提升Go語言網路程式設計能力
SSH是什麼?
SSH 為 Secure Shell 的縮寫,為建立在應用層基礎上的安全協議。SSH 是較可靠,專為遠端登入會話和其他網路服務提供安全性的協議。利用 SSH 協議可以有效防止遠端管理過程中的資訊洩露問題。
通過使用SSH,可以把所有傳輸的資料進行加密,這樣"中間人"這種攻擊方式就不可能實現了,而且也能夠防止DNS欺騙和IP欺騙。使用SSH,還有一個額外的好處就是傳輸的資料是經過壓縮的,所以可以加快傳輸的速度。
SSH如何工作?
SSH由伺服器和客戶端組成,在整個通訊過程中,為建立安全的SSH通道,會經歷如下幾個階段:
- 連線建立
SSH伺服器在指定的埠偵聽客戶端的連線請求,在客戶端向伺服器發起連線請求後,雙方建立一個TCP連線。
- 版本協商
SSH協議目前存在SSH1.X(SSH2.0之前的版本)和SSH2.0版本。SSH2.0協議相比SSH1.X協議來說,在結構上做了擴充套件,可以支援更多的認證方法和金鑰交換方法,同時提高了服務能力。SSH伺服器和客戶端通過協商確定最終使用的SSH版本號。
- 演算法協商
SSH支援多種加密演算法,雙方根據各自支援的演算法,協商出最終用於產生會話金鑰的金鑰交換演算法、用於資料資訊加密的加密演算法、用於進行數字簽名和認證的公鑰演算法以及用於資料完整性保護的HMAC演算法。
- 金鑰交換
伺服器和客戶端通過金鑰交換演算法,動態生成共享的會話金鑰和會話ID,建立加密通道。會話金鑰主要用於後續資料傳輸的加密,會話ID用於在認證過程中標識該SSH連線。
- 使用者認證
SSH客戶端向伺服器端發起認證請求,伺服器端對客戶端進行認證。SSH支援以下幾種認證方式:
- 密碼(password)認證:客戶端通過使用者名稱和密碼的方式進行認證,將加密後的使用者名稱和密碼傳送給伺服器,伺服器解密後與本地儲存的使用者名稱和密碼進行對比,並向客戶端返回認證成功或失敗的訊息。
- 金鑰(publickey)認證:客戶端通過使用者名稱,公鑰以及公鑰演算法等資訊來與伺服器進行認證。
- password-publickey認證:指使用者需要同時滿足密碼認證和金鑰認證才能登入。
- all認證:只要滿足密碼認證和金鑰認證其中一種即可。
- 會話請求
認證通過後,SSH客戶端向伺服器端傳送會話請求,請求伺服器提供某種型別的服務,即請求與伺服器建立相應的會話。
- 會話互動
會話建立後,SSH伺服器端和客戶端在該會話上進行資料資訊的互動。
程式碼實現
可以使用Go語言的ssh包
go get golang.org/x/crypto/ssh
首先是向伺服器發起請求,建立連線。這裡使用ssh.Dial函式,
func Dial(network string, addr string, config *ClientConfig) (*Client, error)
SSH是基於TCP的,所以對該函式的第一個引數傳入"TCP",第二個引數指定為IP地址,第三個引數是客戶端配置資訊。
ClientConfig結構如下:
type ClientConfig struct {
Config
User string
Auth []AuthMethod
HostKeyCallback HostKeyCallback
BannerCallback BannerCallback
ClientVersion string
HostKeyAlgorithms []string
Timeout time.Duration
}
在該結構中首先要指定User,即使用者名稱。Auth,同樣要被指定,這個引數包含了可用於伺服器的身份驗證方法。最後要指定HostKeyCallBack,在加密握手期間呼叫HostKeyCallback以驗證伺服器的主機金鑰。
上述函式包含了從建立連線到使用者認證的過程,下面要建立一個會話。呼叫NewSession方法可以建立會話。
func (c *Client) NewSession() (*Session, error)
然後發起會話請求,使用如下函式
func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error
pty是偽終端,偽終端由終端模擬器提供,終端模擬器是一個執行在使用者態的應用程式。
該函式的第四個引數是終端模式,這是一個map,可以傳入一些鍵值,進行設定。
type TerminalModes map[uint8]uint32
最後要將會話的輸入輸出設定為系統的標準輸入輸出,使得使用者可以直接從終端輸入指令。
完整程式碼
package main
import (
"golang.org/x/crypto/ssh"
"log"
"os"
)
// 基本資訊
const (
address = "xx.xx.xx.xx"
username = "username"
password = "password"
)
func main() {
// 設定配置資訊
config := ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{ssh.Password(password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 用於簡單的認證
}
// 與伺服器建立連線
client, err := ssh.Dial("tcp", address, &config)
if err != nil {
log.Println(err)
return
}
// 建立一個會話
session, _ := client.NewSession()
defer session.Close()
// 設定Terminal Mode
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 關閉回顯
ssh.TTY_OP_ISPEED: 14400, // 設定傳輸速率
ssh.TTY_OP_OSPEED: 14400,
}
// 請求偽終端
err = session.RequestPty("linux", 32, 160, modes)
if err != nil {
log.Println(err)
return
}
// 設定輸入輸出
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
session.Shell() // 啟動shell
session.Wait() // 等待退出
}
執行
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Wed May 18 18:18:03 2022
[root@VM-4-16-centos ~]# uname
Linux