本文由golang教學專欄跟大家介紹golang是怎麼實現SSH連線等相關操作的,不知道大家對SSH了解多少?以下就給大家詳細聊聊Go實作ssh相關操作問題,希望對需要的朋友有幫助!
在日常的一些開發場景中,我們需要去和遠端伺服器進行一些通信,執行一些相關命令操作,這個時候我們就可以使用SSH協定實現目標。 SSH協定是建立在應用層上的安全協議,全稱為Secure Shell,採用的是面向連接的TCP協定進行傳輸,也意味著它是安全可靠的。需要注意的是檔案傳輸並不能在SSH協定上完成,需要在下面提到的SFTP協定完成。
Go官方為我們提供了實現SSH連接的package,位於golang.org/x/crypto下,透過在程式中呼叫套件中提供的相關方法,便可實現與其他機器進行通訊。使用前我們需要使用go get導入相關的依賴套件。
go get golang.org/x/crypto/ssh
在進行通訊之前,我們還需要設定一些用於配置一些用於建立連接的相關參數。 ssh套件下的ClientConfig結構體中,定義了建立SSH連線需要用到的一些設定項,部分項提供了預設參數,我們使用時可以不進行宣告。
在下面的程式碼片段中,我們首先是宣告了使用者名稱和密碼,連線逾時時間設定為10秒鐘,addr變數定義了目標機器的IP位址以及連接埠。
HostKeyCallback項,我們設定了忽略,這是因為SSH協定為客戶端提供了兩種安全驗證方式,一種是基於口令的安全驗證,也就是我們常常使用的帳號密碼形式,另外一種則是基於金鑰的安全驗證,相較於第一種,這種形式的校驗方法極大的提升了安全等級,缺點則是時間損耗相對較長。
如果需要使用這種方式進行校驗,首先我們需要在伺服器上為自己建立一對金鑰,作為客戶端進行存取時,首先會向服務端發送安全驗證請求,服務端收到請求後,首先會將機器上保存的公鑰與客戶端發送的公鑰進行比較,如果一致,服務端則會向客戶端回應加密質詢,客戶端接受到質詢之後,使用私鑰進行解密,然後再將解密結果傳送給服務端,服務端進行校驗後再回傳回應結果,到這裡就算完成了一段金鑰校驗。
//添加配置 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)
在完成了所有的參數初始化之後,我們可以呼叫Dial方法建立SSH連線。 Dial方法一共有三個參數和兩個回傳值,第一個參數network為網路類型,這裡我們使用面向連線的TCP協議,第二個參數addr則為目標機器的IP位址和埠號,第三個參數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") }
在建立了與目標機器的SSH連線之後,我們就可以透過建立SSH會話來與目標機器進行通訊。透過NewSession()方法便可以實現此操作。
//建立SSH会话 sshSession, err := sshClient.NewSession() if err != nil { log.Fatal("unable to create ssh session") }
與目標機器建立會話後,我們就可以透過執行指令等來操作遠端伺服器。 Go目前為我們提供了五個用於操作遠端機器的方法,分別是Run(), Start(), Output(), CombineOutpt(), Shell()。
?其中Output(), **CombineOutpt()**這兩個方法是對Run()方法進行不同程度上的封裝,校驗了輸出流,錯誤流等相關內容。
// 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,它是用來保存每次執行指令後,機器傳回的退出狀態的。有興趣的朋友可以去看看這塊的程式碼,這裡就不貼程式碼了。
這裡面有一個坑,如果我們在遠端機器上去運行一個永遠不會停止的程序,這個時候我們的程式一直等待不到遠端機器發送的退出指令,就會造成程式一直阻塞而無法正常返回。解決的方法是用協程去單獨執行這一塊的任務,或是使用定時器來定時結束session會話,來正常回傳。
Start()方法与Shell方法一致,都是返回一个error类型,在底层都是调用了start()方法和SendRequest方法,关于这两个方法的内容这里就不做详细介绍了,有兴趣的朋友可以自行去阅读。唯一的区别是Start()方法有一个string类型的参数,用于接收用户输入的参数,而Shell()方法是无参数的。
使用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() }
这里我们使用Run()方法来演示一下如果去执行命令,其他方法类型就不做演示了。这里我们使用一个标准输出流、错误流来保存执行结果。
这里演示了一个简单的执行过程,使用了cd命令到/home/min目录下,在给helloworld程序添加可执行权限,最后运行程序。
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) }
// 设置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() // 等待退出
//机器平台信息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() //执行命令 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) } //创建伪终端 // 设置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如何實作ssh相關操作的詳細內容。更多資訊請關注PHP中文網其他相關文章!