mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
SSH Port (#111)
This enables basic ssh for connections using publickey auth without a
passphrase. It can be established by creating a widget with the "meta"
property set to
```
{
"connection": "<user>@<host>:<port>"
}
```
where the :<port> is optional.
---------
Co-authored-by: sawka <mike.sawka@gmail.com>
This commit is contained in:
@@ -14,12 +14,14 @@ require (
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/kevinburke/ssh_config v1.2.0
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/sawka/txwrap v0.2.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/wavetermdev/waveterm/wavesrv v0.0.0-20240508181017-d07068c09d94
|
||||
golang.org/x/term v0.17.0
|
||||
golang.org/x/crypto v0.25.0
|
||||
golang.org/x/term v0.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -30,5 +32,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0.0.0-20240306041034-17e2087ebde2
|
||||
|
||||
@@ -53,15 +53,19 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/wavetermdev/ssh_config v0.0.0-20240306041034-17e2087ebde2 h1:onqZrJVap1sm15AiIGTfWzdr6cEF0KdtddeuuOVhzyY=
|
||||
github.com/wavetermdev/ssh_config v0.0.0-20240306041034-17e2087ebde2/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/wavetermdev/waveterm/wavesrv v0.0.0-20240508181017-d07068c09d94 h1:/SPCxd4KHlS4eRTreYEXWFRr8WfRFBcChlV5cgkaO58=
|
||||
github.com/wavetermdev/waveterm/wavesrv v0.0.0-20240508181017-d07068c09d94/go.mod h1:ywoo7DXdYueQ0tTPhVoB+wzRTgERSE19EA3mR6KGRaI=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -285,9 +285,20 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[str
|
||||
} else {
|
||||
return fmt.Errorf("unknown controller type %q", bc.ControllerType)
|
||||
}
|
||||
shellProc, err := shellexec.StartShellProc(rc.TermSize, cmdStr, cmdOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
// pty buffer equivalent for ssh? i think if i have the ecmd or session i can manage it with output
|
||||
// pty write needs stdin, so if i provide that, i might be able to write that way
|
||||
// need a way to handle setsize???
|
||||
var shellProc *shellexec.ShellProc
|
||||
if remoteName, ok := blockMeta["connection"].(string); ok && remoteName != "" {
|
||||
shellProc, err = shellexec.StartRemoteShellProc(rc.TermSize, cmdStr, cmdOpts, remoteName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
shellProc, err = shellexec.StartShellProc(rc.TermSize, cmdStr, cmdOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bc.UpdateControllerAndSendUpdate(func() bool {
|
||||
bc.ShellProc = shellProc
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
||||
package shellexec
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type ConnInterface interface {
|
||||
Kill()
|
||||
Wait() error
|
||||
Start() error
|
||||
StdinPipe() (io.WriteCloser, error)
|
||||
StdoutPipe() (io.ReadCloser, error)
|
||||
StderrPipe() (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type CmdWrap struct {
|
||||
Cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func (cw CmdWrap) Kill() {
|
||||
cw.Cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func (cw CmdWrap) Wait() error {
|
||||
return cw.Cmd.Wait()
|
||||
}
|
||||
|
||||
func (cw CmdWrap) Start() error {
|
||||
defer func() {
|
||||
for _, extraFile := range cw.Cmd.ExtraFiles {
|
||||
if extraFile != nil {
|
||||
extraFile.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return cw.Cmd.Start()
|
||||
}
|
||||
|
||||
func (cw CmdWrap) StdinPipe() (io.WriteCloser, error) {
|
||||
return cw.Cmd.StdinPipe()
|
||||
}
|
||||
|
||||
func (cw CmdWrap) StdoutPipe() (io.ReadCloser, error) {
|
||||
return cw.Cmd.StdoutPipe()
|
||||
}
|
||||
|
||||
func (cw CmdWrap) StderrPipe() (io.ReadCloser, error) {
|
||||
return cw.Cmd.StderrPipe()
|
||||
}
|
||||
|
||||
type SessionWrap struct {
|
||||
Session *ssh.Session
|
||||
StartCmd string
|
||||
Tty *os.File
|
||||
}
|
||||
|
||||
func (sw SessionWrap) Kill() {
|
||||
sw.Tty.Close()
|
||||
sw.Session.Close()
|
||||
}
|
||||
|
||||
func (sw SessionWrap) Wait() error {
|
||||
return sw.Session.Wait()
|
||||
}
|
||||
|
||||
func (sw SessionWrap) Start() error {
|
||||
return sw.Session.Start(sw.StartCmd)
|
||||
}
|
||||
|
||||
func (sw SessionWrap) StdinPipe() (io.WriteCloser, error) {
|
||||
return sw.Session.StdinPipe()
|
||||
}
|
||||
|
||||
func (sw SessionWrap) StdoutPipe() (io.ReadCloser, error) {
|
||||
stdoutReader, err := sw.Session.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.NopCloser(stdoutReader), nil
|
||||
}
|
||||
|
||||
func (sw SessionWrap) StderrPipe() (io.ReadCloser, error) {
|
||||
stderrReader, err := sw.Session.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.NopCloser(stderrReader), nil
|
||||
}
|
||||
@@ -5,17 +5,24 @@ package shellexec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/wavetermdev/thenextwave/pkg/remote"
|
||||
"github.com/wavetermdev/thenextwave/pkg/util/shellutil"
|
||||
"github.com/wavetermdev/thenextwave/pkg/wavebase"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type TermSize struct {
|
||||
@@ -31,7 +38,7 @@ type CommandOptsType struct {
|
||||
}
|
||||
|
||||
type ShellProc struct {
|
||||
Cmd *exec.Cmd
|
||||
Cmd ConnInterface
|
||||
Pty *os.File
|
||||
CloseOnce *sync.Once
|
||||
DoneCh chan any // closed after proc.Wait() returns
|
||||
@@ -39,9 +46,9 @@ type ShellProc struct {
|
||||
}
|
||||
|
||||
func (sp *ShellProc) Close() {
|
||||
sp.Cmd.Process.Kill()
|
||||
sp.Cmd.Kill()
|
||||
go func() {
|
||||
_, waitErr := sp.Cmd.Process.Wait()
|
||||
waitErr := sp.Cmd.Wait()
|
||||
sp.SetWaitErrorAndSignalDone(waitErr)
|
||||
sp.Pty.Close()
|
||||
}()
|
||||
@@ -104,6 +111,81 @@ func checkCwd(cwd string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var userHostRe = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9._@\\-]*@)?([a-z0-9][a-z0-9.-]*)(?::([0-9]+))?$`)
|
||||
|
||||
func StartRemoteShellProc(termSize TermSize, cmdStr string, cmdOpts CommandOptsType, remoteName string) (*ShellProc, error) {
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
var shellPath string
|
||||
if cmdStr == "" {
|
||||
shellPath = "/bin/bash"
|
||||
} else {
|
||||
shellPath = cmdStr
|
||||
}
|
||||
|
||||
var shellOpts []string
|
||||
if cmdOpts.Login {
|
||||
shellOpts = append(shellOpts, "-l")
|
||||
}
|
||||
if cmdOpts.Interactive {
|
||||
shellOpts = append(shellOpts, "-i")
|
||||
}
|
||||
cmdCombined := fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
|
||||
log.Print(cmdCombined)
|
||||
m := userHostRe.FindStringSubmatch(remoteName)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("invalid format of user@host argument")
|
||||
}
|
||||
remoteUser, remoteHost, remotePortStr := m[1], m[2], m[3]
|
||||
remoteUser = strings.Trim(remoteUser, "@")
|
||||
var remotePort int
|
||||
if remotePortStr != "" {
|
||||
var err error
|
||||
remotePort, err = strconv.Atoi(remotePortStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid port specified on user@host argument")
|
||||
}
|
||||
}
|
||||
|
||||
client, err := remote.ConnectToClient(ctx, &remote.SSHOpts{SSHHost: remoteHost, SSHUser: remoteUser, SSHPort: remotePort}) //todo specify or remove opts
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// todo: connect pty output, etc
|
||||
// redirect to fake pty???
|
||||
|
||||
cmdPty, cmdTty, err := pty.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening new pty: %w", err)
|
||||
}
|
||||
term.MakeRaw(int(cmdTty.Fd()))
|
||||
if termSize.Rows == 0 || termSize.Cols == 0 {
|
||||
termSize.Rows = shellutil.DefaultTermRows
|
||||
termSize.Cols = shellutil.DefaultTermCols
|
||||
}
|
||||
if termSize.Rows <= 0 || termSize.Cols <= 0 {
|
||||
return nil, fmt.Errorf("invalid term size: %v", termSize)
|
||||
}
|
||||
pty.Setsize(cmdPty, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
|
||||
session.Stdin = cmdTty
|
||||
session.Stdout = cmdTty
|
||||
session.Stderr = cmdTty
|
||||
session.RequestPty("xterm-256color", termSize.Rows, termSize.Cols, nil)
|
||||
|
||||
sessionWrap := SessionWrap{session, cmdCombined, cmdTty}
|
||||
err = sessionWrap.Start()
|
||||
if err != nil {
|
||||
cmdPty.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &ShellProc{Cmd: sessionWrap, Pty: cmdPty, CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
|
||||
}
|
||||
|
||||
func StartShellProc(termSize TermSize, cmdStr string, cmdOpts CommandOptsType) (*ShellProc, error) {
|
||||
var ecmd *exec.Cmd
|
||||
var shellOpts []string
|
||||
@@ -156,7 +238,7 @@ func StartShellProc(termSize TermSize, cmdStr string, cmdOpts CommandOptsType) (
|
||||
cmdPty.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &ShellProc{Cmd: ecmd, Pty: cmdPty, CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
|
||||
return &ShellProc{Cmd: CmdWrap{ecmd}, Pty: cmdPty, CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
|
||||
}
|
||||
|
||||
func RunSimpleCmdInPty(ecmd *exec.Cmd, termSize TermSize) ([]byte, error) {
|
||||
|
||||
Reference in New Issue
Block a user