Compare commits

...

8 Commits

Author SHA1 Message Date
Sylvia Crowe d1bffb5a0d merge branch 'main' into use-ssh-library 2024-01-29 13:01:50 -08:00
Sylvia Crowe df94b91e6c feat: create backend for user input requests
This is the first part of a change that allows the backend to request
user input from the frontend. Essentially, the backend will send a
request for the user to answer some query, and the frontend will send
that answer back. It is blocking, so it needs to be used within a
goroutine.

There is some placeholder code in the frontend that will be updated in
future commits. Similarly, there is some debug code in the backend
remote.go file.
2024-01-29 12:43:47 -08:00
Sylvia Crowe 1547fc856e fix: allow retry after ssh auth failure
Previously, the error status was not set when an ssh connection failed.
Because of this, an ssh connection failure would lock the failed remote
until waveterm was rebooted. This fix properly sets the error status so
this cannot happen.
2024-01-24 20:54:37 -08:00
Sylvia Crowe 37821738d8 allow switching between new and old ssh for dev
It is inconvenient to create milestones without being able to merge into
the main branch. But due to the experimental nature of the ssh changes,
it is not desired to use these changes in the main branch yet. This
change disables the new ssh launcher by default. It can be used by
changing the UseSshLibrary constant to true in remote.go. With this, it
becomes possible to merge these changes into the main branch without
them being used in production.
2024-01-24 20:18:27 -08:00
Sylvia Crowe 8e79eeccca merge branch 'main' into use-ssh-library 2024-01-24 18:29:20 -08:00
Sylvia Crowe 203f1f7505 clean up old mshell launch methods
In the debugging the addition of the ssh library, i had several versions
of the MShellProc Launch function. Since this seems mostly stable, I
have removed the old version and the experimental version in favor of
the combined version.
2024-01-24 17:19:52 -08:00
Sylvia Crowe 57a7641f82 add password and keyboard-interactive ssh auth
This adds several new ssh auth methods. In addition to the PublicKey
method used previously, this adds password authentication,
keyboard-interactive authentication, and PublicKey+Passphrase
authentication.

