Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit9435f6c

Browse files
committed
feat: Add support for VS Code and JetBrains Gateway via SSH
This fixes various bugs that made this not work:- Incorrect max message size in `peer`- Incorrect reader buffer size in `peer`- Lack of SFTP support in `agent`- Lack of direct-tcpip support in `agent`- Misuse of command from session. It should always use the shell- Blocking on SSH session, only allowing one at a timeFixes#833 too.
1 parent8fecb67 commit9435f6c

File tree

7 files changed

+91
-49
lines changed

7 files changed

+91
-49
lines changed

‎.github/workflows/coder.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ jobs:
158158
terraform_version:1.1.2
159159
terraform_wrapper:false
160160

161+
-name:Install socat
162+
if:runner.os == 'Linux'
163+
run:apt-get install -y socat
164+
161165
-name:Test with Mock Database
162166
shell:bash
163167
env:

‎agent/agent.go

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"github.com/coder/coder/pty"
2222
"github.com/coder/retry"
2323

24+
"github.com/pkg/sftp"
25+
2426
"github.com/gliderlabs/ssh"
2527
gossh"golang.org/x/crypto/ssh"
2628
"golang.org/x/xerrors"
@@ -120,7 +122,7 @@ func (a *agent) handlePeerConn(ctx context.Context, conn *peer.Conn) {
120122

121123
switchchannel.Protocol() {
122124
case"ssh":
123-
a.sshServer.HandleConn(channel.NetConn())
125+
goa.sshServer.HandleConn(channel.NetConn())
124126
default:
125127
a.options.Logger.Warn(ctx,"unhandled protocol from channel",
126128
slog.F("protocol",channel.Protocol()),
@@ -145,7 +147,10 @@ func (a *agent) init(ctx context.Context) {
145147
sshLogger:=a.options.Logger.Named("ssh-server")
146148
forwardHandler:=&ssh.ForwardedTCPHandler{}
147149
a.sshServer=&ssh.Server{
148-
ChannelHandlers:ssh.DefaultChannelHandlers,
150+
ChannelHandlers:map[string]ssh.ChannelHandler{
151+
"direct-tcpip":ssh.DirectTCPIPHandler,
152+
"session":ssh.DefaultSessionHandler,
153+
},
149154
ConnectionFailedCallback:func(conn net.Conn,errerror) {
150155
sshLogger.Info(ctx,"ssh connection ended",slog.Error(err))
151156
},
@@ -184,61 +189,54 @@ func (a *agent) init(ctx context.Context) {
184189
NoClientAuth:true,
185190
}
186191
},
192+
SubsystemHandlers:map[string]ssh.SubsystemHandler{
193+
"sftp":func(session ssh.Session) {
194+
server,err:=sftp.NewServer(session)
195+
iferr!=nil {
196+
a.options.Logger.Debug(session.Context(),"initialize sftp server",slog.Error(err))
197+
return
198+
}
199+
deferserver.Close()
200+
err=server.Serve()
201+
iferrors.Is(err,io.EOF) {
202+
return
203+
}
204+
a.options.Logger.Debug(session.Context(),"sftp server exited with error",slog.Error(err))
205+
},
206+
},
187207
}
188208

189209
goa.run(ctx)
190210
}
191211

192212
func (a*agent)handleSSHSession(session ssh.Session)error {
193-
var (
194-
commandstring
195-
args= []string{}
196-
errerror
197-
)
198-
199213
currentUser,err:=user.Current()
200214
iferr!=nil {
201215
returnxerrors.Errorf("get current user: %w",err)
202216
}
203217
username:=currentUser.Username
204218

219+
shell,err:=usershell.Get(username)
220+
iferr!=nil {
221+
returnxerrors.Errorf("get user shell: %w",err)
222+
}
223+
205224
// gliderlabs/ssh returns a command slice of zero
206225
// when a shell is requested.
226+
command:=session.RawCommand()
207227
iflen(session.Command())==0 {
208-
command,err=usershell.Get(username)
209-
iferr!=nil {
210-
returnxerrors.Errorf("get user shell: %w",err)
211-
}
212-
}else {
213-
command=session.Command()[0]
214-
iflen(session.Command())>1 {
215-
args=session.Command()[1:]
216-
}
228+
command=shell
217229
}
218230

219-
signals:=make(chan ssh.Signal)
220-
breaks:=make(chanbool)
221-
deferclose(signals)
222-
deferclose(breaks)
223-
gofunc() {
224-
for {
225-
select {
226-
case<-session.Context().Done():
227-
return
228-
// Ignore signals and breaks for now!
229-
case<-signals:
230-
case<-breaks:
231-
}
232-
}
233-
}()
234-
235-
cmd:=exec.CommandContext(session.Context(),command,args...)
231+
// OpenSSH executes all commands with the users current shell.
232+
// We replicate that behavior for IDE support.
233+
cmd:=exec.CommandContext(session.Context(),shell,"-c",command)
236234
cmd.Env=append(os.Environ(),session.Environ()...)
237235
executablePath,err:=os.Executable()
238236
iferr!=nil {
239237
returnxerrors.Errorf("getting os executable: %w",err)
240238
}
241-
cmd.Env=append(session.Environ(),fmt.Sprintf(`GIT_SSH_COMMAND="%s gitssh --"`,executablePath))
239+
cmd.Env=append(cmd.Env,fmt.Sprintf(`GIT_SSH_COMMAND="%s gitssh --"`,executablePath))
242240

243241
sshPty,windowSize,isPty:=session.Pty()
244242
ifisPty {
@@ -267,7 +265,7 @@ func (a *agent) handleSSHSession(session ssh.Session) error {
267265
}
268266

269267
cmd.Stdout=session
270-
cmd.Stderr=session
268+
cmd.Stderr=session.Stderr()
271269
// This blocks forever until stdin is received if we don't
272270
// use StdinPipe. It's unknown what causes this.
273271
stdinPipe,err:=cmd.StdinPipe()
@@ -281,8 +279,7 @@ func (a *agent) handleSSHSession(session ssh.Session) error {
281279
iferr!=nil {
282280
returnxerrors.Errorf("start: %w",err)
283281
}
284-
_=cmd.Wait()
285-
returnnil
282+
returncmd.Wait()
286283
}
287284

288285
// isClosed returns whether the API is closed or not.

‎agent/agent_test.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"net"
8+
"os"
89
"os/exec"
910
"path/filepath"
1011
"runtime"
@@ -13,6 +14,7 @@ import (
1314
"testing"
1415

1516
"github.com/pion/webrtc/v3"
17+
"github.com/pkg/sftp"
1618
"github.com/stretchr/testify/require"
1719
"go.uber.org/goleak"
1820
"golang.org/x/crypto/ssh"
@@ -114,13 +116,36 @@ func TestAgent(t *testing.T) {
114116
conn.Close()
115117
<-done
116118
})
119+
120+
t.Run("SFTP",func(t*testing.T) {
121+
t.Parallel()
122+
sshClient,err:=setupAgent(t).SSHClient()
123+
require.NoError(t,err)
124+
client,err:=sftp.NewClient(sshClient)
125+
require.NoError(t,err)
126+
tempFile:=filepath.Join(t.TempDir(),"sftp")
127+
file,err:=client.Create(tempFile)
128+
require.NoError(t,err)
129+
err=file.Close()
130+
require.NoError(t,err)
131+
_,err=os.Stat(tempFile)
132+
require.NoError(t,err)
133+
})
117134
}
118135

119136
funcsetupSSHCommand(t*testing.T,beforeArgs []string,afterArgs []string)*exec.Cmd {
137+
_,err:=exec.LookPath("socat")
138+
iferr!=nil {
139+
t.Skip("You must have socat installed to run this test!")
140+
}
141+
120142
agentConn:=setupAgent(t)
121-
socket:=filepath.Join(t.TempDir(),"ssh")
122-
listener,err:=net.Listen("unix",socket)
143+
144+
listener,err:=net.Listen("tcp","127.0.0.1:0")
123145
require.NoError(t,err)
146+
t.Cleanup(func() {
147+
_=listener.Close()
148+
})
124149
gofunc() {
125150
for {
126151
conn,err:=listener.Accept()
@@ -136,7 +161,7 @@ func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exe
136161
t.Cleanup(func() {
137162
_=listener.Close()
138163
})
139-
args:=append(beforeArgs,"-o","ProxyCommand socat -UNIX-CLIENT:"+socket,"-o","StrictHostKeyChecking=no","host")
164+
args:=append(beforeArgs,"-o","ProxyCommand socat -TCP4:"+listener.Addr().String(),"-o","StrictHostKeyChecking=no","host")
140165
args=append(args,afterArgs...)
141166
returnexec.Command("ssh",args...)
142167
}

‎cli/configssh_test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"net"
77
"os"
88
"os/exec"
9-
"path/filepath"
109
"strings"
1110
"testing"
1211

@@ -27,6 +26,11 @@ import (
2726

2827
funcTestConfigSSH(t*testing.T) {
2928
t.Parallel()
29+
_,err:=exec.LookPath("socat")
30+
iferr!=nil {
31+
t.Skip("You must have socat installed to run this test!")
32+
}
33+
3034
client:=coderdtest.New(t,nil)
3135
user:=coderdtest.CreateFirstUser(t,client)
3236
coderdtest.NewProvisionerDaemon(t,client)
@@ -85,13 +89,15 @@ func TestConfigSSH(t *testing.T) {
8589
require.NoError(t,err)
8690
deferagentConn.Close()
8791

88-
// Using socat we can force SSH to use aUNIX socket
92+
// Using socat we can force SSH to use aTCP port
8993
// created in this test. That way we still validate
9094
// our configuration, but use the native SSH command
9195
// line to interface.
92-
socket:=filepath.Join(t.TempDir(),"ssh")
93-
listener,err:=net.Listen("unix",socket)
96+
listener,err:=net.Listen("tcp","127.0.0.1:0")
9497
require.NoError(t,err)
98+
t.Cleanup(func() {
99+
_=listener.Close()
100+
})
95101
gofunc() {
96102
for {
97103
conn,err:=listener.Accept()
@@ -108,7 +114,7 @@ func TestConfigSSH(t *testing.T) {
108114
_=listener.Close()
109115
})
110116

111-
cmd,root:=clitest.New(t,"config-ssh","--ssh-option","ProxyCommand socat -UNIX-CLIENT:"+socket,"--ssh-config-file",tempFile.Name())
117+
cmd,root:=clitest.New(t,"config-ssh","--ssh-option","ProxyCommand socat -TCP4:"+listener.Addr().String(),"--ssh-config-file",tempFile.Name())
112118
clitest.SetupConfig(t,client,root)
113119
doneChan:=make(chanstruct{})
114120
pty:=ptytest.New(t)

‎go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,12 @@ require (
9595

9696
requiregithub.com/go-chi/httpratev0.5.3
9797

98-
requiregithub.com/jedib0t/go-pretty/v6v6.3.0
98+
require (
99+
github.com/jedib0t/go-pretty/v6v6.3.0
100+
github.com/pkg/sftpv1.13.4
101+
)
102+
103+
requiregithub.com/kr/fsv0.1.0// indirect
99104

100105
require (
101106
github.com/Azure/go-ansitermv0.0.0-20210617225240-d185dfc1b5a1// indirect

‎go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,7 @@ github.com/klauspost/crc32 v1.2.0/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3H
11031103
github.com/konsorten/go-windows-terminal-sequencesv1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
11041104
github.com/konsorten/go-windows-terminal-sequencesv1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
11051105
github.com/konsorten/go-windows-terminal-sequencesv1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
1106+
github.com/kr/fsv0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
11061107
github.com/kr/fsv0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
11071108
github.com/kr/logfmtv0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
11081109
github.com/kr/prettyv0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -1438,6 +1439,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
14381439
github.com/pkg/profilev1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
14391440
github.com/pkg/profilev1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
14401441
github.com/pkg/sftpv1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
1442+
github.com/pkg/sftpv1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
1443+
github.com/pkg/sftpv1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
14411444
github.com/pmezard/go-difflibv0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14421445
github.com/pmezard/go-difflibv1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14431446
github.com/pmezard/go-difflibv1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

‎peer/channel.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const (
2121
// For some reason messages larger just don't work...
2222
// This shouldn't be a huge deal for real-world usage.
2323
// See: https://github.com/pion/datachannel/issues/59
24-
maxMessageLength=32*1024//32 KB
24+
maxMessageLength=64*1024//64 KB
2525
)
2626

2727
// newChannel creates a new channel and initializes it.
@@ -145,7 +145,9 @@ func (c *Channel) init() {
145145
ifc.opts.Unordered {
146146
c.reader=c.rwc
147147
}else {
148-
c.reader=bufio.NewReader(c.rwc)
148+
// This must be the max message length otherwise a short
149+
// buffer error can occur.
150+
c.reader=bufio.NewReaderSize(c.rwc,maxMessageLength)
149151
}
150152
close(c.opened)
151153
})

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp