SSH, viết tắt của Secure Shell, là một giao thức mạng được sử dụng để đăng nhập từ xa một cách an toàn vào các máy tính khác trên mạng. Tôi tin rằng hầu hết các nhà phát triển backend đều quen thuộc với SSH. Các công cụ shell phổ biến được sử dụng để đăng nhập vào máy chủ, chẳng hạn như Xshell, SecureCRT và iTerm2, đều dựa trên giao thức SSH. Trong Golang, gói crypto/ssh
cung cấp chức năng để triển khai một SSH client. Trong bài viết này, chúng ta sẽ giải thích chi tiết cách triển khai một SSH client bằng Golang.
Tạo Cấu Hình SSH Client
Đầu tiên, chúng ta cần cấu hình các tham số để SSH client kết nối với máy chủ. Cấu hình cơ bản bao gồm username
, authentication method
và host key callback
. Dưới đây là một đoạn mã ví dụ:
package main
import "golang.org/x/crypto/ssh"
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
}
Trong cấu hình này, username được đặt thành "username" và password được đặt thành "password". Hàm ssh.Password
được sử dụng để tạo một phương thức xác thực bằng password. Hàm HostKeyCallback
được gọi mỗi khi một host mới được kết nối và nó được sử dụng để xác minh host key của máy chủ. Trong ví dụ này, ssh.InsecureIgnoreHostKey
được sử dụng, có nghĩa là bất kỳ host key nào cũng được chấp nhận. Không nên sử dụng điều này trong môi trường production vì việc không xác minh host key gây ra rủi ro bảo mật.
Kết Nối Đến SSH Server
Hàm ssh.Dial
được sử dụng để kết nối đến SSH server từ xa. Nó yêu cầu ba tham số: loại mạng (thường là "tcp"), địa chỉ và cổng của máy chủ và đối tượng cấu hình đã tạo trước đó. Dưới đây là một đoạn mã ví dụ:
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)
}
}
Kết nối đến máy chủ với địa chỉ IP 192.168.3.111 trên cổng 22 (cổng mặc định cho giao thức SSH). Nếu kết nối không thành công, một lỗi sẽ được trả về và bạn có thể sử dụng log.Fatal
để in lỗi và thoát chương trình.
Tạo Một SSH Session
Sau khi kết nối SSH được thiết lập, bạn có thể tạo một SSH session để giao tiếp với máy chủ. Dưới đây là một đoạn mã ví dụ:
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()
}
Sử dụng hàm client.NewSession
để tạo một session mới và nếu nó không thành công, một lỗi sẽ được trả về. Sử dụng từ khóa defer
để đảm bảo rằng session được đóng sau khi các thao tác hoàn tất.
Tạo Một Pseudo Terminal
Giao thức SSH cho phép tạo một pseudo terminal, mô phỏng hành vi của một terminal thực và cho phép các lệnh tương tác, chẳng hạn như shell hoặc trình soạn thảo văn bản. Dưới đây là một đoạn mã ví dụ:
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, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := session.RequestPty("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
}
Sử dụng hàm session.RequestPty
để yêu cầu một pseudo terminal. Nó yêu cầu bốn tham số: loại terminal (trong trường hợp này là "xterm"), chiều rộng và chiều cao của terminal và một map để đặt các chế độ terminal. Trong map này, ssh.ECHO
được đặt thành 0, điều này vô hiệu hóa việc lặp lại và tốc độ đầu vào và đầu ra được đặt thành 14,4 kbaud.
Thiết Lập Đầu Vào và Đầu Ra
Chỉ định đầu vào và đầu ra tiêu chuẩn cho shell từ xa. Dưới đây là một đoạn mã ví dụ:
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, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := session.RequestPty("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
//set input and output
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
}
Khởi Động Remote Shell
Một remote shell hiện đã sẵn sàng và bạn có thể sử dụng hàm session.Shell
để khởi động một shell mặc định hoặc sử dụng hàm session.Run
để thực thi một lệnh cụ thể. Dưới đây là một đoạn mã ví dụ:
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, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := session.RequestPty("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
// set input and output
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)
}
}
Khởi động một shell mặc định và nếu nó không khởi động được, một lỗi sẽ được trả về.
Chờ Session Kết Thúc
Sử dụng hàm session.Wait
để chặn cho đến khi session kết thúc. Dưới đây là một đoạn mã ví dụ:
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, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := session.RequestPty("linux", 80, 40, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
}
//set input and output
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())
}
}
Với điều này, bạn đã hoàn thành việc triển khai một SSH client cơ bản. Client này có thể kết nối đến một SSH server, khởi động một remote shell và chờ session kết thúc.
Tóm tắt
SSH client được triển khai trong bài viết này chỉ cung cấp các chức năng cơ bản nhất. Để tạo một SSH client giàu tính năng và thân thiện với người dùng, bạn cần chú ý đến nhiều chi tiết, chẳng hạn như xử lý lỗi, kết nối lại, timeouts, xử lý tín hiệu và hơn thế nữa.