๐Ÿค˜ The native golang ssh client to execute your commands over ssh connection. ๐Ÿš€๐Ÿš€

Overview

Golang SSH Client.

Fast and easy golang ssh client module.

Goph is a lightweight Go SSH client focusing on simplicity!

Installation โ˜ Features โ˜ Usage โ˜ Examples โ˜ License

๐Ÿš€   Installation and Documentation

go get github.com/melbahja/goph

You can find the docs at go docs.

๐Ÿค˜   Features

  • Easy to use and simple API.
  • Supports known hosts by default.
  • Supports connections with passwords.
  • Supports connections with private keys.
  • Supports connections with protected private keys with passphrase.
  • Supports upload files from local to remote.
  • Supports download files from remote to local.
  • Supports connections with ssh agent (Unix systems only).
  • Supports adding new hosts to known_hosts file.
  • Supports file system operations like: Open, Create, Chmod...

๐Ÿ“„   Usage

Run a command via ssh:

package main

import (
	"log"
	"fmt"
	"github.com/melbahja/goph"
)

func main() {

	// Start new ssh connection with private key.
	auth, err := goph.Key("/home/mohamed/.ssh/id_rsa", "")
	if err != nil {
		log.Fatal(err)
	}

	client, err := goph.New("root", "192.1.1.3", auth)
	if err != nil {
		log.Fatal(err)
	}

	// Defer closing the network connection.
	defer client.Close()

	// Execute your command.
	out, err := client.Run("ls /tmp/")

	if err != nil {
		log.Fatal(err)
	}

	// Get your output as []byte.
	fmt.Println(string(out))
}

๐Ÿ” Start Connection With Protected Private Key:

auth, err := goph.Key("/home/mohamed/.ssh/id_rsa", "you_passphrase_here")
if err != nil {
	// handle error
}

client, err := goph.New("root", "192.1.1.3", auth)

๐Ÿ”‘ Start Connection With Password:

client, err := goph.New("root", "192.1.1.3", goph.Password("you_password_here"))

โ˜› Start Connection With SSH Agent (Unix systems only):

auth, err := goph.UseAgent()
if err != nil {
	// handle error
}

client, err := goph.New("root", "192.1.1.3", auth)

โคด๏ธ Upload Local File to Remote:

err := client.Upload("/path/to/local/file", "/path/to/remote/file")

โคต๏ธ Download Remote File to Local:

err := client.Download("/path/to/remote/file", "/path/to/local/file")

โ˜› Execute Bash Commands:

out, err := client.Run("bash -c 'printenv'")

โ˜› Execute Bash Command With Env Variables:

out, err := client.Run(`env MYVAR="MY VALUE" bash -c 'echo $MYVAR;'`)

๐Ÿฅช Using Goph Cmd:

Goph.Cmd struct is like the Go standard os/exec.Cmd.

// Get new `Goph.Cmd`
cmd, err := client.Command("ls", "-alh", "/tmp")

if err != nil {
	// handle the error!
}

// You can set env vars, but the server must be configured to `AcceptEnv line`.
cmd.Env = []string{"MY_VAR=MYVALUE"}

// Run you command.
err = cmd.Run()

๐Ÿ—’๏ธ Just like os/exec.Cmd you can run CombinedOutput, Output, Start, Wait, and ssh.Session methods like Signal...

๐Ÿ“‚ File System Operations Via SFTP:

You can easily get a SFTP client from Goph client:

sftp, err := client.NewSftp()

if err != nil {
	// handle the error!
}

file, err := sftp.Create("/tmp/remote_file")

file.Write([]byte(`Hello world`))
file.Close()

๐Ÿ—’๏ธ For more file operations see SFTP Docs.

๐Ÿฅ™   Examples

See Examples.

๐Ÿค   Missing a Feature?

Feel free to open a new issue, or contact me.

๐Ÿ“˜   License

Goph is provided under the MIT License.