Furthermore, it refactores the ssh connection code into its own wavesrv
file rather than storing int in waveshell's shexec file.
2024-01-24 17:16:22 -08:00
Sylvia Crowe 03ea444030 create proof of concept ssh library integration
This is a first attempt to integrate the golang crypto/ssh library for
handling remote connections. As it stands, this features is limited to
identity files without passphrases. It needs to be expanded to include
key+passphrase and password verifications as well.
2024-01-22 23:18:49 -08:00
7 changed files with 159 additions and 30 deletions
+16
View File
@@ -50,6 +50,9 @@ import {
HistoryViewDataType, HistoryViewDataType,
AlertMessageType, AlertMessageType,
HistorySearchParams, HistorySearchParams,
UserInputRequest,
UserInputResponse,
UserInputResponsePacket,
FocusTypeStrs, FocusTypeStrs,
ScreenLinesType, ScreenLinesType,
HistoryTypeStrs, HistoryTypeStrs,
@@ -4110,6 +4113,19 @@ class Model {
update.screennumrunningcommands.num update.screennumrunningcommands.num
); );
} }
if ("userinputrequest" in update) {
let userInputRequest: UserInputRequest = update.userinputrequest;
let userInputResponse: UserInputResponse = {
type: "text",
text: "what wonderful weather we're having",
};
let userInputResponsePacket: UserInputResponsePacket = {
type: "userinputresp",
requestid: userInputRequest.requestid,
response: userInputResponse,
};
this.ws.pushMessage(userInputResponsePacket);
}
} }
updateRemotes(remotes: RemoteType[]): void { updateRemotes(remotes: RemoteType[]): void {
+22
View File
@@ -328,6 +328,7 @@ type ModelUpdateType = {
alertmessage?: AlertMessageType; alertmessage?: AlertMessageType;
screenstatusindicator?: ScreenStatusIndicatorUpdateType; screenstatusindicator?: ScreenStatusIndicatorUpdateType;
screennumrunningcommands?: ScreenNumRunningCommandsUpdateType; screennumrunningcommands?: ScreenNumRunningCommandsUpdateType;
userinputrequest?: UserInputRequest;
}; };
type HistoryViewDataType = { type HistoryViewDataType = {
@@ -586,6 +587,24 @@ type HistorySearchParams = {
filterCmds?: boolean; filterCmds?: boolean;
}; };
type UserInputRequest = {
requestid: string;
querytext: string;
responsetype: string;
};
type UserInputResponse = {
type: string;
text?: string;
confirm?: boolean;
};
type UserInputResponsePacket = {
type: string;
requestid: string;
response: UserInputResponse;
};
type RenderModeType = "normal" | "collapsed" | "expanded"; type RenderModeType = "normal" | "collapsed" | "expanded";
type WebScreen = { type WebScreen = {
@@ -771,6 +790,9 @@ export type {
RenderModeType, RenderModeType,
AlertMessageType, AlertMessageType,
HistorySearchParams, HistorySearchParams,
UserInputRequest,
UserInputResponse,
UserInputResponsePacket,
ScreenLinesType, ScreenLinesType,
FocusTypeStrs, FocusTypeStrs,
HistoryTypeStrs, HistoryTypeStrs,
+1 -1
View File
@@ -40,7 +40,7 @@ import (
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
) )
const UseSshLibrary = false const UseSshLibrary = true
const RemoteTypeMShell = "mshell" const RemoteTypeMShell = "mshell"
const DefaultTerm = "xterm-256color" const DefaultTerm = "xterm-256color"
+9
View File
@@ -6,6 +6,7 @@ package remote
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"os/user" "os/user"
"strconv" "strconv"
@@ -60,6 +61,14 @@ func ConnectToClient(opts *sstore.SSHOpts) (*ssh.Client, error) {
identityFile = configIdentity identityFile = configIdentity
} }
// test code
request := &sstore.UserInputRequestType{
ResponseType: "text",
QueryText: "unused",
}
response, _ := sstore.MainBus.GetUserInput(request)
log.Printf("response: %s\n", response.Text)
hostKeyCallback := ssh.InsecureIgnoreHostKey() hostKeyCallback := ssh.InsecureIgnoreHostKey()
var authMethods []ssh.AuthMethod var authMethods []ssh.AuthMethod
publicKeyAuth, err := createPublicKeyAuth(identityFile, opts.SSHPassword) publicKeyAuth, err := createPublicKeyAuth(identityFile, opts.SSHPassword)
+12
View File
@@ -20,6 +20,7 @@ const WatchScreenPacketStr = "watchscreen"
const FeInputPacketStr = "feinput" const FeInputPacketStr = "feinput"
const RemoteInputPacketStr = "remoteinput" const RemoteInputPacketStr = "remoteinput"
const CmdInputTextPacketStr = "cmdinputtext" const CmdInputTextPacketStr = "cmdinputtext"
const UserInputResponsePacketStr = "userinputresp"
type FeCommandPacketType struct { type FeCommandPacketType struct {
Type string `json:"type"` Type string `json:"type"`
@@ -92,12 +93,19 @@ type CmdInputTextPacketType struct {
Text utilfn.StrWithPos `json:"text"` Text utilfn.StrWithPos `json:"text"`
} }
type UserInputResponsePacketType struct {
Type string `json:"type"`
RequestId string `json:"requestid"`
Response *sstore.UserInputResponseType `json:"response"`
}
func init() { func init() {
packet.RegisterPacketType(FeCommandPacketStr, reflect.TypeOf(FeCommandPacketType{})) packet.RegisterPacketType(FeCommandPacketStr, reflect.TypeOf(FeCommandPacketType{}))
packet.RegisterPacketType(WatchScreenPacketStr, reflect.TypeOf(WatchScreenPacketType{})) packet.RegisterPacketType(WatchScreenPacketStr, reflect.TypeOf(WatchScreenPacketType{}))
packet.RegisterPacketType(FeInputPacketStr, reflect.TypeOf(FeInputPacketType{})) packet.RegisterPacketType(FeInputPacketStr, reflect.TypeOf(FeInputPacketType{}))
packet.RegisterPacketType(RemoteInputPacketStr, reflect.TypeOf(RemoteInputPacketType{})) packet.RegisterPacketType(RemoteInputPacketStr, reflect.TypeOf(RemoteInputPacketType{}))
packet.RegisterPacketType(CmdInputTextPacketStr, reflect.TypeOf(CmdInputTextPacketType{})) packet.RegisterPacketType(CmdInputTextPacketStr, reflect.TypeOf(CmdInputTextPacketType{}))
packet.RegisterPacketType(UserInputResponsePacketStr, reflect.TypeOf(UserInputResponsePacketType{}))
} }
func (*CmdInputTextPacketType) GetType() string { func (*CmdInputTextPacketType) GetType() string {
@@ -139,3 +147,7 @@ func MakeRemoteInputPacket() *RemoteInputPacketType {
func (*RemoteInputPacketType) GetType() string { func (*RemoteInputPacketType) GetType() string {
return RemoteInputPacketStr return RemoteInputPacketStr
} }
func (*UserInputResponsePacketType) GetType() string {
return UserInputResponsePacketStr
}
+9
View File
@@ -278,6 +278,15 @@ func (ws *WSState) processMessage(msgBytes []byte) error {
sstore.ScreenMemSetCmdInputText(cmdInputPk.ScreenId, cmdInputPk.Text, cmdInputPk.SeqNum) sstore.ScreenMemSetCmdInputText(cmdInputPk.ScreenId, cmdInputPk.Text, cmdInputPk.SeqNum)
return nil return nil
} }
if pk.GetType() == scpacket.UserInputResponsePacketStr {
userInputRespPk := pk.(*scpacket.UserInputResponsePacketType)
uich, ok := sstore.MainBus.UserInputCh[userInputRespPk.RequestId]
if !ok {
return fmt.Errorf("received User Input Response with invalid Id (%s): %v\n", userInputRespPk.RequestId, err)
}
uich <- userInputRespPk.Response
return nil
}
return fmt.Errorf("got ws bad message: %v", pk.GetType()) return fmt.Errorf("got ws bad message: %v", pk.GetType())
} }
+61
View File
@@ -4,10 +4,13 @@
package sstore package sstore
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"sync" "sync"
"time"
"github.com/google/uuid"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/waveshell/pkg/packet"
"github.com/wavetermdev/waveterm/waveshell/pkg/utilfn" "github.com/wavetermdev/waveterm/waveshell/pkg/utilfn"
) )
@@ -65,6 +68,7 @@ type ModelUpdate struct {
AlertMessage *AlertMessageType `json:"alertmessage,omitempty"` AlertMessage *AlertMessageType `json:"alertmessage,omitempty"`
ScreenStatusIndicator *ScreenStatusIndicatorType `json:"screenstatusindicator,omitempty"` ScreenStatusIndicator *ScreenStatusIndicatorType `json:"screenstatusindicator,omitempty"`
ScreenNumRunningCommands *ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"` ScreenNumRunningCommands *ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"`
UserInputRequest *UserInputRequestType `json:"userinputrequest,omitempty"`
} }
func (*ModelUpdate) UpdateType() string { func (*ModelUpdate) UpdateType() string {
@@ -160,6 +164,18 @@ type HistoryInfoType struct {
Show bool `json:"show"` Show bool `json:"show"`
} }
type UserInputRequestType struct {
RequestId string `json:"requestid"`
QueryText string `json:"querytext"`
ResponseType string `json:"responsetype"`
}
type UserInputResponseType struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
Confirm bool `json:"confirm,omitempty"`
}
type UpdateChannel struct { type UpdateChannel struct {
ScreenId string ScreenId string
ClientId string ClientId string
@@ -176,12 +192,14 @@ func (uch UpdateChannel) Match(screenId string) bool {
type UpdateBus struct { type UpdateBus struct {
Lock *sync.Mutex Lock *sync.Mutex
Channels map[string]UpdateChannel Channels map[string]UpdateChannel
UserInputCh map[string](chan *UserInputResponseType)
} }
func MakeUpdateBus() *UpdateBus { func MakeUpdateBus() *UpdateBus {
return &UpdateBus{ return &UpdateBus{
Lock: &sync.Mutex{}, Lock: &sync.Mutex{},
Channels: make(map[string]UpdateChannel), Channels: make(map[string]UpdateChannel),
UserInputCh: make(map[string](chan *UserInputResponseType)),
} }
} }
@@ -273,3 +291,46 @@ type ScreenNumRunningCommandsType struct {
ScreenId string `json:"screenid"` ScreenId string `json:"screenid"`
Num int `json:"num"` Num int `json:"num"`
} }
func (bus *UpdateBus) registerUserInputChannel() (string, chan *UserInputResponseType) {
bus.Lock.Lock()
defer bus.Lock.Unlock()
id := uuid.New().String()
uich := make(chan *UserInputResponseType)
bus.UserInputCh[id] = uich
return id, uich
}
func (bus *UpdateBus) unregisterUserInputChannel(id string) {
bus.Lock.Lock()
defer bus.Lock.Unlock()
delete(bus.UserInputCh, id)
}
func (bus *UpdateBus) GetUserInput(userInputRequest *UserInputRequestType) (*UserInputResponseType, error) {
// create context for timeout
ctx, cancelFn := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancelFn()
id, uich := bus.registerUserInputChannel()
userInputRequest.RequestId = id
update := &ModelUpdate{UserInputRequest: userInputRequest}
bus.SendUpdate(update)
var response *UserInputResponseType
var err error
// prepare to receive response
select {
case resp := <-uich:
response = resp
case <-ctx.Done():
err = fmt.Errorf("timed out waiting for user input")
}
bus.unregisterUserInputChannel(id)
return response, err
}