diff --git a/accesscontrol/accesscontrol.go b/accesscontrol/accesscontrol.go index a268577..e621d64 100644 --- a/accesscontrol/accesscontrol.go +++ b/accesscontrol/accesscontrol.go @@ -5,14 +5,13 @@ import ( "fmt" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" ) // Middleware will exit 1 connections trying to execute commands that are not allowed. // If no allowed commands are provided, no commands will be allowed. func Middleware(cmds ...string) wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { if len(s.Command()) == 0 { sh(s) return diff --git a/accesscontrol/accesscontrol_test.go b/accesscontrol/accesscontrol_test.go index 42f6f35..5f0a4b1 100644 --- a/accesscontrol/accesscontrol_test.go +++ b/accesscontrol/accesscontrol_test.go @@ -4,10 +4,10 @@ import ( "fmt" "testing" + "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/accesscontrol" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" - gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh" ) const out = "hello world" @@ -77,10 +77,10 @@ func TestMiddleware(t *testing.T) { }) } -func setup(tb testing.TB, allowedCmds ...string) *gossh.Session { +func setup(tb testing.TB, allowedCmds ...string) *ssh.Session { tb.Helper() - return testsession.New(tb, &ssh.Server{ - Handler: accesscontrol.Middleware(allowedCmds...)(func(s ssh.Session) { + return testsession.New(tb, &wish.Server{ + Handler: accesscontrol.Middleware(allowedCmds...)(func(s wish.Session) { s.Write([]byte(out)) }), }, nil) diff --git a/activeterm/activeterm.go b/activeterm/activeterm.go index 38cd2e5..8b56864 100644 --- a/activeterm/activeterm.go +++ b/activeterm/activeterm.go @@ -5,13 +5,12 @@ import ( "fmt" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" ) // Middleware will exit 1 connections trying with no active terminals. func Middleware() wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { _, _, active := s.Pty() if !active { fmt.Fprintln(s, "Requires an active PTY") diff --git a/activeterm/activeterm_test.go b/activeterm/activeterm_test.go index 2bf17fd..c3eb8b4 100644 --- a/activeterm/activeterm_test.go +++ b/activeterm/activeterm_test.go @@ -3,10 +3,10 @@ package activeterm_test import ( "testing" + "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/activeterm" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" - gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh" ) func TestMiddleware(t *testing.T) { @@ -21,10 +21,10 @@ func TestMiddleware(t *testing.T) { }) } -func setup(tb testing.TB) *gossh.Session { +func setup(tb testing.TB) *ssh.Session { tb.Helper() - return testsession.New(tb, &ssh.Server{ - Handler: activeterm.Middleware()(func(s ssh.Session) { + return testsession.New(tb, &wish.Server{ + Handler: activeterm.Middleware()(func(s wish.Session) { s.Write([]byte("hello")) }), }, nil) diff --git a/bubbletea/tea.go b/bubbletea/tea.go index 4e85782..da84a81 100644 --- a/bubbletea/tea.go +++ b/bubbletea/tea.go @@ -7,7 +7,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" "github.com/muesli/termenv" ) @@ -21,7 +20,7 @@ type BubbleTeaHandler = Handler // nolint: revive // Handler is the function Bubble Tea apps implement to hook into the // SSH Middleware. This will create a new tea.Program for every connection and // start it with the tea.ProgramOptions returned. -type Handler func(ssh.Session) (tea.Model, []tea.ProgramOption) +type Handler func(wish.Session) (tea.Model, []tea.ProgramOption) // ProgramHandler is the function Bubble Tea apps implement to hook into the SSH // Middleware. This should return a new tea.Program. This handler is different @@ -30,7 +29,7 @@ type Handler func(ssh.Session) (tea.Model, []tea.ProgramOption) // // Make sure to set the tea.WithInput and tea.WithOutput to the ssh.Session // otherwise the program will not function properly. -type ProgramHandler func(ssh.Session) *tea.Program +type ProgramHandler func(wish.Session) *tea.Program // Middleware takes a Handler and hooks the input and output for the // ssh.Session into the tea.Program. It also captures window resize events and @@ -45,7 +44,7 @@ func Middleware(bth Handler) wish.Middleware { // by an SSH client's terminal cannot be detected by the server but this will // allow for manually setting the color profile on all SSH connections. func MiddlewareWithColorProfile(bth Handler, cp termenv.Profile) wish.Middleware { - h := func(s ssh.Session) *tea.Program { + h := func(s wish.Session) *tea.Program { m, opts := bth(s) if m == nil { return nil @@ -64,9 +63,9 @@ func MiddlewareWithColorProfile(bth Handler, cp termenv.Profile) wish.Middleware // Make sure to set the tea.WithInput and tea.WithOutput to the ssh.Session // otherwise the program will not function properly. func MiddlewareWithProgramHandler(bth ProgramHandler, cp termenv.Profile) wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { + return func(sh wish.Handler) wish.Handler { lipgloss.SetColorProfile(cp) - return func(s ssh.Session) { + return func(s wish.Session) { p := bth(s) if p != nil { _, windowChanges, _ := s.Pty() diff --git a/comment/comment.go b/comment/comment.go index 3e883f4..5f772da 100644 --- a/comment/comment.go +++ b/comment/comment.go @@ -1,14 +1,11 @@ package comment -import ( - "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" -) +import "github.com/charmbracelet/wish" // Middleware prints a comment at the end of the session. func Middleware(comment string) wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { sh(s) wish.Println(s, comment) } diff --git a/comment/comment_test.go b/comment/comment_test.go index 05b46e2..5375881 100644 --- a/comment/comment_test.go +++ b/comment/comment_test.go @@ -3,9 +3,9 @@ package comment import ( "testing" + "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" - gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh" ) func TestMiddleware(t *testing.T) { @@ -18,10 +18,10 @@ func TestMiddleware(t *testing.T) { }) } -func setup(tb testing.TB) *gossh.Session { +func setup(tb testing.TB) *ssh.Session { tb.Helper() - return testsession.New(tb, &ssh.Server{ - Handler: Middleware("test")(func(s ssh.Session) {}), + return testsession.New(tb, &wish.Server{ + Handler: Middleware("test")(func(s wish.Session) {}), }, nil) } diff --git a/elapsed/elapsed.go b/elapsed/elapsed.go index 24993f3..c858bae 100644 --- a/elapsed/elapsed.go +++ b/elapsed/elapsed.go @@ -4,7 +4,6 @@ import ( "time" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" ) // MiddlewareWithFormat returns a middleware that logs the elapsed time of the @@ -12,8 +11,8 @@ import ( // // This must be called as the last middleware in the chain. func MiddlewareWithFormat(format string) wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { now := time.Now() sh(s) wish.Printf(s, format, time.Since(now)) diff --git a/elapsed/elapsed_test.go b/elapsed/elapsed_test.go index 7802fcd..405c663 100644 --- a/elapsed/elapsed_test.go +++ b/elapsed/elapsed_test.go @@ -4,14 +4,12 @@ import ( "testing" "time" + "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" - gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh" ) -var ( - waitDuration = time.Second -) +var waitDuration = time.Second func TestMiddleware(t *testing.T) { t.Run("recover session", func(t *testing.T) { @@ -25,10 +23,10 @@ func TestMiddleware(t *testing.T) { }) } -func setup(tb testing.TB) *gossh.Session { +func setup(tb testing.TB) *ssh.Session { tb.Helper() - return testsession.New(tb, &ssh.Server{ - Handler: MiddlewareWithFormat("%v")(func(s ssh.Session) { + return testsession.New(tb, &wish.Server{ + Handler: MiddlewareWithFormat("%v")(func(s wish.Session) { time.Sleep(waitDuration) }), }, nil) diff --git a/examples/bubbletea/main.go b/examples/bubbletea/main.go index e14b0a0..f2264b9 100644 --- a/examples/bubbletea/main.go +++ b/examples/bubbletea/main.go @@ -16,7 +16,6 @@ import ( "github.com/charmbracelet/wish" bm "github.com/charmbracelet/wish/bubbletea" lm "github.com/charmbracelet/wish/logging" - "github.com/gliderlabs/ssh" ) const ( @@ -59,7 +58,7 @@ func main() { // handles the incoming ssh.Session. Here we just grab the terminal info and // pass it to the new model. You can also return tea.ProgramOptions (such as // tea.WithAltScreen) on a session by session basis. -func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) { +func teaHandler(s wish.Session) (tea.Model, []tea.ProgramOption) { pty, _, active := s.Pty() if !active { wish.Fatalln(s, "no active terminal, skipping") diff --git a/examples/bubbleteaprogram/main.go b/examples/bubbleteaprogram/main.go index 9fcc372..aff94e8 100644 --- a/examples/bubbleteaprogram/main.go +++ b/examples/bubbleteaprogram/main.go @@ -16,7 +16,6 @@ import ( "github.com/charmbracelet/wish" bm "github.com/charmbracelet/wish/bubbletea" lm "github.com/charmbracelet/wish/logging" - "github.com/gliderlabs/ssh" "github.com/muesli/termenv" ) @@ -69,7 +68,7 @@ func myCustomBubbleteaMiddleware() wish.Middleware { }() return p } - teaHandler := func(s ssh.Session) *tea.Program { + teaHandler := func(s wish.Session) *tea.Program { pty, _, active := s.Pty() if !active { wish.Fatalln(s, "no active terminal, skipping") diff --git a/examples/cobra/main.go b/examples/cobra/main.go index 0f74dd1..a212264 100644 --- a/examples/cobra/main.go +++ b/examples/cobra/main.go @@ -11,7 +11,6 @@ import ( "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/logging" - "github.com/gliderlabs/ssh" "github.com/spf13/cobra" ) @@ -48,8 +47,8 @@ func main() { wish.WithAddress(fmt.Sprintf("%s:%d", host, port)), wish.WithHostKeyPath(".ssh/term_info_ed25519"), wish.WithMiddleware( - func(h ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + func(h wish.Handler) wish.Handler { + return func(s wish.Session) { rootCmd := cmd() rootCmd.SetArgs(s.Command()) rootCmd.SetIn(s) diff --git a/examples/git/main.go b/examples/git/main.go index c54dab6..9286b52 100644 --- a/examples/git/main.go +++ b/examples/git/main.go @@ -16,7 +16,6 @@ import ( "github.com/charmbracelet/wish" gm "github.com/charmbracelet/wish/git" lm "github.com/charmbracelet/wish/logging" - "github.com/gliderlabs/ssh" ) const ( @@ -29,23 +28,23 @@ type app struct { access gm.AccessLevel } -func (a app) AuthRepo(repo string, pk ssh.PublicKey) gm.AccessLevel { +func (a app) AuthRepo(repo string, pk wish.PublicKey) gm.AccessLevel { return a.access } -func (a app) Push(repo string, pk ssh.PublicKey) { +func (a app) Push(repo string, pk wish.PublicKey) { log.Printf("pushed %s", repo) } -func (a app) Fetch(repo string, pk ssh.PublicKey) { +func (a app) Fetch(repo string, pk wish.PublicKey) { log.Printf("fetch %s", repo) } -func passHandler(ctx ssh.Context, password string) bool { +func passHandler(ctx wish.Context, password string) bool { return false } -func pkHandler(ctx ssh.Context, key ssh.PublicKey) bool { +func pkHandler(ctx wish.Context, key wish.PublicKey) bool { return true } @@ -54,8 +53,8 @@ func main() { a := app{gm.ReadWriteAccess} s, err := wish.NewServer( - ssh.PublicKeyAuth(pkHandler), - ssh.PasswordAuth(passHandler), + wish.WithPublicKeyAuth(pkHandler), + wish.WithPasswordAuth(passHandler), wish.WithAddress(fmt.Sprintf("%s:%d", host, port)), wish.WithHostKeyPath(".ssh/git_server_ed25519"), wish.WithMiddleware( @@ -89,8 +88,8 @@ func main() { // Normally we would use a Bubble Tea program for the TUI but for simplicity, // we'll just write a list of the pushed repos to the terminal and exit the ssh // session. -func gitListMiddleware(h ssh.Handler) ssh.Handler { - return func(s ssh.Session) { +func gitListMiddleware(h wish.Handler) wish.Handler { + return func(s wish.Session) { // Git will have a command included so only run this if there are no // commands passed to ssh. if len(s.Command()) == 0 { diff --git a/examples/go.mod b/examples/go.mod index a1f5ebd..1055a70 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -3,11 +3,11 @@ module examples go 1.18 require ( - github.com/charmbracelet/bubbletea v0.22.1 + github.com/charmbracelet/bubbletea v0.23.1 github.com/charmbracelet/wish v0.5.0 - github.com/gliderlabs/ssh v0.3.5 - github.com/muesli/termenv v0.12.0 + github.com/muesli/termenv v0.13.0 github.com/spf13/cobra v1.5.0 + golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d ) require ( @@ -15,11 +15,13 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect github.com/acomagu/bufpipe v1.0.3 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/caarlos0/sshmarshal v0.1.0 // indirect github.com/charmbracelet/keygen v0.3.0 // indirect github.com/charmbracelet/lipgloss v0.6.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/emirpasic/gods v1.12.0 // indirect + github.com/gliderlabs/ssh v0.3.5 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-git/go-git/v5 v5.4.2 // indirect @@ -30,7 +32,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/cancelreader v0.2.2 // indirect @@ -39,7 +41,6 @@ require ( github.com/sergi/go-diff v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect - golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect diff --git a/examples/go.sum b/examples/go.sum index c770cea..9682404 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -10,10 +10,12 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= -github.com/charmbracelet/bubbletea v0.22.1 h1:z66q0LWdJNOWEH9zadiAIXp2GN1AWrwNXU8obVY9X24= -github.com/charmbracelet/bubbletea v0.22.1/go.mod h1:8/7hVvbPN6ZZPkczLiB8YpLkLJ0n7DMho5Wvfd2X1C0= +github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck= +github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y= github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= @@ -70,8 +72,9 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= @@ -82,9 +85,8 @@ github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIW github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc= -github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A= +github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -133,7 +135,6 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= diff --git a/examples/identity/main.go b/examples/identity/main.go index 13c9f17..4bbb8af 100644 --- a/examples/identity/main.go +++ b/examples/identity/main.go @@ -11,7 +11,7 @@ import ( "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/logging" - "github.com/gliderlabs/ssh" + "golang.org/x/crypto/ssh" ) const ( @@ -23,18 +23,18 @@ func main() { s, err := wish.NewServer( wish.WithAddress(fmt.Sprintf("%s:%d", host, port)), wish.WithHostKeyPath(".ssh/term_info_ed25519"), - wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool { + wish.WithPublicKeyAuth(func(ctx wish.Context, key wish.PublicKey) bool { return true }), wish.WithMiddleware( logging.Middleware(), - func(h ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + func(h wish.Handler) wish.Handler { + return func(s wish.Session) { carlos, _, _, _, _ := ssh.ParseAuthorizedKey( []byte("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILxWe2rXKoiO6W14LYPVfJKzRfJ1f3Jhzxrgjc/D4tU7"), ) switch { - case ssh.KeysEqual(s.PublicKey(), carlos): + case wish.KeysEqual(s.PublicKey(), carlos): wish.Println(s, "Hey Carlos!") default: wish.Println(s, "Hey, I don't know who you are!") diff --git a/examples/simple/main.go b/examples/simple/main.go index 2faa474..6064899 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -11,7 +11,6 @@ import ( "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/logging" - "github.com/gliderlabs/ssh" ) const ( @@ -24,8 +23,8 @@ func main() { wish.WithAddress(fmt.Sprintf("%s:%d", host, port)), wish.WithHostKeyPath(".ssh/term_info_ed25519"), wish.WithMiddleware( - func(h ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + func(h wish.Handler) wish.Handler { + return func(s wish.Session) { wish.Println(s, "Hello, world!") h(s) } diff --git a/git/git.go b/git/git.go index 60ddde2..d550e12 100644 --- a/git/git.go +++ b/git/git.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" ) @@ -54,9 +53,9 @@ type GitHooks = Hooks // nolint: revive // AuthRepo will be called with the ssh.Session public key and the repo name. // Implementers return the appropriate AccessLevel. type Hooks interface { - AuthRepo(string, ssh.PublicKey) AccessLevel - Push(string, ssh.PublicKey) - Fetch(string, ssh.PublicKey) + AuthRepo(string, wish.PublicKey) AccessLevel + Push(string, wish.PublicKey) + Fetch(string, wish.PublicKey) } // Middleware adds Git server functionality to the ssh.Server. Repos are stored @@ -65,8 +64,8 @@ type Hooks interface { // Hooks.Push and Hooks.Fetch will be called on successful completion of // their commands. func Middleware(repoDir string, gh Hooks) wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { cmd := s.Command() if len(cmd) == 2 { gc := cmd[0] @@ -117,7 +116,7 @@ func Middleware(repoDir string, gh Hooks) wish.Middleware { } } -func gitPack(s ssh.Session, gitCmd string, repoDir string, repo string) error { +func gitPack(s wish.Session, gitCmd string, repoDir string, repo string) error { cmd := strings.TrimPrefix(gitCmd, "git-") rp := filepath.Join(repoDir, repo) switch gitCmd { @@ -162,7 +161,7 @@ func fileExists(path string) (bool, error) { } // Fatal prints to the session's STDOUT as a git response and exit 1. -func Fatal(s ssh.Session, v ...interface{}) { +func Fatal(s wish.Session, v ...interface{}) { msg := fmt.Sprint(v...) // hex length includes 4 byte length prefix and ending newline pktLine := fmt.Sprintf("%04x%s\n", len(msg)+5, msg) @@ -176,7 +175,7 @@ func ensureRepo(dir string, repo string) error { return err } if !exists { - err = os.MkdirAll(dir, os.ModeDir|os.FileMode(0700)) + err = os.MkdirAll(dir, os.ModeDir|os.FileMode(0o700)) if err != nil { return err } @@ -195,7 +194,7 @@ func ensureRepo(dir string, repo string) error { return nil } -func runGit(s ssh.Session, dir string, args ...string) error { +func runGit(s wish.Session, dir string, args ...string) error { usi := exec.CommandContext(s.Context(), "git", args...) usi.Dir = dir usi.Stdout = s @@ -206,7 +205,7 @@ func runGit(s ssh.Session, dir string, args ...string) error { return nil } -func ensureDefaultBranch(s ssh.Session, repoPath string) error { +func ensureDefaultBranch(s wish.Session, repoPath string) error { r, err := git.PlainOpen(repoPath) if err != nil { return err diff --git a/git/git_test.go b/git/git_test.go index 6f44933..8c8788f 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -11,7 +11,7 @@ import ( "github.com/charmbracelet/keygen" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" + "golang.org/x/crypto/ssh" ) func TestGitMiddleware(t *testing.T) { @@ -39,7 +39,7 @@ func TestGitMiddleware(t *testing.T) { } srv, err := wish.NewServer( wish.WithMiddleware(Middleware(repoDir, hooks)), - wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool { + wish.WithPublicKeyAuth(func(ctx wish.Context, key wish.PublicKey) bool { return true }), ) @@ -176,18 +176,18 @@ func requireError(t *testing.T, err error) { } } -func requireHasAction(t *testing.T, actions []action, key ssh.PublicKey, repo string) { +func requireHasAction(t *testing.T, actions []action, key wish.PublicKey, repo string) { t.Helper() for _, action := range actions { - if repo == action.repo && ssh.KeysEqual(key, action.key) { + if repo == action.repo && wish.KeysEqual(key, action.key) { return } } t.Fatalf("expected action for %q, got none", repo) } -func createKeyPair(t *testing.T) (ssh.PublicKey, string) { +func createKeyPair(t *testing.T) (wish.PublicKey, string) { t.Helper() keyDir := t.TempDir() @@ -202,13 +202,13 @@ func createKeyPair(t *testing.T) (ssh.PublicKey, string) { } type accessDetails struct { - key ssh.PublicKey + key wish.PublicKey repo string level AccessLevel } type action struct { - key ssh.PublicKey + key wish.PublicKey repo string } @@ -219,23 +219,23 @@ type testHooks struct { access []accessDetails } -func (h *testHooks) AuthRepo(repo string, key ssh.PublicKey) AccessLevel { +func (h *testHooks) AuthRepo(repo string, key wish.PublicKey) AccessLevel { for _, dets := range h.access { - if dets.repo == repo && ssh.KeysEqual(key, dets.key) { + if dets.repo == repo && wish.KeysEqual(key, dets.key) { return dets.level } } return NoAccess } -func (h *testHooks) Push(repo string, key ssh.PublicKey) { +func (h *testHooks) Push(repo string, key wish.PublicKey) { h.Lock() defer h.Unlock() h.pushes = append(h.pushes, action{key, repo}) } -func (h *testHooks) Fetch(repo string, key ssh.PublicKey) { +func (h *testHooks) Fetch(repo string, key wish.PublicKey) { h.Lock() defer h.Unlock() diff --git a/logging/logging.go b/logging/logging.go index e083ae0..b1b0604 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -5,7 +5,6 @@ import ( "time" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" ) // Middleware provides basic connection logging. Connects are logged with the @@ -28,8 +27,8 @@ type Logger interface { // auth was public key based. Disconnect will log the remote address and // connection duration. func MiddlewareWithLogger(l Logger) wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { ct := time.Now() hpk := s.PublicKey() != nil pty, _, _ := s.Pty() diff --git a/logging/logging_test.go b/logging/logging_test.go index 9b61e60..435e837 100644 --- a/logging/logging_test.go +++ b/logging/logging_test.go @@ -3,10 +3,10 @@ package logging_test import ( "testing" + "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/logging" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" - gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh" ) func TestMiddleware(t *testing.T) { @@ -17,10 +17,10 @@ func TestMiddleware(t *testing.T) { }) } -func setup(tb testing.TB) *gossh.Session { +func setup(tb testing.TB) *ssh.Session { tb.Helper() - return testsession.New(tb, &ssh.Server{ - Handler: logging.Middleware()(func(s ssh.Session) { + return testsession.New(tb, &wish.Server{ + Handler: logging.Middleware()(func(s wish.Session) { s.Write([]byte("hello")) }), }, nil) diff --git a/options.go b/options.go index 9478e91..e000fea 100644 --- a/options.go +++ b/options.go @@ -17,16 +17,16 @@ import ( ) // WithAddress returns an ssh.Option that sets the address to listen on. -func WithAddress(addr string) ssh.Option { - return func(s *ssh.Server) error { +func WithAddress(addr string) Option { + return func(s *Server) error { s.Addr = addr return nil } } // WithVersion returns an ssh.Option that sets the server version. -func WithVersion(version string) ssh.Option { - return func(s *ssh.Server) error { +func WithVersion(version string) Option { + return func(s *Server) error { s.Version = version return nil } @@ -37,9 +37,9 @@ func WithVersion(version string) ssh.Option { // Server.Handler. // // Notice that middlewares are composed from first to last, which means the last one is executed first. -func WithMiddleware(mw ...Middleware) ssh.Option { - return func(s *ssh.Server) error { - h := func(s ssh.Session) {} +func WithMiddleware(mw ...Middleware) Option { + return func(s *Server) error { + h := func(s Session) {} for _, m := range mw { h = m(h) } @@ -49,35 +49,35 @@ func WithMiddleware(mw ...Middleware) ssh.Option { } // WithHostKeyFile returns an ssh.Option that sets the path to the private. -func WithHostKeyPath(path string) ssh.Option { +func WithHostKeyPath(path string) Option { if _, err := os.Stat(path); os.IsNotExist(err) { dir, f := filepath.Split(path) n := strings.TrimSuffix(f, "_ed25519") _, err := keygen.NewWithWrite(filepath.Join(dir, n), nil, keygen.Ed25519) if err != nil { - return func(*ssh.Server) error { + return func(*Server) error { return err } } path = filepath.Join(dir, n+"_ed25519") } - return ssh.HostKeyFile(path) + return HostKeyFile(path) } // WithHostKeyPEM returns an ssh.Option that sets the host key from a PEM block. -func WithHostKeyPEM(pem []byte) ssh.Option { - return ssh.HostKeyPEM(pem) +func WithHostKeyPEM(pem []byte) Option { + return HostKeyPEM(pem) } // WithAuthorizedKeys allows to use a SSH authorized_keys file to allowlist users. -func WithAuthorizedKeys(path string) ssh.Option { - return func(s *ssh.Server) error { +func WithAuthorizedKeys(path string) Option { + return func(s *Server) error { if _, err := os.Stat(path); err != nil { return err } - return WithPublicKeyAuth(func(_ ssh.Context, key ssh.PublicKey) bool { - return isAuthorized(path, func(k ssh.PublicKey) bool { - return ssh.KeysEqual(key, k) + return WithPublicKeyAuth(func(_ Context, key PublicKey) bool { + return isAuthorized(path, func(k PublicKey) bool { + return KeysEqual(key, k) }) })(s) } @@ -86,12 +86,12 @@ func WithAuthorizedKeys(path string) ssh.Option { // WithTrustedUserCAKeys authorize certificates that are signed with the given // Certificate Authority public key, and are valid. // Analogous to the TrustedUserCAKeys OpenSSH option. -func WithTrustedUserCAKeys(path string) ssh.Option { - return func(s *ssh.Server) error { +func WithTrustedUserCAKeys(path string) Option { + return func(s *Server) error { if _, err := os.Stat(path); err != nil { return err } - return WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool { + return WithPublicKeyAuth(func(ctx Context, key PublicKey) bool { cert, ok := key.(*gossh.Certificate) if !ok { // not a certificate... @@ -120,7 +120,7 @@ func WithTrustedUserCAKeys(path string) ssh.Option { } } -func isAuthorized(path string, checker func(k ssh.PublicKey) bool) bool { +func isAuthorized(path string, checker func(k PublicKey) bool) bool { f, err := os.Open(path) if err != nil { log.Printf("failed to parse %q: %s", path, err) @@ -157,31 +157,37 @@ func isAuthorized(path string, checker func(k ssh.PublicKey) bool) bool { } // WithPublicKeyAuth returns an ssh.Option that sets the public key auth handler. -func WithPublicKeyAuth(h ssh.PublicKeyHandler) ssh.Option { - return ssh.PublicKeyAuth(h) +func WithPublicKeyAuth(h PublicKeyHandler) Option { + return ssh.PublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool { + return h(ctx, key) + }) } // WithPasswordAuth returns an ssh.Option that sets the password auth handler. -func WithPasswordAuth(p ssh.PasswordHandler) ssh.Option { - return ssh.PasswordAuth(p) +func WithPasswordAuth(p PasswordHandler) Option { + return ssh.PasswordAuth(func(ctx ssh.Context, password string) bool { + return p(ctx, password) + }) } // WithKeyboardInteractiveAuth returns an ssh.Option that sets the keyboard interactive auth handler. -func WithKeyboardInteractiveAuth(h ssh.KeyboardInteractiveHandler) ssh.Option { - return ssh.KeyboardInteractiveAuth(h) +func WithKeyboardInteractiveAuth(h KeyboardInteractiveHandler) Option { + return ssh.KeyboardInteractiveAuth(func(ctx ssh.Context, challenger gossh.KeyboardInteractiveChallenge) bool { + return h(ctx, challenger) + }) } // WithIdleTimeout returns an ssh.Option that sets the connection's idle timeout. -func WithIdleTimeout(d time.Duration) ssh.Option { - return func(s *ssh.Server) error { +func WithIdleTimeout(d time.Duration) Option { + return func(s *Server) error { s.IdleTimeout = d return nil } } // WithMaxTimeout returns an ssh.Option that sets the connection's absolute timeout. -func WithMaxTimeout(d time.Duration) ssh.Option { - return func(s *ssh.Server) error { +func WithMaxTimeout(d time.Duration) Option { + return func(s *Server) error { s.MaxTimeout = d return nil } diff --git a/options_test.go b/options_test.go index f8845b6..88608f0 100755 --- a/options_test.go +++ b/options_test.go @@ -9,39 +9,38 @@ import ( "time" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" - gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh" ) func TestWithIdleTimeout(t *testing.T) { - s := ssh.Server{} + s := Server{} requireNoError(t, WithIdleTimeout(time.Second)(&s)) requireEqual(t, time.Second, s.IdleTimeout) } func TestWithMaxTimeout(t *testing.T) { - s := ssh.Server{} + s := Server{} requireNoError(t, WithMaxTimeout(time.Second)(&s)) requireEqual(t, time.Second, s.MaxTimeout) } func TestIsAuthorized(t *testing.T) { t.Run("valid", func(t *testing.T) { - requireEqual(t, true, isAuthorized("testdata/authorized_keys", func(k ssh.PublicKey) bool { return true })) + requireEqual(t, true, isAuthorized("testdata/authorized_keys", func(k PublicKey) bool { return true })) }) t.Run("invalid", func(t *testing.T) { - requireEqual(t, false, isAuthorized("testdata/invalid_authorized_keys", func(k ssh.PublicKey) bool { return true })) + requireEqual(t, false, isAuthorized("testdata/invalid_authorized_keys", func(k PublicKey) bool { return true })) }) t.Run("file not found", func(t *testing.T) { - requireEqual(t, false, isAuthorized("testdata/nope_authorized_keys", func(k ssh.PublicKey) bool { return true })) + requireEqual(t, false, isAuthorized("testdata/nope_authorized_keys", func(k PublicKey) bool { return true })) }) } func TestWithAuthorizedKeys(t *testing.T) { t.Run("valid", func(t *testing.T) { - s := ssh.Server{} + s := Server{} requireNoError(t, WithAuthorizedKeys("testdata/authorized_keys")(&s)) for key, authorize := range map[string]bool{ @@ -58,7 +57,7 @@ func TestWithAuthorizedKeys(t *testing.T) { }) t.Run("invalid", func(t *testing.T) { - s := ssh.Server{} + s := Server{} requireNoError( t, WithAuthorizedKeys("testdata/invalid_authorized_keys")(&s), @@ -66,7 +65,7 @@ func TestWithAuthorizedKeys(t *testing.T) { }) t.Run("file not found", func(t *testing.T) { - s := ssh.Server{} + s := Server{} if err := WithAuthorizedKeys("testdata/nope_authorized_keys")(&s); err == nil { t.Fatal("expected an error, got nil") } @@ -74,33 +73,33 @@ func TestWithAuthorizedKeys(t *testing.T) { } func TestWithTrustedUserCAKeys(t *testing.T) { - setup := func(tb testing.TB, certPath string) (*ssh.Server, *gossh.ClientConfig) { - s := &ssh.Server{ - Handler: func(s ssh.Session) { - cert, ok := s.PublicKey().(*gossh.Certificate) + setup := func(tb testing.TB, certPath string) (*Server, *ssh.ClientConfig) { + s := &Server{ + Handler: func(s Session) { + cert, ok := s.PublicKey().(*ssh.Certificate) fmt.Fprintf(s, "cert? %v - principals: %v - type: %v", ok, cert.ValidPrincipals, cert.CertType) }, } requireNoError(t, WithTrustedUserCAKeys("testdata/ca.pub")(s)) - signer, err := gossh.ParsePrivateKey(getBytes(t, "testdata/foo")) + signer, err := ssh.ParsePrivateKey(getBytes(t, "testdata/foo")) requireNoError(t, err) - cert, _, _, _, err := gossh.ParseAuthorizedKey(getBytes(t, certPath)) + cert, _, _, _, err := ssh.ParseAuthorizedKey(getBytes(t, certPath)) requireNoError(t, err) - certSigner, err := gossh.NewCertSigner(cert.(*gossh.Certificate), signer) + certSigner, err := ssh.NewCertSigner(cert.(*ssh.Certificate), signer) requireNoError(t, err) - return s, &gossh.ClientConfig{ + return s, &ssh.ClientConfig{ User: "foo", - Auth: []gossh.AuthMethod{ - gossh.PublicKeys(certSigner), + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(certSigner), }, } } t.Run("invalid ca key", func(t *testing.T) { - s := &ssh.Server{} + s := &Server{} if err := WithTrustedUserCAKeys("testdata/invalid-path")(s); err == nil { t.Fatal("expected an error, got nil") } @@ -135,20 +134,20 @@ func TestWithTrustedUserCAKeys(t *testing.T) { }) t.Run("not a cert", func(t *testing.T) { - s := &ssh.Server{ - Handler: func(s ssh.Session) { + s := &Server{ + Handler: func(s Session) { fmt.Fprintln(s, "hello") }, } requireNoError(t, WithTrustedUserCAKeys("testdata/ca.pub")(s)) - signer, err := gossh.ParsePrivateKey(getBytes(t, "testdata/foo")) + signer, err := ssh.ParsePrivateKey(getBytes(t, "testdata/foo")) requireNoError(t, err) - _, err = testsession.NewClientSession(t, testsession.Listen(t, s), &gossh.ClientConfig{ + _, err = testsession.NewClientSession(t, testsession.Listen(t, s), &ssh.ClientConfig{ User: "foo", - Auth: []gossh.AuthMethod{ - gossh.PublicKeys(signer), + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), }, }) requireAuthError(t, err) diff --git a/ratelimiter/ratelimiter.go b/ratelimiter/ratelimiter.go index ae12318..eddf1a9 100644 --- a/ratelimiter/ratelimiter.go +++ b/ratelimiter/ratelimiter.go @@ -7,7 +7,6 @@ import ( "net" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" lru "github.com/hashicorp/golang-lru/v2" "golang.org/x/time/rate" ) @@ -20,13 +19,13 @@ var ErrRateLimitExceeded = errors.New("rate limit exceeded, please try again lat // Its up to the implementation to handle what identifies an session as well // as the implementation details of these limits. type RateLimiter interface { - Allow(s ssh.Session) error + Allow(s wish.Session) error } // Middleware provides a new rate limiting Middleware. func Middleware(limiter RateLimiter) wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { if err := limiter.Allow(s); err != nil { wish.Fatal(s, err) return @@ -62,7 +61,7 @@ type limiters struct { burst int } -func (r *limiters) Allow(s ssh.Session) error { +func (r *limiters) Allow(s wish.Session) error { var key string switch addr := s.RemoteAddr().(type) { case *net.TCPAddr: diff --git a/ratelimiter/ratelimiter_test.go b/ratelimiter/ratelimiter_test.go index 5c426a1..e461d53 100644 --- a/ratelimiter/ratelimiter_test.go +++ b/ratelimiter/ratelimiter_test.go @@ -4,15 +4,15 @@ import ( "testing" "time" + "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" "golang.org/x/sync/errgroup" "golang.org/x/time/rate" ) func TestRateLimiterNoLimit(t *testing.T) { - s := &ssh.Server{ - Handler: Middleware(NewRateLimiter(rate.Limit(0), 0, 5))(func(s ssh.Session) { + s := &wish.Server{ + Handler: Middleware(NewRateLimiter(rate.Limit(0), 0, 5))(func(s wish.Session) { s.Write([]byte("hello")) }), } @@ -24,8 +24,8 @@ func TestRateLimiterNoLimit(t *testing.T) { } func TestRateLimiterZeroedMaxEntried(t *testing.T) { - s := &ssh.Server{ - Handler: Middleware(NewRateLimiter(rate.Limit(1), 1, 0))(func(s ssh.Session) { + s := &wish.Server{ + Handler: Middleware(NewRateLimiter(rate.Limit(1), 1, 0))(func(s wish.Session) { s.Write([]byte("hello")) }), } @@ -37,8 +37,8 @@ func TestRateLimiterZeroedMaxEntried(t *testing.T) { } func TestRateLimiter(t *testing.T) { - s := &ssh.Server{ - Handler: Middleware(NewRateLimiter(rate.Limit(10), 4, 1))(func(s ssh.Session) { + s := &wish.Server{ + Handler: Middleware(NewRateLimiter(rate.Limit(10), 4, 1))(func(s wish.Session) { // noop }), } diff --git a/recover/recover.go b/recover/recover.go index 6dff73d..aeaa1cc 100644 --- a/recover/recover.go +++ b/recover/recover.go @@ -5,7 +5,6 @@ import ( "runtime/debug" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" ) // Middleware is a wish middleware that recovers from panics and log to stderr. @@ -19,12 +18,12 @@ func MiddlewareWithLogger(logger *log.Logger, mw ...wish.Middleware) wish.Middle if logger == nil { logger = log.Default() } - h := func(ssh.Session) {} + h := func(wish.Session) {} for _, m := range mw { h = m(h) } - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { func() { defer func() { if r := recover(); r != nil { diff --git a/recover/recover_test.go b/recover/recover_test.go index bfd941c..753b30e 100644 --- a/recover/recover_test.go +++ b/recover/recover_test.go @@ -3,9 +3,9 @@ package recover import ( "testing" + "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" - gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh" ) func TestMiddleware(t *testing.T) { @@ -15,14 +15,14 @@ func TestMiddleware(t *testing.T) { }) } -func setup(tb testing.TB) *gossh.Session { +func setup(tb testing.TB) *ssh.Session { tb.Helper() - return testsession.New(tb, &ssh.Server{ - Handler: Middleware(func(h ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return testsession.New(tb, &wish.Server{ + Handler: Middleware(func(h wish.Handler) wish.Handler { + return func(s wish.Session) { panic("hello") } - })(func(s ssh.Session) {}), + })(func(s wish.Session) {}), }, nil) } diff --git a/scp/copy_from_client.go b/scp/copy_from_client.go index ebc8301..aa15f1e 100644 --- a/scp/copy_from_client.go +++ b/scp/copy_from_client.go @@ -10,7 +10,7 @@ import ( "regexp" "strconv" - "github.com/gliderlabs/ssh" + "github.com/charmbracelet/wish" ) var ( @@ -27,7 +27,7 @@ func (e parseError) Error() string { return fmt.Sprintf("failed to parse: %q", e.subject) } -func copyFromClient(s ssh.Session, info Info, handler CopyFromClientHandler) error { +func copyFromClient(s wish.Session, info Info, handler CopyFromClientHandler) error { // accepts the request _, _ = s.Write(NULL) diff --git a/scp/copy_to_client.go b/scp/copy_to_client.go index d6aeefb..5fb5d28 100644 --- a/scp/copy_to_client.go +++ b/scp/copy_to_client.go @@ -4,10 +4,10 @@ import ( "fmt" "io/fs" - "github.com/gliderlabs/ssh" + "github.com/charmbracelet/wish" ) -func copyToClient(s ssh.Session, info Info, handler CopyToClientHandler) error { +func copyToClient(s wish.Session, info Info, handler CopyToClientHandler) error { matches, err := handler.Glob(s, info.Path) if err != nil { return err diff --git a/scp/filesystem.go b/scp/filesystem.go index d4b1569..a0376ae 100644 --- a/scp/filesystem.go +++ b/scp/filesystem.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/gliderlabs/ssh" + "github.com/charmbracelet/wish" ) type fileSystemHandler struct{ root string } @@ -45,7 +45,7 @@ func (h *fileSystemHandler) prefixed(path string) string { return filepath.Join(h.root, path) } -func (h *fileSystemHandler) Glob(_ ssh.Session, s string) ([]string, error) { +func (h *fileSystemHandler) Glob(_ wish.Session, s string) ([]string, error) { matches, err := filepath.Glob(h.prefixed(s)) if err != nil { return []string{}, err @@ -60,11 +60,11 @@ func (h *fileSystemHandler) Glob(_ ssh.Session, s string) ([]string, error) { return matches, nil } -func (h *fileSystemHandler) WalkDir(_ ssh.Session, path string, fn fs.WalkDirFunc) error { +func (h *fileSystemHandler) WalkDir(_ wish.Session, path string, fn fs.WalkDirFunc) error { return filepath.WalkDir(h.prefixed(path), fn) } -func (h *fileSystemHandler) NewDirEntry(_ ssh.Session, name string) (*DirEntry, error) { +func (h *fileSystemHandler) NewDirEntry(_ wish.Session, name string) (*DirEntry, error) { path := h.prefixed(name) info, err := os.Stat(path) if err != nil { @@ -80,7 +80,7 @@ func (h *fileSystemHandler) NewDirEntry(_ ssh.Session, name string) (*DirEntry, }, nil } -func (h *fileSystemHandler) NewFileEntry(_ ssh.Session, name string) (*FileEntry, func() error, error) { +func (h *fileSystemHandler) NewFileEntry(_ wish.Session, name string) (*FileEntry, func() error, error) { path := h.prefixed(name) info, err := os.Stat(path) if err != nil { @@ -101,14 +101,14 @@ func (h *fileSystemHandler) NewFileEntry(_ ssh.Session, name string) (*FileEntry }, f.Close, nil } -func (h *fileSystemHandler) Mkdir(_ ssh.Session, entry *DirEntry) error { +func (h *fileSystemHandler) Mkdir(_ wish.Session, entry *DirEntry) error { if err := os.Mkdir(h.prefixed(entry.Filepath), entry.Mode); err != nil { return fmt.Errorf("failed to create dir: %q: %w", entry.Filepath, err) } return h.chtimes(entry.Filepath, entry.Mtime, entry.Atime) } -func (h *fileSystemHandler) Write(_ ssh.Session, entry *FileEntry) (int64, error) { +func (h *fileSystemHandler) Write(_ wish.Session, entry *FileEntry) (int64, error) { f, err := os.OpenFile(h.prefixed(entry.Filepath), os.O_TRUNC|os.O_RDWR|os.O_CREATE, entry.Mode) if err != nil { return 0, fmt.Errorf("failed to open file: %q: %w", entry.Filepath, err) diff --git a/scp/fs.go b/scp/fs.go index fe3aa0a..541529e 100644 --- a/scp/fs.go +++ b/scp/fs.go @@ -4,7 +4,7 @@ import ( "fmt" "io/fs" - "github.com/gliderlabs/ssh" + "github.com/charmbracelet/wish" ) type fsHandler struct{ fsys fs.FS } @@ -17,15 +17,15 @@ func NewFSReadHandler(fsys fs.FS) CopyToClientHandler { return &fsHandler{fsys: fsys} } -func (h *fsHandler) Glob(_ ssh.Session, s string) ([]string, error) { +func (h *fsHandler) Glob(_ wish.Session, s string) ([]string, error) { return fs.Glob(h.fsys, s) } -func (h *fsHandler) WalkDir(_ ssh.Session, path string, fn fs.WalkDirFunc) error { +func (h *fsHandler) WalkDir(_ wish.Session, path string, fn fs.WalkDirFunc) error { return fs.WalkDir(h.fsys, path, fn) } -func (h *fsHandler) NewDirEntry(_ ssh.Session, path string) (*DirEntry, error) { +func (h *fsHandler) NewDirEntry(_ wish.Session, path string) (*DirEntry, error) { info, err := fs.Stat(h.fsys, path) if err != nil { return nil, fmt.Errorf("failed to open dir: %q: %w", path, err) @@ -40,7 +40,7 @@ func (h *fsHandler) NewDirEntry(_ ssh.Session, path string) (*DirEntry, error) { }, nil } -func (h *fsHandler) NewFileEntry(_ ssh.Session, path string) (*FileEntry, func() error, error) { +func (h *fsHandler) NewFileEntry(_ wish.Session, path string) (*FileEntry, func() error, error) { info, err := fs.Stat(h.fsys, path) if err != nil { return nil, nil, fmt.Errorf("failed to stat %q: %w", path, err) diff --git a/scp/scp.go b/scp/scp.go index 7e30306..9fc45a7 100644 --- a/scp/scp.go +++ b/scp/scp.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/charmbracelet/wish" - "github.com/gliderlabs/ssh" ) // CopyToClientHandler is a handler that can be implemented to handle files @@ -23,17 +22,17 @@ type CopyToClientHandler interface { // // Note: if your other functions expect a relative path, make sure that // your Glob implementation returns relative paths as well. - Glob(ssh.Session, string) ([]string, error) + Glob(wish.Session, string) ([]string, error) // WalkDir must be implemented if you want to allow recursive copies. - WalkDir(ssh.Session, string, fs.WalkDirFunc) error + WalkDir(wish.Session, string, fs.WalkDirFunc) error // NewDirEntry should provide a *DirEntry for the given path. - NewDirEntry(ssh.Session, string) (*DirEntry, error) + NewDirEntry(wish.Session, string) (*DirEntry, error) // NewFileEntry should provide a *FileEntry for the given path. // Users may also provide a closing function. - NewFileEntry(ssh.Session, string) (*FileEntry, func() error, error) + NewFileEntry(wish.Session, string) (*FileEntry, func() error, error) } // CopyFromClientHandler is a handler that can be implemented to handle files @@ -41,10 +40,10 @@ type CopyToClientHandler interface { type CopyFromClientHandler interface { // Mkdir should created the given dir. // Note that this usually shouldn't use os.MkdirAll and the like. - Mkdir(ssh.Session, *DirEntry) error + Mkdir(wish.Session, *DirEntry) error // Write should write the given file. - Write(ssh.Session, *FileEntry) (int64, error) + Write(wish.Session, *FileEntry) (int64, error) } // Handler is a interface that can be implemented to handle both SCP @@ -57,8 +56,8 @@ type Handler interface { // Middleware provides a wish middleware using the given CopyToClientHandler // and CopyFromClientHandler. func Middleware(rh CopyToClientHandler, wh CopyFromClientHandler) wish.Middleware { - return func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { + return func(sh wish.Handler) wish.Handler { + return func(s wish.Session) { info := GetInfo(s.Command()) if !info.Ok { sh(s) diff --git a/scp/scp_test.go b/scp/scp_test.go index a24c1a2..618292a 100644 --- a/scp/scp_test.go +++ b/scp/scp_test.go @@ -6,10 +6,10 @@ import ( "path/filepath" "testing" + "github.com/charmbracelet/wish" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" "github.com/matryer/is" - gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh" ) func TestGetInfo(t *testing.T) { @@ -110,10 +110,10 @@ func TestInvalidOps(t *testing.T) { }) } -func setup(tb testing.TB, rh CopyToClientHandler, wh CopyFromClientHandler) *gossh.Session { +func setup(tb testing.TB, rh CopyToClientHandler, wh CopyFromClientHandler) *ssh.Session { tb.Helper() - return testsession.New(tb, &ssh.Server{ - Handler: Middleware(rh, wh)(func(s ssh.Session) { + return testsession.New(tb, &wish.Server{ + Handler: Middleware(rh, wh)(func(s wish.Session) { s.Exit(0) s.Close() }), diff --git a/types.go b/types.go new file mode 100644 index 0000000..b3cc8c8 --- /dev/null +++ b/types.go @@ -0,0 +1,73 @@ +package wish + +import ( + "context" + "sync" + + "github.com/gliderlabs/ssh" + gossh "golang.org/x/crypto/ssh" +) + +// Option is a functional option handler for Server. +type Option = ssh.Option + +// Handler is a callback for handling established SSH sessions. +type Handler = ssh.Handler + +// PublicKey is an abstraction of different types of public keys. +type PublicKey = ssh.PublicKey + +// The Permissions type holds fine-grained permissions that are specific to a +// user or a specific authentication method for a user. Permissions, except for +// "source-address", must be enforced in the server application layer, after +// successful authentication. +type Permissions = ssh.Permissions + +// A Signer can create signatures that verify against a public key. +type Signer = ssh.Signer + +// PublicKeyHandler is a callback for performing public key authentication. +type PublicKeyHandler func(ctx Context, key PublicKey) bool + +// PasswordHandler is a callback for performing password authentication. +type PasswordHandler func(ctx Context, password string) bool + +// KeyboardInteractiveHandler is a callback for performing keyboard-interactive authentication. +type KeyboardInteractiveHandler func(ctx Context, challenger gossh.KeyboardInteractiveChallenge) bool + +// Context is a package specific context interface. It exposes connection +// metadata and allows new values to be easily written to it. It's used in +// authentication handlers and callbacks, and its underlying context.Context is +// exposed on Session in the session Handler. A connection-scoped lock is also +// embedded in the context to make it easier to limit operations per-connection. +type Context interface { + context.Context + sync.Locker + ssh.Context +} + +// Server defines parameters for running an SSH server. The zero value for +// Server is a valid configuration. When both PasswordHandler and +// PublicKeyHandler are nil, no client authentication is performed. +type Server = ssh.Server + +// Session provides access to information about an SSH session and methods +// to read and write to the SSH channel with an embedded Channel interface from +// crypto/ssh. +// +// When Command() returns an empty slice, the user requested a shell. Otherwise +// the user is performing an exec with those command arguments. +type Session = ssh.Session + +var ( + // HostKeyFile returns a functional option that adds HostSigners to the server + // from a PEM file at filepath. + HostKeyFile = ssh.HostKeyFile + + // HostKeyPEM returns a functional option that adds HostSigners to the server + // from a PEM file as bytes. + HostKeyPEM = ssh.HostKeyPEM + + // KeysEqual is constant time compare of the keys to avoid timing attacks. + KeysEqual = ssh.KeysEqual +) diff --git a/wish.go b/wish.go index 51ac209..79cc760 100644 --- a/wish.go +++ b/wish.go @@ -5,19 +5,18 @@ import ( "io" "github.com/charmbracelet/keygen" - "github.com/gliderlabs/ssh" ) // Middleware is a function that takes an ssh.Handler and returns an // ssh.Handler. Implementations should call the provided handler argument. -type Middleware func(ssh.Handler) ssh.Handler +type Middleware func(Handler) Handler // NewServer is returns a default SSH server with the provided Middleware. A // new SSH key pair of type ed25519 will be created if one does not exist. By // default this server will accept all incoming connections, password and // public key. -func NewServer(ops ...ssh.Option) (*ssh.Server, error) { - s := &ssh.Server{} +func NewServer(ops ...Option) (*Server, error) { + s := &Server{} // Some sensible defaults s.Version = "OpenSSH_7.6p1" for _, op := range ops { @@ -39,7 +38,7 @@ func NewServer(ops ...ssh.Option) (*ssh.Server, error) { } // Fatal prints to the given session's STDERR and exits 1. -func Fatal(s ssh.Session, v ...interface{}) { +func Fatal(s Session, v ...interface{}) { Error(s, v...) _ = s.Exit(1) _ = s.Close() @@ -49,7 +48,7 @@ func Fatal(s ssh.Session, v ...interface{}) { // followed by an exit 1. // // Notice that this might cause formatting issues if you don't add a \r\n in the end of your string. -func Fatalf(s ssh.Session, f string, v ...interface{}) { +func Fatalf(s Session, f string, v ...interface{}) { Errorf(s, f, v...) _ = s.Exit(1) _ = s.Close() @@ -57,7 +56,7 @@ func Fatalf(s ssh.Session, f string, v ...interface{}) { // Fatalln formats according to the default format, prints to the session's // STDERR, followed by a new line and an exit 1. -func Fatalln(s ssh.Session, v ...interface{}) { +func Fatalln(s Session, v ...interface{}) { Errorln(s, v...) Errorf(s, "\r") _ = s.Exit(1) @@ -65,36 +64,36 @@ func Fatalln(s ssh.Session, v ...interface{}) { } // Error prints the given error the the session's STDERR. -func Error(s ssh.Session, v ...interface{}) { +func Error(s Session, v ...interface{}) { _, _ = fmt.Fprint(s.Stderr(), v...) } // Errorf formats according to the given format and prints to the session's STDERR. -func Errorf(s ssh.Session, f string, v ...interface{}) { +func Errorf(s Session, f string, v ...interface{}) { _, _ = fmt.Fprintf(s.Stderr(), f, v...) } // Errorf formats according to the default format and prints to the session's STDERR. -func Errorln(s ssh.Session, v ...interface{}) { +func Errorln(s Session, v ...interface{}) { _, _ = fmt.Fprintln(s.Stderr(), v...) } // Print writes to the session's STDOUT followed. -func Print(s ssh.Session, v ...interface{}) { +func Print(s Session, v ...interface{}) { _, _ = fmt.Fprint(s, v...) } // Printf formats according to the given format and writes to the session's STDOUT. -func Printf(s ssh.Session, f string, v ...interface{}) { +func Printf(s Session, f string, v ...interface{}) { _, _ = fmt.Fprintf(s, f, v...) } // Println formats according to the default format and writes to the session's STDOUT. -func Println(s ssh.Session, v ...interface{}) { +func Println(s Session, v ...interface{}) { _, _ = fmt.Fprintln(s, v...) } // WriteString writes the given string to the session's STDOUT. -func WriteString(s ssh.Session, v string) (int, error) { +func WriteString(s Session, v string) (int, error) { return io.WriteString(s, v) } diff --git a/wish_test.go b/wish_test.go index 7db9162..e5c36a7 100755 --- a/wish_test.go +++ b/wish_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/charmbracelet/wish/testsession" - "github.com/gliderlabs/ssh" ) func TestNewServer(t *testing.T) { @@ -31,8 +30,8 @@ func TestNewServerWithOptions(t *testing.T) { func TestError(t *testing.T) { eerr := errors.New("foo err") - sess := testsession.New(t, &ssh.Server{ - Handler: func(s ssh.Session) { + sess := testsession.New(t, &Server{ + Handler: func(s Session) { Error(s, eerr) }, }, nil) @@ -48,8 +47,8 @@ func TestError(t *testing.T) { func TestFatal(t *testing.T) { err := errors.New("foo err") - sess := testsession.New(t, &ssh.Server{ - Handler: func(s ssh.Session) { + sess := testsession.New(t, &Server{ + Handler: func(s Session) { Fatal(s, err) }, }, nil)