1. 程式人生 > 其它 >Go語言網路程式設計:SSH連線

Go語言網路程式設計:SSH連線

旨在提升Go語言網路程式設計能力

SSH是什麼?

SSH 為 Secure Shell 的縮寫,為建立在應用層基礎上的安全協議。SSH 是較可靠,專為遠端登入會話和其他網路服務提供安全性的協議。利用 SSH 協議可以有效防止遠端管理過程中的資訊洩露問題。

通過使用SSH,可以把所有傳輸的資料進行加密,這樣"中間人"這種攻擊方式就不可能實現了,而且也能夠防止DNS欺騙和IP欺騙。使用SSH,還有一個額外的好處就是傳輸的資料是經過壓縮的,所以可以加快傳輸的速度。

SSH如何工作?

SSH由伺服器和客戶端組成,在整個通訊過程中,為建立安全的SSH通道,會經歷如下幾個階段:

  1. 連線建立

SSH伺服器在指定的埠偵聽客戶端的連線請求,在客戶端向伺服器發起連線請求後,雙方建立一個TCP連線。

  1. 版本協商

SSH協議目前存在SSH1.X(SSH2.0之前的版本)和SSH2.0版本。SSH2.0協議相比SSH1.X協議來說,在結構上做了擴充套件,可以支援更多的認證方法和金鑰交換方法,同時提高了服務能力。SSH伺服器和客戶端通過協商確定最終使用的SSH版本號。

  1. 演算法協商

SSH支援多種加密演算法,雙方根據各自支援的演算法,協商出最終用於產生會話金鑰的金鑰交換演算法、用於資料資訊加密的加密演算法、用於進行數字簽名和認證的公鑰演算法以及用於資料完整性保護的HMAC演算法。

  1. 金鑰交換

伺服器和客戶端通過金鑰交換演算法,動態生成共享的會話金鑰和會話ID,建立加密通道。會話金鑰主要用於後續資料傳輸的加密,會話ID用於在認證過程中標識該SSH連線。

  1. 使用者認證

SSH客戶端向伺服器端發起認證請求,伺服器端對客戶端進行認證。SSH支援以下幾種認證方式:

  • 密碼(password)認證:客戶端通過使用者名稱和密碼的方式進行認證,將加密後的使用者名稱和密碼傳送給伺服器,伺服器解密後與本地儲存的使用者名稱和密碼進行對比,並向客戶端返回認證成功或失敗的訊息。
  • 金鑰(publickey)認證:客戶端通過使用者名稱,公鑰以及公鑰演算法等資訊來與伺服器進行認證。
  • password-publickey認證:指使用者需要同時滿足密碼認證和金鑰認證才能登入。
  • all認證:只要滿足密碼認證和金鑰認證其中一種即可。
  1. 會話請求

認證通過後,SSH客戶端向伺服器端傳送會話請求,請求伺服器提供某種型別的服務,即請求與伺服器建立相應的會話。

  1. 會話互動

會話建立後,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