SSH หรือ Secure Shell เป็นโปรโตคอลเครือข่ายที่ใช้สำหรับการล็อกอินเข้าสู่คอมพิวเตอร์เครื่องอื่นบนเครือข่ายจากระยะไกลอย่างปลอดภัย ผมเชื่อว่านักพัฒนาแบ็กเอนด์ส่วนใหญ่คุ้นเคยกับ SSH เครื่องมือเชลล์ทั่วไปที่ใช้สำหรับการล็อกอินเข้าสู่เซิร์ฟเวอร์ เช่น Xshell, SecureCRT และ iTerm2 ล้วนใช้โปรโตคอล SSH เป็นพื้นฐาน ใน Golang แพ็กเกจ crypto/ssh
มีฟังก์ชันสำหรับการใช้งาน SSH client ในบทความนี้ เราจะอธิบายรายละเอียดวิธีการใช้งาน SSH client โดยใช้ Golang
การสร้างการกำหนดค่า SSH Client
อันดับแรก เราต้องกำหนดค่าพารามิเตอร์สำหรับ SSH client เพื่อเชื่อมต่อกับเซิร์ฟเวอร์ การกำหนดค่าพื้นฐานประกอบด้วย username
, authentication method
และ host key callback
นี่คือตัวอย่างโค้ด:
package main
import "golang.org/x/crypto/ssh"
func main() {
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
}
ในการกำหนดค่านี้ ชื่อผู้ใช้ถูกตั้งค่าเป็น "username" และรหัสผ่านถูกตั้งค่าเป็น "password" ฟังก์ชัน ssh.Password
ใช้เพื่อสร้างวิธีการตรวจสอบสิทธิ์ด้วยรหัสผ่าน ฟังก์ชัน HostKeyCallback
จะถูกเรียกใช้ทุกครั้งที่มีการเชื่อมต่อกับโฮสต์ใหม่ และใช้เพื่อตรวจสอบ host key ของเซิร์ฟเวอร์ ในตัวอย่างนี้ ssh.InsecureIgnoreHostKey
ถูกใช้ ซึ่งหมายความว่า host key ใดๆ ก็ได้รับการยอมรับ ไม่แนะนำให้ใช้สิ่งนี้ในสภาพแวดล้อมการผลิตเนื่องจากการไม่ตรวจสอบ host key ทำให้เกิดความเสี่ยงด้านความปลอดภัย
การเชื่อมต่อกับ SSH Server
ฟังก์ชัน ssh.Dial
ใช้เพื่อเชื่อมต่อกับ SSH server จากระยะไกล ต้องใช้พารามิเตอร์สามตัว: ประเภทเครือข่าย (โดยปกติคือ "tcp"), ที่อยู่และพอร์ตของเซิร์ฟเวอร์ และอ็อบเจ็กต์การกำหนดค่าที่สร้างไว้ก่อนหน้านี้ นี่คือตัวอย่างโค้ด:
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)
}
}
เชื่อมต่อกับเซิร์ฟเวอร์ด้วยที่อยู่ IP 192.168.3.111 บนพอร์ต 22 (พอร์ตเริ่มต้นสำหรับโปรโตคอล SSH) หากการเชื่อมต่อล้มเหลว ข้อผิดพลาดจะถูกส่งกลับ และคุณสามารถใช้ log.Fatal
เพื่อพิมพ์ข้อผิดพลาดและออกจากโปรแกรม
การสร้าง SSH Session
เมื่อการเชื่อมต่อ SSH ถูกสร้างขึ้นแล้ว คุณสามารถสร้าง SSH session เพื่อสื่อสารกับเซิร์ฟเวอร์ได้ นี่คือตัวอย่างโค้ด:
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()
}
ใช้ฟังก์ชัน client.NewSession
เพื่อสร้าง session ใหม่ และหากล้มเหลว ข้อผิดพลาดจะถูกส่งกลับ ใช้คีย์เวิร์ด defer
เพื่อให้แน่ใจว่า session จะถูกปิดหลังจากดำเนินการเสร็จสิ้น
การสร้าง Pseudo Terminal
โปรโตคอล SSH อนุญาตให้สร้าง pseudo terminal ซึ่งจำลองพฤติกรรมของ terminal จริงและอนุญาตให้ใช้คำสั่งแบบโต้ตอบ เช่น เชลล์หรือโปรแกรมแก้ไขข้อความ นี่คือตัวอย่างโค้ด:
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)
}
}
ใช้ฟังก์ชัน session.RequestPty
เพื่อขอ pseudo terminal ต้องใช้พารามิเตอร์สี่ตัว: ประเภท terminal (ในกรณีนี้คือ "xterm"), ความกว้างและความสูงของ terminal และแผนที่เพื่อตั้งค่าโหมด terminal ในแผนที่นี้ ssh.ECHO
ถูกตั้งค่าเป็น 0 ซึ่งปิดใช้งานการสะท้อน และความเร็วในการป้อนข้อมูลและเอาต์พุตถูกตั้งค่าเป็น 14.4 kbaud
การตั้งค่า Input และ Output
ระบุอินพุตและเอาต์พุตมาตรฐานสำหรับเชลล์ระยะไกล นี่คือตัวอย่างโค้ด:
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
}
การเริ่ม Remote Shell
ตอนนี้ remote shell พร้อมแล้ว และคุณสามารถใช้ฟังก์ชัน session.Shell
เพื่อเริ่มเชลล์เริ่มต้น หรือใช้ฟังก์ชัน session.Run
เพื่อเรียกใช้คำสั่งเฉพาะ นี่คือตัวอย่างโค้ด:
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)
}
}
เริ่มเชลล์เริ่มต้น และหากเริ่มไม่สำเร็จ ข้อผิดพลาดจะถูกส่งกลับ
การรอให้ Session สิ้นสุด
ใช้ฟังก์ชัน session.Wait
เพื่อบล็อกจนกว่า session จะสิ้นสุด นี่คือตัวอย่างโค้ด:
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())
}
}
ด้วยสิ่งนี้ คุณได้ดำเนินการสร้าง SSH client พื้นฐานเสร็จสมบูรณ์แล้ว client นี้สามารถเชื่อมต่อกับ SSH server เริ่ม remote shell และรอให้ session สิ้นสุด
สรุป
SSH client ที่ใช้งานในบทความนี้มีฟังก์ชันพื้นฐานที่สุดเท่านั้น ในการสร้าง SSH client ที่มีคุณสมบัติครบถ้วนและใช้งานง่าย คุณต้องใส่ใจในรายละเอียดมากมาย เช่น การจัดการข้อผิดพลาด การเชื่อมต่อใหม่ การหมดเวลา การจัดการสัญญาณ และอื่นๆ อีกมากมาย