From fa7489af07fcb4d63159a33324b02f1c67595819 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 00:46:55 +0000 Subject: [PATCH 01/13] feat: Add workspace agent for SSH This adds the initial agent that supports TTY and execution over SSH. It functions across MacOS, Windows, and Linux. This does not handle the coderd interaction yet, but does setup a simple path forward. --- agent/agent.go | 294 ++++++++++++++++++++++++ agent/agent_test.go | 105 +++++++++ agent/usershell/usershell_other.go | 31 +++ agent/usershell/usershell_other_test.go | 27 +++ agent/usershell/usershell_windows.go | 6 + pty/pty_other.go | 7 + pty/pty_windows.go | 9 +- pty/ptytest/ptytest.go | 15 +- pty/start.go | 7 +- pty/start_other.go | 16 +- pty/start_other_test.go | 5 +- pty/start_windows.go | 31 ++- pty/start_windows_test.go | 4 +- templates/null/main.tf | 5 - 14 files changed, 528 insertions(+), 34 deletions(-) create mode 100644 agent/agent.go create mode 100644 agent/agent_test.go create mode 100644 agent/usershell/usershell_other.go create mode 100644 agent/usershell/usershell_other_test.go create mode 100644 agent/usershell/usershell_windows.go delete mode 100644 templates/null/main.tf diff --git a/agent/agent.go b/agent/agent.go new file mode 100644 index 0000000000000..c1f2d70d68512 --- /dev/null +++ b/agent/agent.go @@ -0,0 +1,294 @@ +package agent + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "errors" + "fmt" + "io" + "net" + "os/exec" + "time" + + "cdr.dev/slog" + "github.com/coder/coder/agent/usershell" + "github.com/coder/coder/peer" + "github.com/coder/coder/peerbroker" + "github.com/coder/coder/pty" + "github.com/coder/retry" + + "github.com/gliderlabs/ssh" + gossh "golang.org/x/crypto/ssh" + "golang.org/x/xerrors" +) + +func DialSSH(conn *peer.Conn) (net.Conn, error) { + channel, err := conn.Dial(context.Background(), "ssh", &peer.ChannelOptions{ + Protocol: "ssh", + }) + if err != nil { + return nil, err + } + return channel.NetConn(), nil +} + +func DialSSHClient(conn *peer.Conn) (*gossh.Client, error) { + netConn, err := DialSSH(conn) + if err != nil { + return nil, err + } + sshConn, channels, requests, err := gossh.NewClientConn(netConn, "localhost:22", &gossh.ClientConfig{ + User: "kyle", + Config: gossh.Config{ + Ciphers: []string{"arcfour"}, + }, + // SSH host validation isn't helpful, because obtaining a peer + // connection already signifies user-intent to dial a workspace. + // #nosec + HostKeyCallback: gossh.InsecureIgnoreHostKey(), + }) + if err != nil { + return nil, err + } + return gossh.NewClient(sshConn, channels, requests), nil +} + +type Options struct { + Logger slog.Logger +} + +type Dialer func(ctx context.Context) (*peerbroker.Listener, error) + +func New(dialer Dialer, options *Options) io.Closer { + ctx, cancelFunc := context.WithCancel(context.Background()) + server := &server{ + clientDialer: dialer, + options: options, + closeCancel: cancelFunc, + } + server.init(ctx) + return server +} + +type server struct { + clientDialer Dialer + options *Options + + closeCancel context.CancelFunc + closed chan struct{} + + sshServer *ssh.Server +} + +func (s *server) init(ctx context.Context) { + // Clients' should ignore the host key when connecting. + // The agent needs to authenticate with coderd to SSH, + // so SSH authentication doesn't improve security. + randomHostKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(err) + } + randomSigner, err := gossh.NewSignerFromKey(randomHostKey) + if err != nil { + panic(err) + } + sshLogger := s.options.Logger.Named("ssh-server") + forwardHandler := &ssh.ForwardedTCPHandler{} + s.sshServer = &ssh.Server{ + ChannelHandlers: ssh.DefaultChannelHandlers, + ConnectionFailedCallback: func(conn net.Conn, err error) { + sshLogger.Info(ctx, "ssh connection ended", slog.Error(err)) + }, + Handler: func(session ssh.Session) { + err := s.handleSSHSession(session) + if err != nil { + s.options.Logger.Debug(ctx, "ssh session failed", slog.Error(err)) + _ = session.Exit(1) + return + } + }, + HostSigners: []ssh.Signer{randomSigner}, + LocalPortForwardingCallback: func(ctx ssh.Context, destinationHost string, destinationPort uint32) bool { + // Allow local port forwarding all! + sshLogger.Debug(ctx, "local port forward", + slog.F("destination-host", destinationHost), + slog.F("destination-port", destinationPort)) + return true + }, + PtyCallback: func(ctx ssh.Context, pty ssh.Pty) bool { + return true + }, + ReversePortForwardingCallback: func(ctx ssh.Context, bindHost string, bindPort uint32) bool { + // Allow reverse port forwarding all! + sshLogger.Debug(ctx, "local port forward", + slog.F("bind-host", bindHost), + slog.F("bind-port", bindPort)) + return true + }, + RequestHandlers: map[string]ssh.RequestHandler{ + "tcpip-forward": forwardHandler.HandleSSHRequest, + "cancel-tcpip-forward": forwardHandler.HandleSSHRequest, + }, + ServerConfigCallback: func(ctx ssh.Context) *gossh.ServerConfig { + return &gossh.ServerConfig{ + Config: gossh.Config{ + // "arcfour" is the fastest SSH cipher. We prioritize throughput + // over encryption here, because the WebRTC connection is already + // encrypted. If possible, we'd disable encryption entirely here. + Ciphers: []string{"arcfour"}, + }, + NoClientAuth: true, + } + }, + } + + go s.run(ctx) +} + +func (*server) handleSSHSession(session ssh.Session) error { + var ( + command string + args = []string{} + err error + ) + + // gliderlabs/ssh returns a command slice of zero + // when a shell is requested. + if len(session.Command()) == 0 { + command, err = usershell.Get(session.User()) + if err != nil { + return xerrors.Errorf("get user shell: %w", err) + } + } else { + command = session.Command()[0] + if len(session.Command()) > 1 { + args = session.Command()[1:] + } + } + + signals := make(chan ssh.Signal) + breaks := make(chan bool) + defer close(signals) + defer close(breaks) + go func() { + for { + select { + case <-session.Context().Done(): + return + // Ignore signals and breaks for now! + case <-signals: + case <-breaks: + } + } + }() + + cmd := exec.CommandContext(session.Context(), command, args...) + cmd.Env = session.Environ() + + sshPty, windowSize, isPty := session.Pty() + if isPty { + cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", sshPty.Term)) + ptty, process, err := pty.Start(cmd) + if err != nil { + return xerrors.Errorf("start command: %w", err) + } + go func() { + for win := range windowSize { + err := ptty.Resize(uint16(win.Width), uint16(win.Height)) + if err != nil { + panic(err) + } + } + }() + go func() { + _, _ = io.Copy(ptty.Input(), session) + }() + go func() { + _, _ = io.Copy(session, ptty.Output()) + }() + _, _ = process.Wait() + return nil + } + + cmd.Stdout = session + cmd.Stderr = session + // This blocks forever until stdin is received if we don't + // use StdinPipe. It's unknown what causes this. + stdinPipe, err := cmd.StdinPipe() + if err != nil { + return xerrors.Errorf("create stdin pipe: %w", err) + } + go func() { + _, _ = io.Copy(stdinPipe, session) + }() + err = cmd.Start() + if err != nil { + return xerrors.Errorf("start: %w", err) + } + _ = cmd.Wait() + return nil +} + +func (s *server) run(ctx context.Context) { + var peerListener *peerbroker.Listener + var err error + // An exponential back-off occurs when the connection is failing to dial. + // This is to prevent server spam in case of a coderd outage. + for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(ctx); { + peerListener, err = s.clientDialer(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + if s.isClosed() { + return + } + s.options.Logger.Warn(context.Background(), "failed to dial", slog.Error(err)) + continue + } + s.options.Logger.Debug(context.Background(), "connected") + break + } + + for { + conn, err := peerListener.Accept() + if err != nil { + // This is closed! + return + } + go s.handlePeerConn(ctx, conn) + } +} + +func (s *server) handlePeerConn(ctx context.Context, conn *peer.Conn) { + for { + channel, err := conn.Accept(ctx) + if err != nil { + // TODO: Log here! + return + } + + switch channel.Protocol() { + case "ssh": + s.sshServer.HandleConn(channel.NetConn()) + case "proxy": + // Proxy the port provided. + } + } +} + +// isClosed returns whether the API is closed or not. +func (s *server) isClosed() bool { + select { + case <-s.closed: + return true + default: + return false + } +} + +func (s *server) Close() error { + s.sshServer.Close() + return nil +} diff --git a/agent/agent_test.go b/agent/agent_test.go new file mode 100644 index 0000000000000..f1d075c82426a --- /dev/null +++ b/agent/agent_test.go @@ -0,0 +1,105 @@ +package agent_test + +import ( + "context" + "runtime" + "strings" + "testing" + + "github.com/pion/webrtc/v3" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + "golang.org/x/crypto/ssh" + + "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/agent" + "github.com/coder/coder/peer" + "github.com/coder/coder/peerbroker" + "github.com/coder/coder/peerbroker/proto" + "github.com/coder/coder/provisionersdk" + "github.com/coder/coder/pty/ptytest" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + +func TestAgent(t *testing.T) { + t.Parallel() + t.Run("SessionExec", func(t *testing.T) { + t.Parallel() + api := setup(t) + stream, err := api.NegotiateConnection(context.Background()) + require.NoError(t, err) + conn, err := peerbroker.Dial(stream, []webrtc.ICEServer{}, &peer.ConnOptions{ + Logger: slogtest.Make(t, nil), + }) + require.NoError(t, err) + defer conn.Close() + sshClient, err := agent.DialSSHClient(conn) + require.NoError(t, err) + session, err := sshClient.NewSession() + require.NoError(t, err) + command := "echo test" + if runtime.GOOS == "windows" { + command = "cmd.exe /c echo test" + } + output, err := session.Output(command) + require.NoError(t, err) + require.Equal(t, "test", strings.TrimSpace(string(output))) + }) + + t.Run("SessionTTY", func(t *testing.T) { + t.Parallel() + api := setup(t) + stream, err := api.NegotiateConnection(context.Background()) + require.NoError(t, err) + conn, err := peerbroker.Dial(stream, []webrtc.ICEServer{}, &peer.ConnOptions{ + Logger: slogtest.Make(t, nil), + }) + require.NoError(t, err) + defer conn.Close() + sshClient, err := agent.DialSSHClient(conn) + require.NoError(t, err) + session, err := sshClient.NewSession() + require.NoError(t, err) + prompt := "$" + command := "bash" + if runtime.GOOS == "windows" { + command = "cmd.exe" + prompt = ">" + } + err = session.RequestPty("xterm", 128, 128, ssh.TerminalModes{}) + require.NoError(t, err) + ptty := ptytest.New(t) + require.NoError(t, err) + session.Stdout = ptty.Output() + session.Stderr = ptty.Output() + session.Stdin = ptty.Input() + err = session.Start(command) + require.NoError(t, err) + ptty.ExpectMatch(prompt) + ptty.WriteLine("echo test") + ptty.ExpectMatch("test") + ptty.WriteLine("exit") + err = session.Wait() + require.NoError(t, err) + }) +} + +func setup(t *testing.T) proto.DRPCPeerBrokerClient { + client, server := provisionersdk.TransportPipe() + closer := agent.New(func(ctx context.Context) (*peerbroker.Listener, error) { + return peerbroker.Listen(server, &peer.ConnOptions{ + Logger: slogtest.Make(t, nil), + }) + }, &agent.Options{ + Logger: slogtest.Make(t, nil), + }) + t.Cleanup(func() { + _ = client.Close() + _ = server.Close() + _ = closer.Close() + }) + return proto.NewDRPCPeerBrokerClient(provisionersdk.Conn(client)) +} diff --git a/agent/usershell/usershell_other.go b/agent/usershell/usershell_other.go new file mode 100644 index 0000000000000..49bcf2b7fa9ae --- /dev/null +++ b/agent/usershell/usershell_other.go @@ -0,0 +1,31 @@ +//go:build !windows +// +build !windows + +package usershell + +import ( + "os" + "strings" + + "golang.org/x/xerrors" +) + +// Get returns the /etc/passwd entry for the username provided. +func Get(username string) (string, error) { + contents, err := os.ReadFile("/etc/passwd") + if err != nil { + return "", xerrors.Errorf("read /etc/passwd: %w", err) + } + lines := strings.Split(string(contents), "\n") + for _, line := range lines { + if !strings.HasPrefix(line, username+":") { + continue + } + parts := strings.Split(line, ":") + if len(parts) < 7 { + return "", xerrors.Errorf("malformed user entry: %q", line) + } + return parts[6], nil + } + return "", xerrors.New("user not found in /etc/passwd and $SHELL not set") +} diff --git a/agent/usershell/usershell_other_test.go b/agent/usershell/usershell_other_test.go new file mode 100644 index 0000000000000..77b11912f3402 --- /dev/null +++ b/agent/usershell/usershell_other_test.go @@ -0,0 +1,27 @@ +//go:build !windows +// +build !windows + +package usershell_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/agent/usershell" +) + +func TestGet(t *testing.T) { + t.Parallel() + t.Run("Has", func(t *testing.T) { + t.Parallel() + shell, err := usershell.Get("root") + require.NoError(t, err) + require.NotEmpty(t, shell) + }) + t.Run("NotFound", func(t *testing.T) { + t.Parallel() + _, err := usershell.Get("notauser") + require.Error(t, err) + }) +} diff --git a/agent/usershell/usershell_windows.go b/agent/usershell/usershell_windows.go new file mode 100644 index 0000000000000..91bff1d8297cd --- /dev/null +++ b/agent/usershell/usershell_windows.go @@ -0,0 +1,6 @@ +package usershell + +// Get returns the command prompt binary name. +func Get(username string) (string, error) { + return "cmd.exe", nil +} diff --git a/pty/pty_other.go b/pty/pty_other.go index dbdda408b1365..e2520a2387116 100644 --- a/pty/pty_other.go +++ b/pty/pty_other.go @@ -6,6 +6,7 @@ package pty import ( "io" "os" + "sync" "github.com/creack/pty" ) @@ -23,6 +24,7 @@ func newPty() (PTY, error) { } type otherPty struct { + mutex sync.Mutex pty, tty *os.File } @@ -41,6 +43,8 @@ func (p *otherPty) Output() io.ReadWriter { } func (p *otherPty) Resize(cols uint16, rows uint16) error { + p.mutex.Lock() + defer p.mutex.Unlock() return pty.Setsize(p.tty, &pty.Winsize{ Rows: rows, Cols: cols, @@ -48,6 +52,9 @@ func (p *otherPty) Resize(cols uint16, rows uint16) error { } func (p *otherPty) Close() error { + p.mutex.Lock() + defer p.mutex.Unlock() + err := p.pty.Close() if err != nil { return err diff --git a/pty/pty_windows.go b/pty/pty_windows.go index b6a9f8ae2e5dd..fa6f1932a48c3 100644 --- a/pty/pty_windows.go +++ b/pty/pty_windows.go @@ -96,12 +96,15 @@ func (p *ptyWindows) Close() error { return nil } p.closed = true + _ = p.outputWrite.Close() + _ = p.outputRead.Close() + _ = p.inputWrite.Close() + _ = p.inputRead.Close() ret, _, err := procClosePseudoConsole.Call(uintptr(p.console)) - if ret != 0 { + if ret < 0 { return xerrors.Errorf("close pseudo console: %w", err) } - _ = p.outputRead.Close() - _ = p.inputWrite.Close() + return nil } diff --git a/pty/ptytest/ptytest.go b/pty/ptytest/ptytest.go index 7ea5b7a119f0d..60cd88ce606a2 100644 --- a/pty/ptytest/ptytest.go +++ b/pty/ptytest/ptytest.go @@ -5,8 +5,10 @@ import ( "bytes" "fmt" "io" + "os" "os/exec" "regexp" + "runtime" "strings" "testing" "unicode/utf8" @@ -28,10 +30,10 @@ func New(t *testing.T) *PTY { return create(t, ptty) } -func Start(t *testing.T, cmd *exec.Cmd) *PTY { - ptty, err := pty.Start(cmd) +func Start(t *testing.T, cmd *exec.Cmd) (*PTY, *os.Process) { + ptty, ps, err := pty.Start(cmd) require.NoError(t, err) - return create(t, ptty) + return create(t, ptty), ps } func create(t *testing.T, ptty pty.PTY) *PTY { @@ -86,10 +88,15 @@ func (p *PTY) ExpectMatch(str string) string { break } } + p.t.Logf("matched %q = %q", str, stripAnsi.ReplaceAllString(buffer.String(), "")) return buffer.String() } func (p *PTY) WriteLine(str string) { - _, err := fmt.Fprintf(p.PTY.Input(), "%s\n", str) + newline := "\n" + if runtime.GOOS == "windows" { + newline = "\r\n" + } + _, err := fmt.Fprintf(p.PTY.Input(), "%s%s", str, newline) require.NoError(p.t, err) } diff --git a/pty/start.go b/pty/start.go index 2b75843ee16c2..d0cbcd667d7b7 100644 --- a/pty/start.go +++ b/pty/start.go @@ -1,7 +1,10 @@ package pty -import "os/exec" +import ( + "os" + "os/exec" +) -func Start(cmd *exec.Cmd) (PTY, error) { +func Start(cmd *exec.Cmd) (PTY, *os.Process, error) { return startPty(cmd) } diff --git a/pty/start_other.go b/pty/start_other.go index 103f55202efe3..dc38809227094 100644 --- a/pty/start_other.go +++ b/pty/start_other.go @@ -4,16 +4,17 @@ package pty import ( + "os" "os/exec" "syscall" "github.com/creack/pty" ) -func startPty(cmd *exec.Cmd) (PTY, error) { +func startPty(cmd *exec.Cmd) (PTY, *os.Process, error) { ptty, tty, err := pty.Open() if err != nil { - return nil, err + return nil, nil, err } cmd.SysProcAttr = &syscall.SysProcAttr{ Setsid: true, @@ -25,10 +26,15 @@ func startPty(cmd *exec.Cmd) (PTY, error) { err = cmd.Start() if err != nil { _ = ptty.Close() - return nil, err + return nil, nil, err } - return &otherPty{ + oPty := &otherPty{ pty: ptty, tty: tty, - }, nil + } + go func() { + _ = cmd.Wait() + _ = oPty.Close() + }() + return oPty, cmd.Process, nil } diff --git a/pty/start_other_test.go b/pty/start_other_test.go index a5e7d94b36af1..30c87935bcd69 100644 --- a/pty/start_other_test.go +++ b/pty/start_other_test.go @@ -7,8 +7,9 @@ import ( "os/exec" "testing" - "github.com/coder/coder/pty/ptytest" "go.uber.org/goleak" + + "github.com/coder/coder/pty/ptytest" ) func TestMain(m *testing.M) { @@ -19,7 +20,7 @@ func TestStart(t *testing.T) { t.Parallel() t.Run("Echo", func(t *testing.T) { t.Parallel() - pty := ptytest.Start(t, exec.Command("echo", "test")) + pty, _ := ptytest.Start(t, exec.Command("echo", "test")) pty.ExpectMatch("test") }) } diff --git a/pty/start_windows.go b/pty/start_windows.go index 136ba245736ab..39476ce1ef07d 100644 --- a/pty/start_windows.go +++ b/pty/start_windows.go @@ -11,47 +11,48 @@ import ( "unsafe" "golang.org/x/sys/windows" + "golang.org/x/xerrors" ) // Allocates a PTY and starts the specified command attached to it. // See: https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-hosted-process -func startPty(cmd *exec.Cmd) (PTY, error) { +func startPty(cmd *exec.Cmd) (PTY, *os.Process, error) { fullPath, err := exec.LookPath(cmd.Path) if err != nil { - return nil, err + return nil, nil, err } pathPtr, err := windows.UTF16PtrFromString(fullPath) if err != nil { - return nil, err + return nil, nil, err } argsPtr, err := windows.UTF16PtrFromString(windows.ComposeCommandLine(cmd.Args)) if err != nil { - return nil, err + return nil, nil, err } if cmd.Dir == "" { cmd.Dir, err = os.Getwd() if err != nil { - return nil, err + return nil, nil, err } } dirPtr, err := windows.UTF16PtrFromString(cmd.Dir) if err != nil { - return nil, err + return nil, nil, err } pty, err := newPty() if err != nil { - return nil, err + return nil, nil, err } winPty := pty.(*ptyWindows) attrs, err := windows.NewProcThreadAttributeList(1) if err != nil { - return nil, err + return nil, nil, err } // Taken from: https://github.com/microsoft/hcsshim/blob/2314362e977aa03b3ed245a4beb12d00422af0e2/internal/winapi/process.go#L6 err = attrs.Update(0x20016, unsafe.Pointer(winPty.console), unsafe.Sizeof(winPty.console)) if err != nil { - return nil, err + return nil, nil, err } startupInfo := &windows.StartupInfoEx{} @@ -73,12 +74,20 @@ func startPty(cmd *exec.Cmd) (PTY, error) { &processInfo, ) if err != nil { - return nil, err + return nil, nil, err } defer windows.CloseHandle(processInfo.Thread) defer windows.CloseHandle(processInfo.Process) - return pty, nil + process, err := os.FindProcess(int(processInfo.ProcessId)) + if err != nil { + return nil, nil, xerrors.Errorf("find process %d: %w", processInfo.ProcessId, err) + } + go func() { + _, _ = process.Wait() + _ = pty.Close() + }() + return pty, process, nil } // Taken from: https://github.com/microsoft/hcsshim/blob/7fbdca16f91de8792371ba22b7305bf4ca84170a/internal/exec/exec.go#L476 diff --git a/pty/start_windows_test.go b/pty/start_windows_test.go index faee269776830..d0398d0dec019 100644 --- a/pty/start_windows_test.go +++ b/pty/start_windows_test.go @@ -20,12 +20,12 @@ func TestStart(t *testing.T) { t.Parallel() t.Run("Echo", func(t *testing.T) { t.Parallel() - pty := ptytest.Start(t, exec.Command("cmd.exe", "/c", "echo", "test")) + pty, _ := ptytest.Start(t, exec.Command("cmd.exe", "/c", "echo", "test")) pty.ExpectMatch("test") }) t.Run("Resize", func(t *testing.T) { t.Parallel() - pty := ptytest.Start(t, exec.Command("cmd.exe")) + pty, _ := ptytest.Start(t, exec.Command("cmd.exe")) err := pty.Resize(100, 50) require.NoError(t, err) }) diff --git a/templates/null/main.tf b/templates/null/main.tf deleted file mode 100644 index 9bb3f2042e2a4..0000000000000 --- a/templates/null/main.tf +++ /dev/null @@ -1,5 +0,0 @@ -variable "bananas" { - description = "hello!" -} - -resource "null_resource" "example" {} From b54e8153d2c10b8b634235cc475af1ffaf68ac33 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 17 Feb 2022 23:15:01 -0600 Subject: [PATCH 02/13] Fix pty tests on Windows --- agent/agent.go | 44 +++++++++++++++++++++---- agent/agent_test.go | 11 +++++-- agent/usershell/usershell_darwin.go | 10 ++++++ agent/usershell/usershell_other.go | 4 +-- agent/usershell/usershell_other_test.go | 4 +-- go.mod | 2 +- pty/start_other.go | 4 --- pty/start_windows.go | 4 --- 8 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 agent/usershell/usershell_darwin.go diff --git a/agent/agent.go b/agent/agent.go index c1f2d70d68512..7856ff3fc9aa7 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -9,6 +9,8 @@ import ( "io" "net" "os/exec" + "os/user" + "sync" "time" "cdr.dev/slog" @@ -39,7 +41,6 @@ func DialSSHClient(conn *peer.Conn) (*gossh.Client, error) { return nil, err } sshConn, channels, requests, err := gossh.NewClientConn(netConn, "localhost:22", &gossh.ClientConfig{ - User: "kyle", Config: gossh.Config{ Ciphers: []string{"arcfour"}, }, @@ -66,6 +67,7 @@ func New(dialer Dialer, options *Options) io.Closer { clientDialer: dialer, options: options, closeCancel: cancelFunc, + closed: make(chan struct{}), } server.init(ctx) return server @@ -76,6 +78,7 @@ type server struct { options *Options closeCancel context.CancelFunc + closeMutex sync.Mutex closed chan struct{} sshServer *ssh.Server @@ -153,10 +156,19 @@ func (*server) handleSSHSession(session ssh.Session) error { err error ) + username := session.User() + if username == "" { + currentUser, err := user.Current() + if err != nil { + return xerrors.Errorf("get current user: %w", err) + } + username = currentUser.Username + } + // gliderlabs/ssh returns a command slice of zero // when a shell is requested. if len(session.Command()) == 0 { - command, err = usershell.Get(session.User()) + command, err = usershell.Get(username) if err != nil { return xerrors.Errorf("get user shell: %w", err) } @@ -208,6 +220,7 @@ func (*server) handleSSHSession(session ssh.Session) error { _, _ = io.Copy(session, ptty.Output()) }() _, _ = process.Wait() + _ = ptty.Close() return nil } @@ -254,7 +267,11 @@ func (s *server) run(ctx context.Context) { for { conn, err := peerListener.Accept() if err != nil { - // This is closed! + if s.isClosed() { + return + } + s.options.Logger.Debug(ctx, "peer listener accept exited; restarting connection", slog.Error(err)) + s.run(ctx) return } go s.handlePeerConn(ctx, conn) @@ -265,15 +282,21 @@ func (s *server) handlePeerConn(ctx context.Context, conn *peer.Conn) { for { channel, err := conn.Accept(ctx) if err != nil { - // TODO: Log here! + if s.isClosed() { + return + } + s.options.Logger.Debug(ctx, "accept channel from peer connection", slog.Error(err)) return } switch channel.Protocol() { case "ssh": s.sshServer.HandleConn(channel.NetConn()) - case "proxy": - // Proxy the port provided. + default: + s.options.Logger.Warn(ctx, "unhandled protocol from channel", + slog.F("protocol", channel.Protocol()), + slog.F("label", channel.Label()), + ) } } } @@ -289,6 +312,13 @@ func (s *server) isClosed() bool { } func (s *server) Close() error { - s.sshServer.Close() + s.closeMutex.Lock() + defer s.closeMutex.Unlock() + if s.isClosed() { + return nil + } + close(s.closed) + s.closeCancel() + _ = s.sshServer.Close() return nil } diff --git a/agent/agent_test.go b/agent/agent_test.go index f1d075c82426a..662c054eae146 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -11,6 +11,7 @@ import ( "go.uber.org/goleak" "golang.org/x/crypto/ssh" + "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/agent" "github.com/coder/coder/peer" @@ -35,7 +36,9 @@ func TestAgent(t *testing.T) { Logger: slogtest.Make(t, nil), }) require.NoError(t, err) - defer conn.Close() + t.Cleanup(func() { + _ = conn.Close() + }) sshClient, err := agent.DialSSHClient(conn) require.NoError(t, err) session, err := sshClient.NewSession() @@ -58,7 +61,9 @@ func TestAgent(t *testing.T) { Logger: slogtest.Make(t, nil), }) require.NoError(t, err) - defer conn.Close() + t.Cleanup(func() { + _ = conn.Close() + }) sshClient, err := agent.DialSSHClient(conn) require.NoError(t, err) session, err := sshClient.NewSession() @@ -94,7 +99,7 @@ func setup(t *testing.T) proto.DRPCPeerBrokerClient { Logger: slogtest.Make(t, nil), }) }, &agent.Options{ - Logger: slogtest.Make(t, nil), + Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), }) t.Cleanup(func() { _ = client.Close() diff --git a/agent/usershell/usershell_darwin.go b/agent/usershell/usershell_darwin.go new file mode 100644 index 0000000000000..d2b9a454e0470 --- /dev/null +++ b/agent/usershell/usershell_darwin.go @@ -0,0 +1,10 @@ +package usershell + +import "os" + +// Get returns the $SHELL environment variable. +// TODO: This should use "dscl" to fetch the proper value. See: +// https://stackoverflow.com/questions/16375519/how-to-get-the-default-shell +func Get(username string) (string, error) { + return os.Getenv("SHELL"), nil +} diff --git a/agent/usershell/usershell_other.go b/agent/usershell/usershell_other.go index 49bcf2b7fa9ae..6f69a1e270ac3 100644 --- a/agent/usershell/usershell_other.go +++ b/agent/usershell/usershell_other.go @@ -1,5 +1,5 @@ -//go:build !windows -// +build !windows +//go:build !windows && !darwin +// +build !windows,!darwin package usershell diff --git a/agent/usershell/usershell_other_test.go b/agent/usershell/usershell_other_test.go index 77b11912f3402..9469f31c70e70 100644 --- a/agent/usershell/usershell_other_test.go +++ b/agent/usershell/usershell_other_test.go @@ -1,5 +1,5 @@ -//go:build !windows -// +build !windows +//go:build !windows && !darwin +// +build !windows,!darwin package usershell_test diff --git a/go.mod b/go.mod index 5c13f4423b4e4..5c1877c11d91c 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/pion/logging v0.2.2 github.com/pion/transport v0.13.0 github.com/pion/webrtc/v3 v3.1.23 + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef github.com/quasilyte/go-ruleguard/dsl v0.3.17 github.com/spf13/cobra v1.3.0 @@ -115,7 +116,6 @@ require ( github.com/pion/stun v0.3.5 // indirect github.com/pion/turn/v2 v2.0.6 // indirect github.com/pion/udp v0.1.1 // indirect - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect diff --git a/pty/start_other.go b/pty/start_other.go index dc38809227094..e15825fe731ce 100644 --- a/pty/start_other.go +++ b/pty/start_other.go @@ -32,9 +32,5 @@ func startPty(cmd *exec.Cmd) (PTY, *os.Process, error) { pty: ptty, tty: tty, } - go func() { - _ = cmd.Wait() - _ = oPty.Close() - }() return oPty, cmd.Process, nil } diff --git a/pty/start_windows.go b/pty/start_windows.go index 39476ce1ef07d..1019a969aef2c 100644 --- a/pty/start_windows.go +++ b/pty/start_windows.go @@ -83,10 +83,6 @@ func startPty(cmd *exec.Cmd) (PTY, *os.Process, error) { if err != nil { return nil, nil, xerrors.Errorf("find process %d: %w", processInfo.ProcessId, err) } - go func() { - _, _ = process.Wait() - _ = pty.Close() - }() return pty, process, nil } From c484d4704b0ace40e79a589c223461ee3bd2f3b4 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 15:01:39 +0000 Subject: [PATCH 03/13] Fix log race --- agent/agent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/agent.go b/agent/agent.go index 7856ff3fc9aa7..4e40a4a2f5adb 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -282,7 +282,7 @@ func (s *server) handlePeerConn(ctx context.Context, conn *peer.Conn) { for { channel, err := conn.Accept(ctx) if err != nil { - if s.isClosed() { + if errors.Is(err, peer.ErrClosed) || s.isClosed() { return } s.options.Logger.Debug(ctx, "accept channel from peer connection", slog.Error(err)) From cc2bcde21e02685e4e996e4bd166b581e4ddcd28 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 15:12:00 +0000 Subject: [PATCH 04/13] Lock around dial error to fix log output --- provisionerd/provisionerd.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index 4c438d7360f66..b8449a38fbb05 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -110,10 +110,13 @@ func (p *provisionerDaemon) connect(ctx context.Context) { if errors.Is(err, context.Canceled) { return } + p.closeMutex.Lock() if p.isClosed() { + p.closeMutex.Unlock() return } p.opts.Logger.Warn(context.Background(), "failed to dial", slog.Error(err)) + p.closeMutex.Unlock() continue } p.opts.Logger.Debug(context.Background(), "connected") From ae36c63fd4a2b825cd760ad3b073f2a2b8a41e27 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 15:22:45 +0000 Subject: [PATCH 05/13] Fix context return early --- agent/agent.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/agent/agent.go b/agent/agent.go index 4e40a4a2f5adb..ac1c2b6884524 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -260,9 +260,17 @@ func (s *server) run(ctx context.Context) { s.options.Logger.Warn(context.Background(), "failed to dial", slog.Error(err)) continue } + defer func() { + _ = peerListener.Close() + }() s.options.Logger.Debug(context.Background(), "connected") break } + select { + case <-ctx.Done(): + return + default: + } for { conn, err := peerListener.Accept() From 4932ba78ff15845866ce6c292180ab76279d8554 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 16:45:55 +0000 Subject: [PATCH 06/13] fix: Leaking yamux session after HTTP handler is closed Closes #317. We depended on the context canceling the yamux connection, but this isn't a sync operation. Explicitly calling close ensures the handler waits for yamux to complete before exit. --- coderd/provisionerdaemons.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index a75730dc0a876..91503cba8cec5 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -84,6 +84,9 @@ func (api *api) provisionerDaemonsServe(rw http.ResponseWriter, r *http.Request) _ = conn.Close(websocket.StatusInternalError, fmt.Sprintf("multiplex server: %s", err)) return } + defer func() { + _ = session.Close() + }() mux := drpcmux.New() err = proto.DRPCRegisterProvisionerDaemon(mux, &provisionerdServer{ ID: daemon.ID, From 5601b4d9da160928ef91f81006fd04252ea0d996 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 17:02:18 +0000 Subject: [PATCH 07/13] Lock around close return --- provisionerd/provisionerd.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index 4c438d7360f66..b8449a38fbb05 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -110,10 +110,13 @@ func (p *provisionerDaemon) connect(ctx context.Context) { if errors.Is(err, context.Canceled) { return } + p.closeMutex.Lock() if p.isClosed() { + p.closeMutex.Unlock() return } p.opts.Logger.Warn(context.Background(), "failed to dial", slog.Error(err)) + p.closeMutex.Unlock() continue } p.opts.Logger.Debug(context.Background(), "connected") From 055ce11347d5e1300cdd4d3c7202afcee89a3a09 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 19:45:04 +0000 Subject: [PATCH 08/13] Force failure with log --- .github/workflows/coder.yaml | 8 +++++--- coderd/coderdtest/coderdtest.go | 4 ++-- coderd/provisionerdaemons.go | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/coder.yaml b/.github/workflows/coder.yaml index f75fee6d7aa5f..d821943c66b63 100644 --- a/.github/workflows/coder.yaml +++ b/.github/workflows/coder.yaml @@ -150,9 +150,11 @@ jobs: terraform_wrapper: false - name: Test with Mock Database + env: + GOMAXPROCS: ${{ runner.os == 'Windows' && 1 || 2 }} run: gotestsum --junitfile="gotests.xml" --packages="./..." -- - -covermode=atomic -coverprofile="gotests.coverage" - -timeout=3m -count=5 -race -short -parallel=2 + -covermode=atomic -coverprofile="gotests.coverage" -failfast + -timeout=3m -count=5 -race -short - name: Upload DataDog Trace if: (success() || failure()) && github.actor != 'dependabot[bot]' @@ -166,7 +168,7 @@ jobs: if: runner.os == 'Linux' run: DB=true gotestsum --junitfile="gotests.xml" --packages="./..." -- -covermode=atomic -coverprofile="gotests.coverage" -timeout=3m - -count=1 -race -parallel=2 + -count=1 -race -parallel=2 -failfast - name: Upload DataDog Trace if: (success() || failure()) && github.actor != 'dependabot[bot]' diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index dc7f782c83748..2ecd367803b48 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -157,7 +157,7 @@ func AwaitProjectImportJob(t *testing.T, client *codersdk.Client, organization s provisionerJob, err = client.ProjectImportJob(context.Background(), organization, job) require.NoError(t, err) return provisionerJob.Status.Completed() - }, 3*time.Second, 25*time.Millisecond) + }, 5*time.Second, 25*time.Millisecond) return provisionerJob } @@ -169,7 +169,7 @@ func AwaitWorkspaceProvisionJob(t *testing.T, client *codersdk.Client, organizat provisionerJob, err = client.WorkspaceProvisionJob(context.Background(), organization, job) require.NoError(t, err) return provisionerJob.Status.Completed() - }, 3*time.Second, 25*time.Millisecond) + }, 5*time.Second, 25*time.Millisecond) return provisionerJob } diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 91503cba8cec5..83c22da7d6a3a 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -84,8 +84,9 @@ func (api *api) provisionerDaemonsServe(rw http.ResponseWriter, r *http.Request) _ = conn.Close(websocket.StatusInternalError, fmt.Sprintf("multiplex server: %s", err)) return } - defer func() { - _ = session.Close() + go func() { + <-r.Context().Done() + _, _ = fmt.Printf("\n\n\n\nDONE\n\n\n\n") }() mux := drpcmux.New() err = proto.DRPCRegisterProvisionerDaemon(mux, &provisionerdServer{ From 70d3723d193b072516c29ad95986cdef1f88dafc Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 21:32:26 +0000 Subject: [PATCH 09/13] Fix failed handler --- agent/agent.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index ac1c2b6884524..f1e5c70034171 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -100,9 +100,6 @@ func (s *server) init(ctx context.Context) { forwardHandler := &ssh.ForwardedTCPHandler{} s.sshServer = &ssh.Server{ ChannelHandlers: ssh.DefaultChannelHandlers, - ConnectionFailedCallback: func(conn net.Conn, err error) { - sshLogger.Info(ctx, "ssh connection ended", slog.Error(err)) - }, Handler: func(session ssh.Session) { err := s.handleSSHSession(session) if err != nil { From 223540ef0582fc01e06cf9f383bcefd6efad0084 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 21:35:26 +0000 Subject: [PATCH 10/13] Upgrade dep --- agent/agent.go | 3 +++ go.mod | 4 ++-- go.sum | 7 ++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index f1e5c70034171..ac1c2b6884524 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -100,6 +100,9 @@ func (s *server) init(ctx context.Context) { forwardHandler := &ssh.ForwardedTCPHandler{} s.sshServer = &ssh.Server{ ChannelHandlers: ssh.DefaultChannelHandlers, + ConnectionFailedCallback: func(conn net.Conn, err error) { + sshLogger.Info(ctx, "ssh connection ended", slog.Error(err)) + }, Handler: func(session ssh.Session) { err := s.handleSSHSession(session) if err != nil { diff --git a/go.mod b/go.mod index 706e29268f3c4..c018ade5929d3 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/coder/retry v1.3.0 github.com/creack/pty v1.1.17 github.com/fatih/color v1.13.0 - github.com/gliderlabs/ssh v0.2.2 + github.com/gliderlabs/ssh v0.3.3 github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/render v1.0.1 github.com/go-playground/validator/v10 v10.10.0 @@ -65,7 +65,7 @@ require ( github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/alecthomas/chroma v0.10.0 // indirect - github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect diff --git a/go.sum b/go.sum index 154472eda1660..cb9a8a7679c7a 100644 --- a/go.sum +++ b/go.sum @@ -131,8 +131,9 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= @@ -422,7 +423,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -443,8 +443,9 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA= +github.com/gliderlabs/ssh v0.3.3/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= From 1a48bea96e706eb9f08d8da4f64fdb0f50e2e83d Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 21:37:25 +0000 Subject: [PATCH 11/13] Fix defer inside loops --- agent/agent.go | 3 --- coderd/provisionerdaemons.go | 4 ---- 2 files changed, 7 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index ac1c2b6884524..285efe3dc9836 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -260,9 +260,6 @@ func (s *server) run(ctx context.Context) { s.options.Logger.Warn(context.Background(), "failed to dial", slog.Error(err)) continue } - defer func() { - _ = peerListener.Close() - }() s.options.Logger.Debug(context.Background(), "connected") break } diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 83c22da7d6a3a..a75730dc0a876 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -84,10 +84,6 @@ func (api *api) provisionerDaemonsServe(rw http.ResponseWriter, r *http.Request) _ = conn.Close(websocket.StatusInternalError, fmt.Sprintf("multiplex server: %s", err)) return } - go func() { - <-r.Context().Done() - _, _ = fmt.Printf("\n\n\n\nDONE\n\n\n\n") - }() mux := drpcmux.New() err = proto.DRPCRegisterProvisionerDaemon(mux, &provisionerdServer{ ID: daemon.ID, From ee0ee7028ffaad07848687cf761bf259b9cb57c5 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 18 Feb 2022 22:25:16 +0000 Subject: [PATCH 12/13] Fix context cancel for HTTP requests --- coderd/coderdtest/coderdtest.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 2ecd367803b48..40eba2fb53942 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "io" + "net" "net/http/httptest" "net/url" "os" @@ -59,7 +60,13 @@ func New(t *testing.T) *codersdk.Client { Database: db, Pubsub: pubsub, }) - srv := httptest.NewServer(handler) + srv := httptest.NewUnstartedServer(handler) + srv.Config.BaseContext = func(_ net.Listener) context.Context { + ctx, cancelFunc := context.WithCancel(context.Background()) + t.Cleanup(cancelFunc) + return ctx + } + srv.Start() serverURL, err := url.Parse(srv.URL) require.NoError(t, err) t.Cleanup(srv.Close) From 516b6059f02803fb0dfc2024282257490b5a48eb Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Sat, 19 Feb 2022 04:13:35 +0000 Subject: [PATCH 13/13] Fix resize --- pty/pty_other.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pty/pty_other.go b/pty/pty_other.go index e2520a2387116..c3933878456cb 100644 --- a/pty/pty_other.go +++ b/pty/pty_other.go @@ -45,7 +45,7 @@ func (p *otherPty) Output() io.ReadWriter { func (p *otherPty) Resize(cols uint16, rows uint16) error { p.mutex.Lock() defer p.mutex.Unlock() - return pty.Setsize(p.tty, &pty.Winsize{ + return pty.Setsize(p.pty, &pty.Winsize{ Rows: rows, Cols: cols, })