Golang實現(xiàn)SSH、SFTP操作小結
1 前言
在日常的一些開發(fā)場景中,我們需要去和遠程服務器進行一些通信,執(zhí)行一些相關命令操作,這個時候我們就可以使用SSH協(xié)議實現(xiàn)目標。SSH協(xié)議是建立在應用層上的安全協(xié)議,全稱為Secure Shell,采用的是面向連接的TCP協(xié)議進行傳輸,也就意味著它是安全可靠的。需要注意的是文件傳輸并不能在SSH協(xié)議上完成,需要在下面提到的SFTP協(xié)議完成。
2 Go實現(xiàn)
Go官方為我們提供了用于實現(xiàn)SSH連接的package,位于golang.org/x/crypto下,通過在程序中調(diào)用包中提供的相關方法,便可以實現(xiàn)與其他機器進行通信。使用前我們需要使用go get導入相關的依賴包。
go get golang.org/x/crypto/ssh
2.1 配置相關參數(shù)
在進行通信之前,我們還需要配置一些用于配置一些用于建立連接的相關參數(shù)。ssh包下的ClientConfig結構體中,定義了建立SSH連接需要用到的一些配置項,部分項提供了默認參數(shù),我們使用時可以不進行聲明。
下面的代碼段中,我們首先是聲明了用戶名和密碼,連接超時時間設置為10秒鐘,addr變量定義了目標機器的IP地址以及端口。
HostKeyCallback項,我們設置了忽略,這是因為SSH協(xié)議為客戶端提供了兩種安全驗證方式,一種是基于口令的安全驗證,也就是我們常常使用的賬號密碼形式,另外一種則是基于密鑰的安全驗證,相較于第一種,這種形式的校驗方法極大的提升了安全等級,缺點則是時間損耗相對較長。
如果需要使用這種方式進行校驗,首先我們需要在服務器上為自己創(chuàng)建一對密鑰,作為客戶端進行訪問時,首先會向服務端發(fā)送安全驗證請求,服務端收到請求后,首先會將機器上保存的公鑰與客戶端發(fā)送的公鑰進行比較,如果一致,服務端則會向客戶端響應加密質詢,客戶端接受到質詢之后,使用私鑰進行解密,然后再將解密結果發(fā)送給服務端,服務端進行校驗后再返回響應結果,到這里就算是完成了一段密鑰校驗。
//添加配置
config := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{ssh.Password("Password")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
}
addr := fmt.Sprintf("%v:%v", IP, Port)
2.2 建立連接
在完成了所有的參數(shù)初始化之后,我們便可以調(diào)用Dial方法建立SSH連接。Dial方法一共有三個參數(shù)和兩個返回值,第一個參數(shù)network為網(wǎng)絡類型,這里我們使用面向連接的TCP協(xié)議,第二個參數(shù)addr則為目標機器的IP地址和端口號,第三個參數(shù)config則為前面我們生命的配置項。Dial會返回一個SSH連接和錯誤類型。
func Dial(network, addr string, config *ClientConfig) (*Client, error)
//建立SSH連接
sshClient, err := ssh.Dial("tcp", addr, config)
if err != nil {
log.Fatal("unable to create ssh conn")
}
2.3 創(chuàng)建會話
在建立了與目標機器的SSH連接之后,我們就可以通過創(chuàng)建SSH會話來與目標機器進行通信。通過NewSession()方法便可以實現(xiàn)這一操作。
//建立SSH會話
sshSession, err := sshClient.NewSession()
if err != nil {
log.Fatal("unable to create ssh session")
}
2.4 執(zhí)行操作
與目標機器建立會話后,我們就可以通過執(zhí)行命令等來操作遠程服務器。Go目前為我們提供了五個用于操作遠程機器的方法,分別是Run(), Start(), Output(), CombineOutpt(), Shell()。
??其中 Output(), **CombineOutpt()**這兩個方法是對Run()方法進行不同程度上的封裝,校驗了輸出流,錯誤流等相關內(nèi)容。
// Output runs cmd on the remote host and returns its standard output.
func (s *Session) Output(cmd string) ([]byte, error) {
if s.Stdout != nil {
return nil, errors.New("ssh: Stdout already set")
}
var b bytes.Buffer
s.Stdout = &b
err := s.Run(cmd)
return b.Bytes(), err
}
// CombinedOutput runs cmd on the remote host and returns its combined
// standard output and standard error.
func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
if s.Stdout != nil {
return nil, errors.New("ssh: Stdout already set")
}
if s.Stderr != nil {
return nil, errors.New("ssh: Stderr already set")
}
var b singleWriter
s.Stdout = &b
s.Stderr = &b
err := s.Run(cmd)
return b.b.Bytes(), err
}
Run()方法則是對Start()方法進行了封裝,添加了Wait方法,用于校驗遠程服務器的退出指令。Wait()方法中有一個管道類型的變量exitStatus,它是用來保存每次執(zhí)行命令后,機器返回的退出狀態(tài)的。有興趣的朋友可以去看看這塊的代碼,這里就不貼代碼了。
這里面有一個坑,如果我們在遠程機器上去運行一個永遠不會停止的程序,這個時候我們的程序一直等待不到遠程機器發(fā)送的退出指令,就會造成程序一直阻塞而無法正常返回。解決的辦法是用一個協(xié)程去單獨執(zhí)行這一塊的任務,或者是使用定時器來定時結束session會話,來正常返回。
Start()方法與Shell方法一致,都是返回一個error類型,在底層都是調(diào)用了start()方法和SendRequest方法,關于這兩個方法的內(nèi)容這里就不做詳細介紹了,有興趣的朋友可以自行去閱讀。唯一的區(qū)別是Start()方法有一個string類型的參數(shù),用于接收用戶輸入的參數(shù),而Shell()方法是無參數(shù)的。
使用Shell()方法配合RequestPty()等方法可以在本地建立一個偽終端,可以直接通過輸入命令的形式操作目標機器。下面都會做一個示例。
//Run
func (s *Session) Run(cmd string) error {
err := s.Start(cmd)
if err != nil {
fmt.Println(err)
return err
}
return s.Wait()
}
// Start runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Run, Start or Shell.
func (s *Session) Start(cmd string) error {
if s.started {
return errors.New("ssh: session already started")
}
req := execMsg{
Command: cmd,
}
ok, err := s.ch.SendRequest("exec", true, Marshal(&req))
if err == nil && !ok {
err = fmt.Errorf("ssh: command %v failed", cmd)
}
if err != nil {
return err
}
return s.start()
}
2.5 示例代碼(執(zhí)行命令)
這里我們使用Run()方法來演示一下如果去執(zhí)行命令,其他方法類型就不做演示了。這里我們使用一個標準輸出流、錯誤流來保存執(zhí)行結果。
這里演示了一個簡單的執(zhí)行過程,使用了cd命令到/home/min目錄下,在給helloworld程序添加可執(zhí)行權限,最后運行程序。
var stdoutBuf, stderrBuf bytes.Buffer
session.Stdout = &stdoutBuf
session.Stderr = &stderrBuf
// cd /home/min
// chmod +x helloworld
// ./helloworld
cmd := fmt.Sprintf("cd %v ; chmod +x %v ; %v &", "/home/min", "helloworld", ./helloworld)
err := session.Run(cmd)
if err != nil {
log.Fatal("[ERROR]: ", session.Stderr, err)
}
2.6(創(chuàng)建偽終端)
// 設置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() // 等待退出
2.7 完整代碼
//機器平臺信息
type Machine struct {
IP string
Port string
Username string
Password string
}
//建立SSH連接
func CreateSSHConn(m *model.Machine) error {
//初始化連接信息
config := &ssh.ClientConfig{
User: m.Username,
Auth: []ssh.AuthMethod{ssh.Password(m.Password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
addr := fmt.Sprintf("%v:%v", m.IP, m.Port)
//建立ssh連接
sshClient, err := ssh.Dial("tcp", addr, config)
if err != nil {
fmt.Println("unable create ssh conn", err)
return err
}
defer sshClient.Close()
//建立ssh會話
session, err := sshClient.NewSession()
if err != nil {
fmt.Println("unable create ssh conn", err)
return err
}
defer session.Close()
//執(zhí)行命令
var stdoutBuf, stderrBuf bytes.Buffer
session.Stdout = &stdoutBuf
session.Stderr = &stderrBuf
cmd := fmt.Sprintf("cd %v ; chmod +x %v ; %v &", "/home/min", "helloworld", ./helloworld)
if err := session.Run(cmd); err != nil {
log.Fatal("[ERROR]: ", session.Stderr, err)
}
//創(chuàng)建偽終端
// 設置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.Fatal(err)
}
// 設置輸入輸出
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
session.Shell() // 啟動shell
session.Wait() // 等待退出
return err
}到此這篇關于Golang實現(xiàn)SSH、SFTP操作小結的文章就介紹到這了,更多相關Golang實現(xiàn)SSH和SFT內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

