diff --git a/frontend/app/element/search.tsx b/frontend/app/element/search.tsx index 923ed464ce..085d18c0cd 100644 --- a/frontend/app/element/search.tsx +++ b/frontend/app/element/search.tsx @@ -29,6 +29,7 @@ const SearchComponent = ({ isOpen: isOpenAtom, focusInput: focusInputAtom, anchorRef, + searchInputRef, offsetX = 10, offsetY = 10, onSearch, @@ -36,13 +37,12 @@ const SearchComponent = ({ onPrev, }: SearchProps) => { const localInputRef = useRef(null); - const inputRef = providedInputRef || localInputRef; + const inputRef = searchInputRef || localInputRef; const [isOpen, setIsOpen] = useAtom(isOpenAtom); const [search, setSearch] = useAtom(searchAtom); const [index, setIndex] = useAtom(indexAtom); const [numResults, setNumResults] = useAtom(numResultsAtom); const [focusInputCounter, setFocusInputCounter] = useAtom(focusInputAtom); - const inputRef = useRef(null); const handleOpenChange = useCallback((open: boolean) => { setIsOpen(open); diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index b3220ab6d1..bb07f466b0 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -852,6 +852,7 @@ declare global { "ssh:identityagent"?: string; "ssh:identitiesonly"?: boolean; "ssh:proxyjump"?: string[]; + "ssh:proxycommand"?: string; "ssh:userknownhostsfile"?: string[]; "ssh:globalknownhostsfile"?: string[]; }; diff --git a/go.mod b/go.mod index 2165fda9dc..460763ba8f 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,6 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index 41ae6f4cda..139b34b195 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,6 @@ github.com/mattn/go-sqlite3 v1.14.40 h1:f7+saIsbq4EF86mUqe0uiecQOJYMOdfi5uATADmU github.com/mattn/go-sqlite3 v1.14.40/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hxI0Q8L83UAJPpeJ+gSpn3cCU/tjd3eg= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= diff --git a/pkg/remote/connutil.go b/pkg/remote/connutil.go index 89d53f6ae4..873891071d 100644 --- a/pkg/remote/connutil.go +++ b/pkg/remote/connutil.go @@ -26,7 +26,7 @@ import ( "golang.org/x/crypto/ssh" ) -var userHostRe = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9._@\\-]*@)?([a-zA-Z0-9][a-zA-Z0-9.-]*)(?::([0-9]+))?$`) +var userHostRe = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9._@\\-]*@)?([a-zA-Z0-9][a-zA-Z0-9._-]*)(?::([0-9]+))?$`) func ParseOpts(input string) (*SSHOpts, error) { m := userHostRe.FindStringSubmatch(input) diff --git a/pkg/remote/sshclient.go b/pkg/remote/sshclient.go index b1c9faca21..c76bfadb46 100644 --- a/pkg/remote/sshclient.go +++ b/pkg/remote/sshclient.go @@ -838,9 +838,69 @@ func createClientConfig(connCtx context.Context, sshKeywords *wconfig.ConnKeywor }, nil } +// parseProxyCommandAsJump attempts to convert a common ProxyCommand pattern +// (ssh -W %h:%p ) into a ProxyJump host string. +// It returns the jump host and true on success. +// Pattern: ssh [options] -W %h:%p [user@][:port] +func parseProxyCommandAsJump(proxyCmd string) (string, bool) { + args := strings.Fields(proxyCmd) + if len(args) < 3 { + return "", false + } + + sshBin := args[0] + baseName := filepath.Base(sshBin) + if baseName != "ssh" { + return "", false + } + + var jumpHost, jumpUser, jumpPort string + foundW := false + + for i := 1; i < len(args); i++ { + arg := args[i] + switch { + case arg == "-W" && i+1 < len(args): + nextArg := args[i+1] + if !strings.Contains(nextArg, "%h") { + return "", false + } + foundW = true + i++ + case arg == "-p" && i+1 < len(args): + jumpPort = args[i+1] + i++ + case arg == "-l" && i+1 < len(args): + jumpUser = args[i+1] + i++ + case strings.HasPrefix(arg, "-"): + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + i++ + } + default: + if jumpHost == "" { + jumpHost = arg + } + } + } + + if jumpHost == "" || !foundW { + return "", false + } + + if jumpUser != "" && !strings.Contains(jumpHost, "@") { + jumpHost = jumpUser + "@" + jumpHost + } + if jumpPort != "" && !strings.Contains(jumpHost, ":") { + jumpHost = jumpHost + ":" + jumpPort + } + return jumpHost, true +} + func connectInternal(ctx context.Context, networkAddr string, clientConfig *ssh.ClientConfig, currentClient *ssh.Client) (*ssh.Client, error) { var clientConn net.Conn var err error + if currentClient == nil { d := net.Dialer{Timeout: clientConfig.Timeout} blocklogger.Infof(ctx, "[conndebug] ssh dial %s\n", networkAddr) @@ -927,6 +987,18 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh. sshKeywords.SshIdentityFile = append(sshKeywords.SshIdentityFile, internalSshConfigKeywords.SshIdentityFile...) sshKeywords.SshIdentityFile = append(sshKeywords.SshIdentityFile, sshConfigKeywords.SshIdentityFile...) + proxyCmd := utilfn.SafeDeref(sshKeywords.SshProxyCommand) + if proxyCmd != "" && len(sshKeywords.SshProxyJump) == 0 { + if jumpHost, ok := parseProxyCommandAsJump(proxyCmd); ok { + blocklogger.Infof(connCtx, "[conndebug] converted proxycommand to proxyjump: %s\n", jumpHost) + sshKeywords.SshProxyJump = []string{jumpHost} + sshKeywords.SshProxyCommand = nil + proxyCmd = "" + } else { + blocklogger.Infof(connCtx, "[conndebug] proxycommand not convertible to proxyjump, skipping: %s\n", proxyCmd) + } + } + for _, proxyName := range sshKeywords.SshProxyJump { proxyOpts, err := ParseOpts(proxyName) if err != nil { @@ -1100,6 +1172,12 @@ func findSshConfigKeywords(hostPattern string) (connKeywords *wconfig.ConnKeywor } sshKeywords.SshProxyJump = append(sshKeywords.SshProxyJump, proxyJumpName) } + + proxyCommandRaw, _ := WaveSshConfigUserSettings().GetStrict(hostPattern, "ProxyCommand") + proxyCommandClean := trimquotes.TryTrimQuotes(proxyCommandRaw) + if proxyCommandClean != "" { + sshKeywords.SshProxyCommand = &proxyCommandClean + } rawUserKnownHostsFile, _ := WaveSshConfigUserSettings().GetStrict(hostPattern, "UserKnownHostsFile") sshKeywords.SshUserKnownHostsFile = strings.Fields(rawUserKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes rawGlobalKnownHostsFile, _ := WaveSshConfigUserSettings().GetStrict(hostPattern, "GlobalKnownHostsFile") @@ -1130,6 +1208,7 @@ func findSshDefaults(hostPattern string) (connKeywords *wconfig.ConnKeywords, ou sshKeywords.SshProxyJump = []string{} sshKeywords.SshUserKnownHostsFile = strings.Fields(ssh_config.Default("UserKnownHostsFile")) sshKeywords.SshGlobalKnownHostsFile = strings.Fields(ssh_config.Default("GlobalKnownHostsFile")) + sshKeywords.SshProxyCommand = nil return sshKeywords, nil } @@ -1197,6 +1276,9 @@ func mergeKeywords(oldKeywords *wconfig.ConnKeywords, newKeywords *wconfig.ConnK if newKeywords.SshProxyJump != nil { outKeywords.SshProxyJump = newKeywords.SshProxyJump } + if newKeywords.SshProxyCommand != nil { + outKeywords.SshProxyCommand = newKeywords.SshProxyCommand + } if newKeywords.SshUserKnownHostsFile != nil { outKeywords.SshUserKnownHostsFile = newKeywords.SshUserKnownHostsFile } diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 67118b1670..831dbb6623 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -419,6 +419,7 @@ type ConnKeywords struct { SshIdentityAgent *string `json:"ssh:identityagent,omitempty"` SshIdentitiesOnly *bool `json:"ssh:identitiesonly,omitempty"` SshProxyJump []string `json:"ssh:proxyjump,omitempty"` + SshProxyCommand *string `json:"ssh:proxycommand,omitempty"` SshUserKnownHostsFile []string `json:"ssh:userknownhostsfile,omitempty"` SshGlobalKnownHostsFile []string `json:"ssh:globalknownhostsfile,omitempty"` } diff --git a/schema/connections.json b/schema/connections.json index 3cb33b5d55..b4c947525e 100644 --- a/schema/connections.json +++ b/schema/connections.json @@ -114,6 +114,9 @@ }, "type": "array" }, + "ssh:proxycommand": { + "type": "string" + }, "ssh:userknownhostsfile": { "items": { "type": "string"