跳板机管理平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
6.3 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "os/signal"
  9. "os/user"
  10. "runtime"
  11. "strconv"
  12. "strings"
  13. "syscall"
  14. "time"
  15. "github.com/manifoldco/promptui"
  16. _ "github.com/mattn/go-sqlite3"
  17. "golang.org/x/crypto/ssh"
  18. "golang.org/x/crypto/ssh/terminal"
  19. )
  20. var (
  21. DarwinAuthFileFmt = "/Users/%s/.ssh/id_rsa"
  22. LinuxAuthFileFmt = "/home/%s/.ssh/id_rsa"
  23. AuthFileFmt = LinuxAuthFileFmt
  24. DatabaseFile = "/usr/local/jumpserver/jumpserver.db"
  25. QuitSelect = "quit"
  26. )
  27. type SSHTerminal struct {
  28. Session *ssh.Session
  29. exitMsg string
  30. stdout io.Reader
  31. stdin io.Writer
  32. stderr io.Reader
  33. }
  34. // ssh server
  35. type HostServer struct {
  36. Name string
  37. Host string
  38. Port int
  39. }
  40. // SSHUser ssh user
  41. type SSHUser struct {
  42. Username string
  43. Password string
  44. IdentityFile string
  45. }
  46. func init() {
  47. if runtime.GOOS == "linux" {
  48. AuthFileFmt = LinuxAuthFileFmt
  49. } else if runtime.GOOS == "darwin" {
  50. AuthFileFmt = DarwinAuthFileFmt
  51. } else {
  52. fmt.Printf("program not support os(%s)\n", runtime.GOOS)
  53. }
  54. }
  55. func (t *SSHTerminal) updateTerminalSize() {
  56. go func() {
  57. // 窗口大小改变
  58. sigwinchCh := make(chan os.Signal, 1)
  59. signal.Notify(sigwinchCh, syscall.SIGWINCH)
  60. fd := int(os.Stdin.Fd())
  61. termWidth, termHeight, err := terminal.GetSize(fd)
  62. if err != nil {
  63. fmt.Println(err)
  64. }
  65. for {
  66. select {
  67. case sigwinch := <-sigwinchCh:
  68. if sigwinch == nil {
  69. return
  70. }
  71. currTermWidth, currTermHeight, err := terminal.GetSize(fd)
  72. // 窗口没有发生改变
  73. if currTermHeight == termHeight && currTermWidth == termWidth {
  74. continue
  75. }
  76. // 调整窗体大小
  77. t.Session.WindowChange(currTermHeight, currTermWidth)
  78. if err != nil {
  79. fmt.Printf("Unable to send window-change reqest: %s.", err)
  80. continue
  81. }
  82. termWidth, termHeight = currTermWidth, currTermHeight
  83. }
  84. }
  85. }()
  86. }
  87. func (t *SSHTerminal) interactiveSession() error {
  88. defer func() {
  89. if t.exitMsg == "" {
  90. fmt.Fprintln(os.Stdout, "the connection was closed on the remote side on ", time.Now().Format(time.RFC822))
  91. } else {
  92. fmt.Fprintln(os.Stdout, t.exitMsg)
  93. }
  94. }()
  95. fd := int(os.Stdin.Fd())
  96. // raw模式 cbreak
  97. state, err := terminal.MakeRaw(fd)
  98. if err != nil {
  99. return err
  100. }
  101. defer terminal.Restore(fd, state)
  102. // 获取当前窗体大小
  103. termWidth, termHeight, err := terminal.GetSize(fd)
  104. if err != nil {
  105. return err
  106. }
  107. termType := os.Getenv("TERM")
  108. if termType == "" {
  109. termType = "xterm-256color"
  110. }
  111. // 设置tty
  112. err = t.Session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{
  113. ssh.ECHO: 1,
  114. ssh.TTY_OP_ISPEED: 14400,
  115. ssh.TTY_OP_OSPEED: 14400,
  116. })
  117. if err != nil {
  118. return err
  119. }
  120. // 动态调整窗体大小
  121. t.updateTerminalSize()
  122. t.stdin, err = t.Session.StdinPipe()
  123. if err != nil {
  124. return err
  125. }
  126. t.stdout, err = t.Session.StdoutPipe()
  127. if err != nil {
  128. return err
  129. }
  130. t.stderr, err = t.Session.StderrPipe()
  131. go io.Copy(os.Stderr, t.stderr)
  132. go io.Copy(os.Stdout, t.stdout)
  133. go func() {
  134. buf := make([]byte, 128)
  135. for {
  136. n, err := os.Stdin.Read(buf)
  137. if err != nil {
  138. fmt.Println(err)
  139. return
  140. }
  141. if n > 0 {
  142. _, err = t.stdin.Write(buf[:n])
  143. if err != nil {
  144. fmt.Println(err)
  145. t.exitMsg = err.Error()
  146. return
  147. }
  148. }
  149. }
  150. }()
  151. err = t.Session.Shell()
  152. if err != nil {
  153. return err
  154. }
  155. err = t.Session.Wait()
  156. if err != nil {
  157. return err
  158. }
  159. return nil
  160. }
  161. func NewTerminal(server *HostServer, sshUser *SSHUser) (err error) {
  162. key, err := ioutil.ReadFile(fmt.Sprintf(AuthFileFmt, sshUser.Username))
  163. if err != nil {
  164. return
  165. }
  166. signer, err := ssh.ParsePrivateKey(key)
  167. if err != nil {
  168. return
  169. }
  170. sshConfig := &ssh.ClientConfig{
  171. User: sshUser.Username,
  172. Auth: []ssh.AuthMethod{
  173. ssh.Password(sshUser.Password),
  174. ssh.PublicKeys(signer),
  175. },
  176. HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  177. }
  178. client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", server.Host, server.Port), sshConfig)
  179. if err != nil {
  180. fmt.Println(err)
  181. }
  182. defer client.Close()
  183. session, err := client.NewSession()
  184. if err != nil {
  185. return err
  186. }
  187. defer session.Close()
  188. s := SSHTerminal{
  189. Session: session,
  190. }
  191. return s.interactiveSession()
  192. }
  193. func handlerSignal() {
  194. c := make(chan os.Signal, 1)
  195. signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
  196. for {
  197. s := <-c
  198. switch s {
  199. case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
  200. return
  201. case syscall.SIGHUP:
  202. default:
  203. return
  204. }
  205. }
  206. }
  207. func main() {
  208. // 获取当前用户
  209. userInfo, err := user.Current()
  210. if err != nil {
  211. fmt.Println("user.Current failed!")
  212. return
  213. }
  214. // 连接数据库
  215. db, err := sql.Open("sqlite3", DatabaseFile)
  216. if err != nil {
  217. fmt.Printf("open database failed! err=%v", err)
  218. return
  219. }
  220. defer db.Close()
  221. // 查询当前用户有访问权限的主机
  222. rows, err := db.Query(fmt.Sprintf("SELECT name, ip, port FROM hosts where isdelete=0 and name in(select hostname from hostuser where username='%s');", userInfo.Username))
  223. if err != nil {
  224. fmt.Printf("db.Query failed! err=%v", err)
  225. return
  226. }
  227. defer rows.Close()
  228. // ui展示列表
  229. menuLabels := make([]string, 0)
  230. menuLabels = append(menuLabels, QuitSelect) // 退出
  231. for rows.Next() {
  232. var (
  233. name string
  234. ip string
  235. port int
  236. )
  237. err = rows.Scan(&name, &ip, &port)
  238. if err != nil {
  239. fmt.Printf("rows.Scan failed! err=%v", err)
  240. continue
  241. }
  242. menuLabels = append(menuLabels, fmt.Sprintf("%s:%s:%d", name, ip, port))
  243. }
  244. // 选项列表
  245. prompt := promptui.Select{
  246. Label: "Select Host (quit for ctrl+c or select quit)",
  247. Items: menuLabels,
  248. Size: len(menuLabels),
  249. }
  250. for {
  251. _, selectLabel, err := prompt.Run()
  252. if err != nil {
  253. fmt.Printf("Prompt failed %v\n", err)
  254. return
  255. }
  256. fmt.Printf("You choose %q\n", selectLabel)
  257. if selectLabel == QuitSelect {
  258. return
  259. }
  260. // 获取选中信息
  261. hostItems := strings.Split(selectLabel, ":")
  262. if len(hostItems) != 3 {
  263. fmt.Printf("invalid ssh host: %s", selectLabel)
  264. continue
  265. }
  266. port, err := strconv.ParseInt(hostItems[2], 10, 32)
  267. if err != nil {
  268. fmt.Printf("invalid ssh port: %s", selectLabel[2])
  269. continue
  270. }
  271. server := &HostServer{Name: hostItems[0], Host: hostItems[1], Port: int(port)}
  272. user := &SSHUser{Username: userInfo.Username, IdentityFile: fmt.Sprintf(AuthFileFmt, userInfo.Username)}
  273. // 新建一个终端
  274. err = NewTerminal(server, user)
  275. if err != nil {
  276. fmt.Printf("NewTerminal err=%v\n", err)
  277. }
  278. }
  279. }