Comments
  • Making defaultPath evaluation lazy

    Making defaultPath evaluation lazy

    Would you consider wrapping defaultPath in a function? The issue is on Windows environments HOME is not set by default. Simple solution would be setting it manually when the program starts:

    os.Setenv("HOME", os.Getenv("USERPROFILE"))
    

    but it doesn't get recognized by goph since it is evaluated beforehand.

    opened by xxzefgh 5
  • upload multiple files with same sftp connection

    upload multiple files with same sftp connection

    I noticed that when trying to upload in sequence multiple files (calling the Upload function for one file after the other) at some point Linux hosts will kick you out with a "administratively prohibited: open failed". Is there a way around it?

    opened by iotgopher 4
  • Issue with known hosts

    Issue with known hosts

    I believe this is not completely correct. I have have a host (NAME) I should be saving that first and then the remote IP or both but right now it only save the remote IP.. but the Check for Knownhost seems to use the host rather than IP so I get a mistmatched

    https://github.com/melbahja/goph/blob/f67d58a8a072ab20293fd0813895e7c3130aecf0/hosts.go#L99

    Also I was looking at https://www.godoc.org/golang.org/x/crypto/ssh#HostKeyCallback and it seems that It receives the hostname as passed to Dial or NewClientConn

    I think the logic should check if host and remote are the same otherwise write the host if not matching

    opened by epinault 3
  • Custom

    Custom "port" ?

    There are a few places (including command line flags) where it is mentioned but I don't see it being actually used. Is it implemented? If yes, how is it set?

    opened by xpufx 3
  • Prompt to Add to knownhosts on new connections

    Prompt to Add to knownhosts on new connections

    This is an ssh.HostKeyCallback function I came up with and probably needs to be cleaned up. This was apart of an internal package I created for ssh connections. It doesn't print fingerprints as it should. But it was ignored because the tool I was creating was an internal project.

    func knownHostKey(hostname string, remote net.Addr, key ssh.PublicKey) error {
    	knownFile := os.ExpandEnv("$HOME/.ssh/known_hosts")
    	_, err := os.Stat(knownFile)
    	if errors.Is(err, os.ErrNotExist) {
    		f, ferr := os.OpenFile(knownFile, os.O_CREATE, 0600)
    		if ferr != nil {
    			return ferr
    		}
    		f.Close()
    	}
    	callback, err := knownhosts.New(knownFile)
    	if err != nil {
    		return err
    	}
    	err = callback(hostname, remote, key)
    	if err == nil {
    		return nil
    	}
    	var keyErr *knownhosts.KeyError
    	if errors.As(err, &keyErr) && len(keyErr.Want) == 0 {
    		r := bufio.NewReader(os.Stdin)
    		fmt.Print("Unknown Host. Would you like to add it [y/N]: ")
    		reply, rerr := r.ReadString('\n')
    		if rerr != nil {
    			return rerr
    		}
    		reply = strings.Replace(reply, "\n", "", -1)
    		reply = strings.Replace(reply, "\r", "", -1)
    		if strings.ToLower(reply) != "y" {
    			return err
    		}
    		f, ferr := os.OpenFile(knownFile, os.O_WRONLY|os.O_APPEND, 0600)
    		if ferr != nil {
    			return ferr
    		}
    		defer f.Close()
    		knownHost := knownhosts.Normalize(remote.String())
    		_, err = f.WriteString(knownhosts.Line([]string{knownHost}, key) + "\n")
    		if err != nil {
    			return err
    		}
    		return nil
    	}
    	return err
    }
    

    I can add a PR if this is something you would like to support in this package.

    opened by dixonwille 3
  • Need a function that automatically adds host to known_hosts.

    Need a function that automatically adds host to known_hosts.

    Are you considering adding a function that automatically adds hosts to known_hosts for goph.Config.Callback?

    For some controllable internal networks, we basically do not need to consider man-in-the-middle attacks when connecting to the server, and we use other solutions to control user login and record behavior.

    opened by nightan42643 2
  • SSH NewSession Can Hang Indefinitely

    SSH NewSession Can Hang Indefinitely

    See https://github.com/golang/go/issues/26643

    The relevant fix can be seen here: https://github.com/cybozu-go/cke/pull/81/files

    I hit this using the latest goph client on a remote machine with a badly behaving SSH Server.

    Workaround is to timeout the channel in which the goph calls are being executed.

        select {
        case result := <- someChannel:
            fmt.Println(result)
        case <- time.After(timeout * time.Second):
            fmt.Println("ssh new session was hung")
        }
    
    opened by amcrn 2
  • Fix issue around adding incorrect KnownHost

    Fix issue around adding incorrect KnownHost

    Host should always be check against remote as it could be a name or an IP. but if the name is passed then it won t match the remote IP and therefore saved the incorrect entry in the known host

    opened by epinault 2
  • Download files

    Download files

    I get this error "no new variables on left side of :=" on this line of code err := client.Download("/path/to/remote/file", "/path/to/local/file")

    what can i do???

    opened by LfJohnVo 1
  • context support for command cancellation

    context support for command cancellation

    It is convenient to be able to cancel a command with context.

    A standard go library already has exec.CommandContext, and I just used the same approach here.

    When the context is cancelled, a SIGINT signal is sent to the session. I also considered making a specific signal configurable (because interrupt asks nicely and isn't always reliable). But for now this seems to work fine.

    The previous API without context still works fine, so no compatibility is broken.

    opened by antonsergeyev 1
  • example question

    example question

    When switch on "passphrase" as following. flag.BoolVar(&passphrase, "passphrase", true, "ask for private key passphrase.")

    it will output error message as following: `E:\goph-1.2.1\examples\goph>gophex.exe Enter Private Key Passphrase: panic: The handle is invalid.

    goroutine 1 [running]: main.askPass(0xb3ae66, 0x1e, 0x0, 0x0) E:/goph-1.2.1/examples/goph/main.go:156 +0x1a5 main.getPassphrase(...) E:/goph-1.2.1/examples/goph/main.go:168 main.main() E:/goph-1.2.1/examples/goph/main.go:114 +0x4d7` No chance to input Passphrase

    opened by hongnod 1
  • Added 'SeparatedOutput' command to capture stdout and stderr separately

    Added 'SeparatedOutput' command to capture stdout and stderr separately

    I just copy/pasted the code you already had but allowed a channel for stderr. Had to wrestle with the type checker, so that's why all the code I added is duplicate.

    The interesting part is here

     	c.Session.Stdout = &stdout_buff
    	c.Session.Stderr = &stderr_buff
    

    And now I don't have to lose any information when running remote commands that may have stdout and stderr.

    opened by AriSweedler 0
  • Support two-factor auth with key and password

    Support two-factor auth with key and password

    Hello! It seems that goph currently does not support two-factor auth with a key AND a password. See https://security.stackexchange.com/questions/17931/possible-to-use-both-private-key-and-password-authentication-for-ssh-login. Thank you!

    opened by qwerty287 1
  • How to get session.stdout change events๏ผŸ

    How to get session.stdout change events๏ผŸ

    I want to do something when the session.stdout is changed. Now I can only judge whether the length is zero through the timer loop, which seems very unfriendly to performance. For example.

    	ticker := time.NewTicker(time.Duration(100) * time.Millisecond)
    	defer ticker.Stop()
    
    	for {
    		select {
    		case <-ticker.C:
    			data := session.Output.Bytes()
    			if len(data) > 0 {
    				// do something
    			}
    		case <-cancel:
    			return
    		}
    	}
    
    opened by iwctwbai 0
  • Add sudo

    Add sudo

    This pr is for this issue: https://github.com/melbahja/goph/issues/21 You can now run sudo commands like this:

    package main
    
    import (
    	"fmt"
    	"log"
    )
    
    func main() {
    	// Start new ssh connection with private key.
    	auth, err := Key("/home/smss/.ssh/id_rsa", "")
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	client, err := New("smss", "127.0.0.1", auth)
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	// Defer closing the network connection.
    	defer client.Close()
    	client.SetPass("test_pass")
    	out, err := client.Run("sudo ls /")
    
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	// Get your output as []byte.
    	fmt.Println(string(out))
    }
    
    opened by SmsS4 0
  • How can I escape ```client.Run(cmd)``` loop?

    How can I escape ```client.Run(cmd)``` loop?

    Question: How can I escape the client.Run(cmd) loop? When I call client.Run(cmd) or cmd.Execute(myCmd) my program can't go to the next tasks. I am trying to run the command in the background of the remote host. then Close connection and allow the app to do other things.

    defer sshClient.Close()         // The app cannot close this 
     _, _:= sshClient.Run(myCmd + "&")     // Infinite loop.
                
    someEsleTasks()                // The app cannot execute this line 
    

    Currently I'm skipping Runc(cmd) and jumping to the next code via goroutine. like this

           go func() {
               _, _:= sshClient.Run(myCmd + "&")     // Infinite loop.
    
               sshClient.Close()             // The app cannot execute this line 
           }()
    

    I'm looking for a logical solution to my problem. thank you so much

    opened by bashery 6
Owner
Mohamed El Bahja
Developer, FOSS Enthusiast.
Mohamed El Bahja
Gsshrun - Running commands via ssh on the server/hosting (if ssh support) specified in the connection file

Gsshrun - Running commands via ssh on the server/hosting (if ssh support) specified in the connection file

Mษ™hษ™mmษ™d 2 Sep 8, 2022
wire protocol for multiplexing connections or streams into a single connection, based on a subset of the SSH Connection Protocol

qmux qmux is a wire protocol for multiplexing connections or streams into a single connection. It is based on the SSH Connection Protocol, which is th

Jeff Lindsay 191 Sep 25, 2022
SSHWaiterUtil - Wait for file to appear over an SSH connection

SSHWaiterUtil Simple util to wait for a remote file to appear, over SSH using pr

George Johnson 0 Jan 11, 2022
Nhat Tran 0 Feb 10, 2022
The devs are over here at devzat, chat over SSH!

Devzat Where are the devs at? Devzat! Devzat is chat over SSH Try it out: ssh sshchat.hackclub.com Add this to ~/.ssh/config: Host chat HostName s

Ishan Goel 1.4k Oct 3, 2022
red-tldr is a lightweight text search tool, which is used to help red team staff quickly find the commands and key points they want to execute, so it is more suitable for use by red team personnel with certain experience.

Red Team TL;DR English | ไธญๆ–‡็ฎ€ไฝ“ What is Red Team TL;DR ? red-tldr is a lightweight text search tool, which is used to help red team staff quickly find t

ๅ€พๆ—‹ 176 Sep 23, 2022
Golang `net/rpc` over SSH using installed SSH program

Golang net/rpc over SSH using installed SSH program This package implements a helper functions to launch an RPC client and server. It uses the install

null 0 Nov 5, 2021
Send network packets over a TCP or UDP connection.

Packet is the main class representing a single network message. It has a byte code indicating the type of the message and a []byte type payload.

Aero 74 Sep 27, 2022
Extended ssh-agent which supports git commit signing over ssh

ssh-agentx ssh-agentx Rationale Requirements Configuration ssh-agentx Configuration ssh-gpg-signer Linux Windows Signing commits after configuration T

Wim 10 Jun 29, 2022
one simple git ssh server (just for learning git over ssh )

wriet one simple git ssh server use golang write one simple git ssh server how to running starting service docker-compose up -d add authorized_keys i

rong fengliang 2 Mar 5, 2022
Helps you to send ssh commands to target machine in your local network from outside via gRPC

rpc-ssh In case, you don't want to make your ssh port accessible from outside local network. This repository helps you to send ssh commands to target

Berkay Akyazฤฑ 1 Dec 8, 2021
โ€œDear Port80โ€ is a zero-config TCP proxy server that hides SSH connection behind a HTTP server!

Dear Port80 About The Project: โ€œDear Port80โ€ is a zero-config TCP proxy server that hides SSH connection behind a HTTP server! +---------------------

Abbas Gheydi 6 Jun 29, 2022
Lightweight rest api that allows users to run Powershell commands over HTTP.

Powershell Proxy Lightweight rest api that allows users to run Powershell commands over HTTP. Requests require a valid JWT and responses are returned

Thomas Sampson 4 Mar 18, 2022
Make TCP connection storm between server and client for benchmarking network stuff

Make TCP connection storm between server and client for benchmarking network stuff

Masahiro Nagano 2 Nov 14, 2021
Clidle - Wordle over SSH with golang

clidle Wordle, now over SSH. Try it: ssh clidle.ddns.net -p 3000 Or, run it loca

Ajeet D'Souza 416 Sep 19, 2022
Chisel is a fast TCP/UDP tunnel, transported over HTTP, secured via SSH.

Chisel is a fast TCP/UDP tunnel, transported over HTTP, secured via SSH. Single executable including both client and server. Written in Go (golang). Chisel is mainly useful for passing through firewalls, though it can also be used to provide a secure endpoint into your network.

Jaime Pillora 7.9k Sep 30, 2022
Serve traffic (HTTP/gRPC) over SSH using Domain Sockets

Serve On SSH Introduction There is often a need to offer services for administrative purposes on servers or even for microservices that are running on

John Doak 7 Jan 9, 2022
Chat over SSH.

ssh-chat Custom SSH server written in Go. Instead of a shell, you get a chat prompt. Demo Join the party: $ ssh ssh.chat Please abide by our project'

Andrey Petrov 4.9k Sep 25, 2022