Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 09da385

Browse files
authored
fix: Terminal emulation used by SSH sessions (#3473)
Fixes #3371
1 parent b4c29f3 commit 09da385

File tree

15 files changed

+387
-82
lines changed

15 files changed

+387
-82
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"gonet",
3232
"gossh",
3333
"gsyslog",
34+
"GTTY",
3435
"hashicorp",
3536
"hclsyntax",
3637
"httpapi",
@@ -67,6 +68,7 @@
6768
"ntqry",
6869
"OIDC",
6970
"oneof",
71+
"opty",
7072
"paralleltest",
7173
"parameterscopeid",
7274
"pqtype",
@@ -76,6 +78,7 @@
7678
"provisionerd",
7779
"provisionersdk",
7880
"ptty",
81+
"ptys",
7982
"ptytest",
8083
"reconfig",
8184
"retrier",
@@ -87,6 +90,7 @@
8790
"sourcemapped",
8891
"Srcs",
8992
"stretchr",
93+
"STTY",
9094
"stuntest",
9195
"tailbroker",
9296
"tailcfg",
@@ -105,6 +109,7 @@
105109
"tfjson",
106110
"tfplan",
107111
"tfstate",
112+
"tios",
108113
"tparallel",
109114
"trimprefix",
110115
"tsdial",

agent/agent.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ func (a *agent) runStartupScript(ctx context.Context, script string) error {
374374
return nil
375375
}
376376

377-
writer, err := os.OpenFile(filepath.Join(os.TempDir(), "coder-startup-script.log"), os.O_CREATE|os.O_RDWR, 0600)
377+
writer, err := os.OpenFile(filepath.Join(os.TempDir(), "coder-startup-script.log"), os.O_CREATE|os.O_RDWR, 0o600)
378378
if err != nil {
379379
return xerrors.Errorf("open startup script log file: %w", err)
380380
}
@@ -537,6 +537,8 @@ func (a *agent) init(ctx context.Context) {
537537
},
538538
SubsystemHandlers: map[string]ssh.SubsystemHandler{
539539
"sftp": func(session ssh.Session) {
540+
session.DisablePTYEmulation()
541+
540542
server, err := sftp.NewServer(session)
541543
if err != nil {
542544
a.logger.Debug(session.Context(), "initialize sftp server", slog.Error(err))
@@ -661,7 +663,8 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
661663
}
662664

663665
func (a *agent) handleSSHSession(session ssh.Session) (retErr error) {
664-
cmd, err := a.createCommand(session.Context(), session.RawCommand(), session.Environ())
666+
ctx := session.Context()
667+
cmd, err := a.createCommand(ctx, session.RawCommand(), session.Environ())
665668
if err != nil {
666669
return err
667670
}
@@ -678,32 +681,34 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) {
678681

679682
sshPty, windowSize, isPty := session.Pty()
680683
if isPty {
684+
// Disable minimal PTY emulation set by gliderlabs/ssh (NL-to-CRNL).
685+
// See https://github.com/coder/coder/issues/3371.
686+
session.DisablePTYEmulation()
687+
681688
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", sshPty.Term))
682689

683690
// The pty package sets `SSH_TTY` on supported platforms.
684-
ptty, process, err := pty.Start(cmd)
691+
ptty, process, err := pty.Start(cmd, pty.WithPTYOption(
692+
pty.WithSSHRequest(sshPty),
693+
pty.WithLogger(slog.Stdlib(ctx, a.logger, slog.LevelInfo)),
694+
))
685695
if err != nil {
686696
return xerrors.Errorf("start command: %w", err)
687697
}
688698
defer func() {
689699
closeErr := ptty.Close()
690700
if closeErr != nil {
691-
a.logger.Warn(context.Background(), "failed to close tty",
692-
slog.Error(closeErr))
701+
a.logger.Warn(ctx, "failed to close tty", slog.Error(closeErr))
693702
if retErr == nil {
694703
retErr = closeErr
695704
}
696705
}
697706
}()
698-
err = ptty.Resize(uint16(sshPty.Window.Height), uint16(sshPty.Window.Width))
699-
if err != nil {
700-
return xerrors.Errorf("resize ptty: %w", err)
701-
}
702707
go func() {
703708
for win := range windowSize {
704709
resizeErr := ptty.Resize(uint16(win.Height), uint16(win.Width))
705710
if resizeErr != nil {
706-
a.logger.Warn(context.Background(), "failed to resize tty", slog.Error(resizeErr))
711+
a.logger.Warn(ctx, "failed to resize tty", slog.Error(resizeErr))
707712
}
708713
}
709714
}()
@@ -718,8 +723,7 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) {
718723
// ExitErrors just mean the command we run returned a non-zero exit code, which is normal
719724
// and not something to be concerned about. But, if it's something else, we should log it.
720725
if err != nil && !xerrors.As(err, &exitErr) {
721-
a.logger.Warn(context.Background(), "wait error",
722-
slog.Error(err))
726+
a.logger.Warn(ctx, "wait error", slog.Error(err))
723727
}
724728
return err
725729
}

cli/ssh.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ func ssh() *cobra.Command {
195195
// shutdown of services.
196196
defer cancel()
197197

198+
if validOut {
199+
// Set initial window size.
200+
width, height, err := term.GetSize(int(stdoutFile.Fd()))
201+
if err == nil {
202+
_ = sshSession.WindowChange(height, width)
203+
}
204+
}
198205
err = sshSession.Wait()
199206
if err != nil {
200207
// If the connection drops unexpectedly, we get an ExitMissingError but no other

go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ replace github.com/tcnksm/go-httpstat => github.com/kylecarbs/go-httpstat v0.0.0
5151
// https://github.com/tailscale/tailscale/compare/main...coder:tailscale:main
5252
replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20220907193453-fb5ba5ab658d
5353

54+
// Switch to our fork that imports fixes from http://github.com/tailscale/ssh.
55+
// See: https://github.com/coder/coder/issues/3371
56+
//
57+
// Note that http://github.com/tailscale/ssh has been merged into the Tailscale
58+
// repo as tailscale.com/tempfork/gliderlabs/ssh, however, we can't replace the
59+
// subpath and it includes changes to golang.org/x/crypto/ssh as well which
60+
// makes importing it directly a bit messy.
61+
replace github.com/gliderlabs/ssh => github.com/coder/ssh v0.0.0-20220811105153-fcea99919338
62+
5463
require (
5564
cdr.dev/slog v1.4.2-0.20220525200111-18dce5c2cd5f
5665
cloud.google.com/go/compute v1.7.0
@@ -126,6 +135,7 @@ require (
126135
github.com/spf13/pflag v1.0.5
127136
github.com/stretchr/testify v1.8.0
128137
github.com/tabbed/pqtype v0.1.1
138+
github.com/u-root/u-root v0.9.0
129139
github.com/unrolled/secure v1.12.0
130140
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1
131141
go.opentelemetry.io/otel v1.8.0

go.sum

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ github.com/coder/glog v1.0.1-0.20220322161911-7365fe7f2cd1 h1:UqBrPWSYvRI2s5RtOu
352352
github.com/coder/glog v1.0.1-0.20220322161911-7365fe7f2cd1/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
353353
github.com/coder/retry v1.3.0 h1:5lAAwt/2Cm6lVmnfBY7sOMXcBOwcwJhmV5QGSELIVWY=
354354
github.com/coder/retry v1.3.0/go.mod h1:tXuRgZgWjUnU5LZPT4lJh4ew2elUhexhlnXzrJWdyFY=
355+
github.com/coder/ssh v0.0.0-20220811105153-fcea99919338 h1:tN5GKFT68YLVzJoA8AHuiMNJ0qlhoD3pGN3JY9gxSko=
356+
github.com/coder/ssh v0.0.0-20220811105153-fcea99919338/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
355357
github.com/coder/tailscale v1.1.1-0.20220907193453-fb5ba5ab658d h1:IQ8wJn8MfDS+sesYPpn3EDAyvoGMxFvyyE9uWtcfU6w=
356358
github.com/coder/tailscale v1.1.1-0.20220907193453-fb5ba5ab658d/go.mod h1:MO+tWkQp2YIF3KBnnej/mQvgYccRS5Xk/IrEpZ4Z3BU=
357359
github.com/coder/wireguard-go/tun/netstack v0.0.0-20220823170024-a78136eb0cab h1:9yEvRWXXfyKzXu8AqywCi+tFZAoqCy4wVcsXwuvZNMc=
@@ -638,9 +640,6 @@ github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU=
638640
github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
639641
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
640642
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
641-
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
642-
github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw=
643-
github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
644643
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
645644
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
646645
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
@@ -1810,6 +1809,8 @@ github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2Ogf
18101809
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
18111810
github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
18121811
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
1812+
github.com/u-root/u-root v0.9.0 h1:1dpUzrE0FyKrNEjxpKFOkyveuV1f3T0Ko5CQg4gTkCg=
1813+
github.com/u-root/u-root v0.9.0/go.mod h1:ewc9w6JF1ayZCVC9Y5wsrUiCBw3nMmPC3QItvrEwmew=
18131814
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
18141815
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
18151816
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=

pty/pty.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ package pty
22

33
import (
44
"io"
5+
"log"
56
"os"
7+
8+
"github.com/gliderlabs/ssh"
69
)
710

811
// PTY is a minimal interface for interacting with a TTY.
912
type PTY interface {
1013
io.Closer
1114

15+
// Name of the TTY. Example on Linux would be "/dev/pts/1".
16+
Name() string
17+
1218
// Output handles TTY output.
1319
//
1420
// cmd.SetOutput(pty.Output()) would be used to specify a command
@@ -35,7 +41,6 @@ type PTY interface {
3541
// to Wait() on a process, this abstraction provides a goroutine-safe interface for interacting with
3642
// the process.
3743
type Process interface {
38-
3944
// Wait for the command to complete. Returned error is as for exec.Cmd.Wait()
4045
Wait() error
4146

@@ -52,9 +57,33 @@ type WithFlags interface {
5257
EchoEnabled() (bool, error)
5358
}
5459

60+
// Options represents a an option for a PTY.
61+
type Option func(*ptyOptions)
62+
63+
type ptyOptions struct {
64+
logger *log.Logger
65+
sshReq *ssh.Pty
66+
}
67+
68+
// WithSSHRequest applies the ssh.Pty request to the PTY.
69+
//
70+
// Only partially supported on Windows (e.g. window size).
71+
func WithSSHRequest(req ssh.Pty) Option {
72+
return func(opts *ptyOptions) {
73+
opts.sshReq = &req
74+
}
75+
}
76+
77+
// WithLogger sets a logger for logging errors.
78+
func WithLogger(logger *log.Logger) Option {
79+
return func(opts *ptyOptions) {
80+
opts.logger = logger
81+
}
82+
}
83+
5584
// New constructs a new Pty.
56-
func New() (PTY, error) {
57-
return newPty()
85+
func New(opts ...Option) (PTY, error) {
86+
return newPty(opts...)
5887
}
5988

6089
// ReadWriter is an implementation of io.ReadWriter that wraps two separate

pty/pty_linux.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,23 @@
22

33
package pty
44

5-
import "golang.org/x/sys/unix"
5+
import (
6+
"github.com/u-root/u-root/pkg/termios"
7+
"golang.org/x/sys/unix"
8+
)
69

7-
func (p *otherPty) EchoEnabled() (bool, error) {
8-
termios, err := unix.IoctlGetTermios(int(p.pty.Fd()), unix.TCGETS)
10+
func (p *otherPty) EchoEnabled() (echo bool, err error) {
11+
err = p.control(p.pty, func(fd uintptr) error {
12+
t, err := termios.GetTermios(fd)
13+
if err != nil {
14+
return err
15+
}
16+
17+
echo = (t.Lflag & unix.ECHO) != 0
18+
return nil
19+
})
920
if err != nil {
1021
return false, err
1122
}
12-
return (termios.Lflag & unix.ECHO) != 0, nil
23+
return echo, nil
1324
}

0 commit comments

Comments
 (0)