From 88db5d43db731685c53cc4fc8833a5f331234960 Mon Sep 17 00:00:00 2001 From: tarek salem Date: Sun, 12 Mar 2023 18:51:31 +0200 Subject: [PATCH 1/7] enable connection using raw private key without file path --- auth.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/auth.go b/auth.go index c1d4931..318a322 100644 --- a/auth.go +++ b/auth.go @@ -37,6 +37,17 @@ func Key(prvFile string, passphrase string) (Auth, error) { }, nil } +func RawKey(privateKey string, passphrase string) (Auth, error) { + signer, err := GetSignerForRawKey([]byte(privateKey), passphrase) + if err != nil { + return nil, err + } + + return Auth{ + ssh.PublicKeys(signer), + }, nil +} + // HasAgent checks if ssh agent exists. func HasAgent() bool { return os.Getenv("SSH_AUTH_SOCK") != "" @@ -78,3 +89,27 @@ func GetSigner(prvFile string, passphrase string) (ssh.Signer, error) { return signer, err } + +// GetSigner returns ssh signer from private key file. +func GetSignerForRawKey(privateKey []byte, passphrase string) (ssh.Signer, error) { + + var ( + err error + signer ssh.Signer + ) + + if err != nil { + + return nil, err + + } else if passphrase != "" { + + signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(passphrase)) + + } else { + + signer, err = ssh.ParsePrivateKey(privateKey) + } + + return signer, err +} From 085fabe8ada729a2ef190b896967ded87cb432ce Mon Sep 17 00:00:00 2001 From: tarek salem Date: Sun, 12 Mar 2023 19:02:18 +0200 Subject: [PATCH 2/7] pointing to the personal package --- examples/goph/main.go | 2 +- go.mod | 2 +- goph_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/goph/main.go b/examples/goph/main.go index 97778b5..fca9d85 100644 --- a/examples/goph/main.go +++ b/examples/goph/main.go @@ -14,8 +14,8 @@ import ( "strings" "time" - "github.com/melbahja/goph" "github.com/pkg/sftp" + "github.com/tareksalem/goph" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/terminal" ) diff --git a/go.mod b/go.mod index f7e98dc..66c7180 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/melbahja/goph +module github.com/tareksalem/goph go 1.13 diff --git a/goph_test.go b/goph_test.go index b99e061..adcffb0 100644 --- a/goph_test.go +++ b/goph_test.go @@ -6,7 +6,7 @@ import ( "net" "testing" - "github.com/melbahja/goph" + "github.com/tareksalem/goph" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/terminal" ) From 66fbe936bbcad1cdbde48a9957679c9ebd7b8007 Mon Sep 17 00:00:00 2001 From: tarek salem Date: Sun, 12 Mar 2023 19:54:28 +0200 Subject: [PATCH 3/7] testing implemenation --- auth.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/auth.go b/auth.go index 318a322..bd8bc98 100644 --- a/auth.go +++ b/auth.go @@ -6,6 +6,7 @@ package goph import ( "fmt" "io/ioutil" + "log" "net" "os" @@ -38,6 +39,7 @@ func Key(prvFile string, passphrase string) (Auth, error) { } func RawKey(privateKey string, passphrase string) (Auth, error) { + fmt.Printf("================got key %s", privateKey) signer, err := GetSignerForRawKey([]byte(privateKey), passphrase) if err != nil { return nil, err @@ -98,6 +100,10 @@ func GetSignerForRawKey(privateKey []byte, passphrase string) (ssh.Signer, error signer ssh.Signer ) + // fmt.Printf("================= got error %e", err) + log.Printf("======================dd %s", privateKey) + log.Fatal(err) + if err != nil { return nil, err From 70af04c1fff3d99f0dc379b6881d8e95f244079f Mon Sep 17 00:00:00 2001 From: tarek Date: Thu, 20 Apr 2023 03:32:35 +0200 Subject: [PATCH 4/7] adding executing bash scripts functionality --- auth.go | 3 --- client.go | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/auth.go b/auth.go index bd8bc98..7e356ea 100644 --- a/auth.go +++ b/auth.go @@ -39,7 +39,6 @@ func Key(prvFile string, passphrase string) (Auth, error) { } func RawKey(privateKey string, passphrase string) (Auth, error) { - fmt.Printf("================got key %s", privateKey) signer, err := GetSignerForRawKey([]byte(privateKey), passphrase) if err != nil { return nil, err @@ -100,8 +99,6 @@ func GetSignerForRawKey(privateKey []byte, passphrase string) (ssh.Signer, error signer ssh.Signer ) - // fmt.Printf("================= got error %e", err) - log.Printf("======================dd %s", privateKey) log.Fatal(err) if err != nil { diff --git a/client.go b/client.go index a1165b2..56fbcb1 100644 --- a/client.go +++ b/client.go @@ -4,6 +4,7 @@ package goph import ( + "bytes" "context" "fmt" "io" @@ -109,6 +110,27 @@ func (c Client) Run(cmd string) ([]byte, error) { return sess.CombinedOutput(cmd) } +// Run starts a new SSH session and runs the cmd, it returns CombinedOutput and err if any. +func (c Client) Script(script string) (*Cmd, error) { + + var ( + err error + sess *ssh.Session + ) + + if sess, err = c.NewSession(); err != nil { + return nil, err + } + + sess.Stdin = bytes.NewBufferString(script + "\n") + return &Cmd{ + Path: "", + Args: []string{}, + Session: sess, + Context: context.Background(), + }, nil +} + // Run starts a new SSH session with context and runs the cmd. It returns CombinedOutput and err if any. func (c Client) RunContext(ctx context.Context, name string) ([]byte, error) { cmd, err := c.CommandContext(ctx, name) From a6528614238ee21294783a95b3d83a471da2ec91 Mon Sep 17 00:00:00 2001 From: tarek Date: Thu, 20 Apr 2023 11:41:49 +0200 Subject: [PATCH 5/7] adding excute scripts --- client.go | 5 +++- cmd.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 56fbcb1..11b5586 100644 --- a/client.go +++ b/client.go @@ -122,12 +122,13 @@ func (c Client) Script(script string) (*Cmd, error) { return nil, err } - sess.Stdin = bytes.NewBufferString(script + "\n") return &Cmd{ Path: "", Args: []string{}, Session: sess, Context: context.Background(), + script: bytes.NewBufferString(script + "\n"), + _type: rawScript, }, nil } @@ -158,6 +159,8 @@ func (c Client) Command(name string, args ...string) (*Cmd, error) { Args: args, Session: sess, Context: context.Background(), + _type: cmdLine, + script: bytes.NewBufferString(name + "\n"), }, nil } diff --git a/cmd.go b/cmd.go index 3a4608f..67e1ce5 100644 --- a/cmd.go +++ b/cmd.go @@ -4,11 +4,22 @@ package goph import ( + "bytes" "context" "fmt" + "io" + "strings" + "github.com/pkg/errors" "golang.org/x/crypto/ssh" - "strings" +) + +type remoteScriptType byte + +const ( + cmdLine remoteScriptType = iota + rawScript + scriptFile ) // Cmd it's like os/exec.Cmd but for ssh session. @@ -28,6 +39,15 @@ type Cmd struct { // Context for cancellation Context context.Context + + _type remoteScriptType + + script *bytes.Buffer + + // scriptFile string + + stdout io.Writer + stderr io.Writer } // CombinedOutput runs cmd on the remote host and returns its combined stdout and stderr. @@ -52,6 +72,73 @@ func (c *Cmd) Output() ([]byte, error) { }) } +// Output runs cmd on the remote host and returns its stdout. +func (c *Cmd) RawOutput() ([]byte, error) { + if err := c.init(); err != nil { + return nil, errors.Wrap(err, "cmd init") + } + var out bytes.Buffer + c.stdout = &out + return c.runWithContext(func() ([]byte, error) { + return out.Bytes(), c.run() + }) +} + +func (c *Cmd) run() error { + if c._type == cmdLine { + return c.runCmds() + } else if c._type == rawScript { + return c.runScript() + } else if c._type == scriptFile { + return nil + // return c.runScriptFile() + } else { + return errors.New("Not supported RemoteScript type") + } +} + +func (c *Cmd) runCmd(cmd string) error { + c.Session.Stdout = c.stdout + c.Session.Stderr = c.stderr + + if err := c.Session.Run(cmd); err != nil { + return err + } + return nil +} + +func (c *Cmd) runCmds() error { + for { + statment, err := c.script.ReadString('\n') + if err == io.EOF { + break + } + if err != nil { + return err + } + + if err := c.runCmd(statment); err != nil { + return err + } + } + + return nil +} + +func (c *Cmd) runScript() error { + c.Session.Stdin = c.script + c.Session.Stdout = c.stdout + c.Session.Stderr = c.stderr + if err := c.Session.Shell(); err != nil { + return err + } + if err := c.Session.Wait(); err != nil { + return err + } + + return nil +} + // Run runs cmd on the remote host. func (c *Cmd) Run() error { if err := c.init(); err != nil { From 7ec06b5ca55a7ee164fdcc3126f1cb173ecc16d8 Mon Sep 17 00:00:00 2001 From: tarek Date: Tue, 25 Apr 2023 07:21:09 +0200 Subject: [PATCH 6/7] adding goph --- client.go | 9 +++++++++ cmd.go | 28 +++++++++++++++++++++++----- go.mod | 1 + go.sum | 4 ++++ goph_test.go | 7 ++++--- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/client.go b/client.go index 11b5586..47bbaee 100644 --- a/client.go +++ b/client.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "io" + "io/ioutil" "net" "os" "time" @@ -132,6 +133,14 @@ func (c Client) Script(script string) (*Cmd, error) { }, nil } +func (c Client) ScriptFile(filePath string) (*Cmd, error) { + file, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + return c.Script(string(file)) +} + // Run starts a new SSH session with context and runs the cmd. It returns CombinedOutput and err if any. func (c Client) RunContext(ctx context.Context, name string) ([]byte, error) { cmd, err := c.CommandContext(ctx, name) diff --git a/cmd.go b/cmd.go index 67e1ce5..2d4e9c2 100644 --- a/cmd.go +++ b/cmd.go @@ -50,6 +50,12 @@ type Cmd struct { stderr io.Writer } +func appendStringToBuffer(str string, buf bytes.Buffer) *bytes.Buffer { + parentStr := buf.String() + parentStr = fmt.Sprintf("%v \n %v", str, parentStr) + return bytes.NewBufferString(parentStr) +} + // CombinedOutput runs cmd on the remote host and returns its combined stdout and stderr. func (c *Cmd) CombinedOutput() ([]byte, error) { if err := c.init(); err != nil { @@ -73,14 +79,22 @@ func (c *Cmd) Output() ([]byte, error) { } // Output runs cmd on the remote host and returns its stdout. -func (c *Cmd) RawOutput() ([]byte, error) { +func (c *Cmd) ScriptOutput() ([]byte, error) { if err := c.init(); err != nil { return nil, errors.Wrap(err, "cmd init") } - var out bytes.Buffer - c.stdout = &out + var ( + stdout bytes.Buffer + stderr bytes.Buffer + ) + c.stdout = &stdout + c.stderr = &stderr return c.runWithContext(func() ([]byte, error) { - return out.Bytes(), c.run() + err := c.run() + if err != nil { + return stderr.Bytes(), err + } + return stdout.Bytes(), err }) } @@ -126,6 +140,8 @@ func (c *Cmd) runCmds() error { } func (c *Cmd) runScript() error { + envs := strings.Join(c.Env, "\n") + c.script = appendStringToBuffer(fmt.Sprintf("%v \n", envs), *c.script) c.Session.Stdin = c.script c.Session.Stdout = c.stdout c.Session.Stderr = c.stderr @@ -135,7 +151,6 @@ func (c *Cmd) runScript() error { if err := c.Session.Wait(); err != nil { return err } - return nil } @@ -168,6 +183,9 @@ func (c *Cmd) String() string { // Init inits and sets session env vars. func (c *Cmd) init() (err error) { + if c._type == rawScript { + return + } // Set session env vars var env []string for _, value := range c.Env { diff --git a/go.mod b/go.mod index 66c7180..035a18b 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/pkg/errors v0.9.1 github.com/pkg/sftp v1.13.5 golang.org/x/crypto v0.6.0 + golang.org/x/term v0.7.0 ) diff --git a/go.sum b/go.sum index eea2896..ee8f3df 100644 --- a/go.sum +++ b/go.sum @@ -34,10 +34,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/goph_test.go b/goph_test.go index adcffb0..1e9715a 100644 --- a/goph_test.go +++ b/goph_test.go @@ -6,9 +6,10 @@ import ( "net" "testing" - "github.com/tareksalem/goph" "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" + + "github.com/tareksalem/goph" ) var privateBytes = []byte(` @@ -183,7 +184,7 @@ func newServer(port string) { } }(requests) - term := terminal.NewTerminal(channel, "> ") + term := term.NewTerminal(channel, "> ") go func() { defer channel.Close() From 5610944b1f5bb3d841bf0a1f22e4df7569316501 Mon Sep 17 00:00:00 2001 From: tarek Date: Tue, 25 Apr 2023 07:31:57 +0200 Subject: [PATCH 7/7] fixing goph raw key parsing --- auth.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/auth.go b/auth.go index 7e356ea..318a322 100644 --- a/auth.go +++ b/auth.go @@ -6,7 +6,6 @@ package goph import ( "fmt" "io/ioutil" - "log" "net" "os" @@ -99,8 +98,6 @@ func GetSignerForRawKey(privateKey []byte, passphrase string) (ssh.Signer, error signer ssh.Signer ) - log.Fatal(err) - if err != nil { return nil, err