diff --git a/go.work.sum b/go.work.sum index 96bd1f831..f8f55de89 100644 --- a/go.work.sum +++ b/go.work.sum @@ -134,14 +134,28 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/wavesrv/go.mod b/wavesrv/go.mod index 252b64f29..30b29bf9d 100644 --- a/wavesrv/go.mod +++ b/wavesrv/go.mod @@ -19,9 +19,9 @@ require ( github.com/sashabaranov/go-openai v1.9.0 github.com/sawka/txwrap v0.1.2 github.com/wavetermdev/waveterm/waveshell v0.0.0 - golang.org/x/crypto v0.17.0 + golang.org/x/crypto v0.24.0 golang.org/x/mod v0.10.0 - golang.org/x/sys v0.15.0 + golang.org/x/sys v0.21.0 mvdan.cc/sh/v3 v3.7.0 ) @@ -29,6 +29,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect go.uber.org/atomic v1.7.0 // indirect ) diff --git a/wavesrv/go.sum b/wavesrv/go.sum index 13d2e2ce6..849b8366a 100644 --- a/wavesrv/go.sum +++ b/wavesrv/go.sum @@ -51,6 +51,8 @@ github.com/sashabaranov/go-openai v1.9.0 h1:NoiO++IISxxJ1pRc0n7uZvMGMake0G+FJ1XP github.com/sashabaranov/go-openai v1.9.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sawka/txwrap v0.1.2 h1:v8xS0Z1LE7/6vMZA81PYihI+0TSR6Zm1MalzzBIuXKc= github.com/sawka/txwrap v0.1.2/go.mod h1:T3nlw2gVpuolo6/XEetvBbk1oMXnY978YmBFy1UyHvw= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= @@ -61,12 +63,17 @@ 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/wavesrv/pkg/remote/sshclient.go b/wavesrv/pkg/remote/sshclient.go index b8f008208..2fdcba4f3 100644 --- a/wavesrv/pkg/remote/sshclient.go +++ b/wavesrv/pkg/remote/sshclient.go @@ -22,18 +22,21 @@ import ( "time" "github.com/kevinburke/ssh_config" + "github.com/skeema/knownhosts" "github.com/wavetermdev/waveterm/waveshell/pkg/base" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" "github.com/wavetermdev/waveterm/wavesrv/pkg/sstore" "github.com/wavetermdev/waveterm/wavesrv/pkg/userinput" "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/knownhosts" + xknownhosts "golang.org/x/crypto/ssh/knownhosts" ) type UserInputCancelError struct { Err error } +type HostKeyAlgorithmsFunction = func(hostWithPort string) (algos []string) + func (uice UserInputCancelError) Error() string { return uice.Err.Error() } @@ -356,7 +359,7 @@ func lineContainsMatch(line []byte, matches [][]byte) bool { return false } -func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { +func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, HostKeyAlgorithmsFunction, error) { rawUserKnownHostsFiles, _ := ssh_config.GetStrict(opts.SSHHost, "UserKnownHostsFile") userKnownHostsFiles := strings.Fields(rawUserKnownHostsFiles) // TODO - smarter splitting escaped spaces and quotes rawGlobalKnownHostsFiles, _ := ssh_config.GetStrict(opts.SSHHost, "GlobalKnownHostsFile") @@ -364,7 +367,7 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { osUser, err := user.Current() if err != nil { - return nil, err + return nil, nil, err } var unexpandedKnownHostsFiles []string if osUser.Username == "root" { @@ -380,7 +383,7 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { // there are no good known hosts files if len(knownHostsFiles) == 0 { - return nil, fmt.Errorf("no known_hosts files provided by ssh. defaults are overridden") + return nil, nil, fmt.Errorf("no known_hosts files provided by ssh. defaults are overridden") } var unreadableFiles []string @@ -389,9 +392,10 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { // incorrectly. if a problem file is found, it is removed from our list // and we try again var basicCallback ssh.HostKeyCallback + var keyDb *knownhosts.HostKeyDB for basicCallback == nil && len(knownHostsFiles) > 0 { var err error - basicCallback, err = knownhosts.New(knownHostsFiles...) + keyDb, err = knownhosts.NewDB(knownHostsFiles...) if serr, ok := err.(*os.PathError); ok { badFile := serr.Path unreadableFiles = append(unreadableFiles, badFile) @@ -402,35 +406,41 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { } } if len(okFiles) >= len(knownHostsFiles) { - return nil, fmt.Errorf("problem file (%s) doesn't exist. this should not be possible", badFile) + return nil, nil, fmt.Errorf("problem file (%s) doesn't exist. this should not be possible", badFile) } knownHostsFiles = okFiles } else if err != nil { // TODO handle obscure problems if possible - return nil, fmt.Errorf("known_hosts formatting error: %+v", err) + return nil, nil, fmt.Errorf("known_hosts formatting error: %+v", err) + } else { + basicCallback = keyDb.HostKeyCallback() } } + if basicCallback == nil { + return nil, nil, fmt.Errorf("no valid knownhosts files exist") + } + waveHostKeyCallback := func(hostname string, remote net.Addr, key ssh.PublicKey) error { err := basicCallback(hostname, remote, key) if err == nil { // success return nil - } else if _, ok := err.(*knownhosts.RevokedError); ok { + } else if _, ok := err.(*xknownhosts.RevokedError); ok { // revoked credentials are refused outright return err - } else if _, ok := err.(*knownhosts.KeyError); !ok { + } else if _, ok := err.(*xknownhosts.KeyError); !ok { // this is an unknown error (note the !ok is opposite of usual) return err } - serr, _ := err.(*knownhosts.KeyError) + serr, _ := err.(*xknownhosts.KeyError) if len(serr.Want) == 0 { // the key was not found // try to write to a file that could be parsed var err error for _, filename := range knownHostsFiles { - newLine := knownhosts.Line([]string{knownhosts.Normalize(hostname)}, key) + newLine := xknownhosts.Line([]string{xknownhosts.Normalize(hostname)}, key) getUserVerification := createUnknownKeyVerifier(filename, hostname, remote.String(), key) err = writeToKnownHosts(filename, newLine, getUserVerification) if err == nil { @@ -445,7 +455,7 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { // should catch cases where there is no known_hosts file if err != nil { for _, filename := range unreadableFiles { - newLine := knownhosts.Line([]string{knownhosts.Normalize(hostname)}, key) + newLine := xknownhosts.Line([]string{xknownhosts.Normalize(hostname)}, key) getUserVerification := createMissingKnownHostsVerifier(filename, hostname, remote.String(), key) err = writeToKnownHosts(filename, newLine, getUserVerification) if err == nil { @@ -495,7 +505,7 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { return fmt.Errorf("remote host identification has changed") } - updatedCallback, err := knownhosts.New(knownHostsFiles...) + updatedCallback, err := xknownhosts.New(knownHostsFiles...) if err != nil { return err } @@ -503,7 +513,7 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { return updatedCallback(hostname, remote, key) } - return waveHostKeyCallback, nil + return waveHostKeyCallback, keyDb.HostKeyAlgorithms, nil } func DialContext(ctx context.Context, network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { @@ -569,17 +579,18 @@ func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDispla authMethods = append(authMethods, authMethod) } - hostKeyCallback, err := createHostKeyCallback(opts) + hostKeyCallback, hostKeyAlgorithms, err := createHostKeyCallback(opts) if err != nil { return nil, err } - clientConfig := &ssh.ClientConfig{ - User: sshKeywords.User, - Auth: authMethods, - HostKeyCallback: hostKeyCallback, - } networkAddr := sshKeywords.HostName + ":" + sshKeywords.Port + clientConfig := &ssh.ClientConfig{ + User: sshKeywords.User, + Auth: authMethods, + HostKeyCallback: hostKeyCallback, + HostKeyAlgorithms: hostKeyAlgorithms(networkAddr), + } return DialContext(connCtx, "tcp", networkAddr, clientConfig) }