SSH Reverse Tunnel with GO - go

I am trying to write a server / client that can help get around Firewalls / Nat Issues.
I noticed SSH has built into support for doing this already.
(http://rustyrazorblade.com/2010/03/ssh-reverse-tunnel-to-access-box-behind-firewall/)
I tried a few different SSH examples and none seem to be working. I found one project that says it implemented the Remote Port Fowarding -> https://godoc.org/dev.justinjudd.org/justin/easyssh
The Server says it is Listening for connections but I am unable to SSH from Server Machine To Client Machine. (ssh localhost 8080 on remote machine should forward to client machine.
Client ->
package main
import (
"log"
"dev.justinjudd.org/justin/easyssh"
"golang.org/x/crypto/ssh"
)
func main() {
config := &ssh.ClientConfig{
User: "test",
Auth: []ssh.AuthMethod{
ssh.Password("test"),
},
}
conn, err := easyssh.Dial("tcp", "*SSH-SERVER*:22", config)
if err != nil {
log.Fatalf("unable to connect: %s", err)
}
defer conn.Close()
err = conn.RemoteForward("0.0.0.0:8080", "127.0.0.1:22")
if err != nil {
log.Fatalf("unable to forward local port: %s", err)
}
}
Server ->
package main
import (
"fmt"
"io/ioutil"
"log"
"dev.justinjudd.org/justin/easyssh"
"golang.org/x/crypto/ssh"
)
func main() {
privateBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
log.Fatal("Failed to load private key (./id_rsa)")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
log.Fatal("Failed to parse private key")
}
config := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if c.User() == "test" && string(pass) == "test" {
log.Printf("User logged in: %s", c.User())
return nil, nil
}
return nil, fmt.Errorf("password rejected for %s", c.User())
},
}
config.AddHostKey(private)
easyssh.HandleChannel(easyssh.SessionRequest, easyssh.SessionHandler())
easyssh.HandleChannel(easyssh.DirectForwardRequest, easyssh.DirectPortForwardHandler())
easyssh.HandleRequestFunc(easyssh.RemoteForwardRequest, easyssh.TCPIPForwardRequest)
easyssh.ListenAndServe(":22", config, nil)
}

I found a bug related to remote port forwarding in easyssh:
https://dev.justinjudd.org/justin/easyssh/src/master/tcpip.go#L107
the ssh.DiscardRequests(reqs) should be run in a separated goroutine, or else the next data transfer will not be executed.

I'm not sure that you understand how SSH tunneling works looking on your code.
You need to have SSH connectivity to the remote (server).
Then you setup SSH tunnel so local TCP:8080 port will be forwarded to the remote server TCP:8080 port using SSH connection. Actual 8080 port can be closed via firewall.
Can you connect from your client to your server with SSH?
You need to check localhost:8080 port and you need to be sure that your server 8080 port is listened by some application too.
Take a look here for some examples and theory.

Related

How to communication between 2 program with different IP in Golang

can someone recommend me a communication protocol that can connect 2 Golang programs with different IP address (other devices) ? Because when I try Socket programming in Golang, it can only connect programs on localhost.
if you want to run a server on your home network which is probably behind a NAT and connect to it from outside of you home network
first:
you need to bind your server socket to your local ip address which might be something
like 192.168.... and listen to it
second:
when systems outside of your network send packets to your server they send it to your
public Ip which you can find by googling "what is my ip" and therefore your router
has no idea what local ip address it should forward the packet to that is why you
need to do Port Forwarding which basically tells the router "hey when ever you
received a packet on port "n" send it to a system with the ip address of "x.x.x.x".
so if you local ip is 192.168.1.10 and your public ip is 5.68.125.48 and you are listening on port 8080 on your router setup page you forward port 8080 to 192.168.1.10 and on the client side you connect and send packets to 5.68.125.48:8080 now the router knows who on the local network should receive the packet
You can try a solution like this:
package main
import (
"bufio"
"fmt"
"net"
"strconv"
"time"
)
func main() {
fmt.Println("Server started...")
ln, err := net.Listen("tcp", ":8000")
if err != nil {
fmt.Println("Error starting socket server: " + err.Error())
}
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println("Error listening to client: " + err.Error())
continue
}
fmt.Println(conn.RemoteAddr().String() + ": client connected")
go receiveData(conn)
go sendData(conn)
}
}
func sendData(conn net.Conn) {
i := 0
for {
_, err := fmt.Fprintf(conn, strconv.Itoa(i)+". data from server\n")
i++
if err != nil {
fmt.Println(conn.RemoteAddr().String() + ": end sending data")
return
}
time.Sleep(time.Duration(1) * time.Second)
}
}
func receiveData(conn net.Conn) {
for {
message, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
fmt.Println(conn.RemoteAddr().String() + ": client disconnected")
conn.Close()
fmt.Println(conn.RemoteAddr().String() + ": end receiving data")
return
}
fmt.Print(conn.RemoteAddr().String() + ": received " + message)
}
}

Unable to get the Socks Proxy working Golang applications

I am running a golang application on my local laptop, which connects with the Service APIs from a kubernetes cluster. To allow the communication between my laptop and Service APIs on Kubernetes Cluster, I have setup the Socks5 proxy and Able to connect with all services over CURL command.
But I am unable to set same proxy in the Golang application. I have tried various options already
Settings env vars - tried setting these 3 at OS level, App level but it did not work
http_proxy
https_proxy
all_proxy
I am running on Ubuntu Desktop 20.X, so I tried setting Socks proxy at OS Network level as well. All other apps using proxy, but golang isnt picking that up.
I tried setting the http.transport level configs too, but it didnt helped.
func InsecureTLSTransport() *http.Transport {
// START - Only for Proxy - Dev Mode.
var dialer, err = proxy.SOCKS5("socks5", "<IP:port>", nil, proxy.Direct)
if err != nil {
fmt.Fprintln(os.Stderr, "can't connect to the proxy:", err)
}
return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Dial: dialer.Dial,
}
}
I want the socks5 Proxy to be used by my golang app at runtime. Any suggestions?
you should use tcp instead of socks5 in the network parameter.
package main
import (
"fmt"
"golang.org/x/net/proxy"
)
func main() {
d, err := proxy.SOCKS5("tcp", "<IP:port>", nil, proxy.Direct) // <-- here
if err != nil {
fmt.Println(err)
return
}
c, err := d.Dial("tcp", "https://google.com")
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
}

SSH through bastion host

I've just started to use Go and I am trying to setup an ssh connection through a bastion host, i successfully authenticate to the bastion host, but fail on the LAN host. I've read a number of posts, the answer to this i've found very helpful. But i'm not sure what would be in that persons config. My code is as follows. I'm trying to do with with PublicKeys only and if its important i'm starting on a mac, authenticate to linux, then fail to make the second connection to another linux host. Plain ssh works fine
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
"io/ioutil"
"log"
"os/user"
)
const TCP = "tcp"
const PORT = "22"
func bastionConnect(bastion string, localh string) *ssh.Client {
var usr, _ = user.Current()
var homeDir = usr.HomeDir
fmt.Printf("home is %v\n", homeDir)
key, err := ioutil.ReadFile(homeDir + "/.ssh/id_rsa")
if err != nil {
fmt.Print("i'm dying at reading ssh key")
panic(err)
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
fmt.Print("i'm dying at parsing private key")
panic(err)
}
fmt.Printf("I'm returning public keys for %v", signer.PublicKey())
config := &ssh.ClientConfig{
User: usr.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}
bClient, err := ssh.Dial(TCP, bastion+":22", config)
if err != nil {
log.Fatal(err)
}
fmt.Print("passed bastion host\n")
// Dial a connection to the service host, from the bastion
conn, err := bClient.Dial(TCP, fmt.Sprintf("%s:%s", localh, PORT))
if err != nil {
log.Fatal(err)
}
ncc, chans, reqs, err := ssh.NewClientConn(conn, fmt.Sprintf("%s:%s", localh, PORT), config)
if err != nil {
fmt.Printf("Error trying to conntect to %s via bastion host\n%v\n", localh, err)
log.Fatal(err)
}
sClient := ssh.NewClient(ncc, chans, reqs)
return sClient
}
func main() {
var bastion = "jumpdev.example.org"
var lanHost = "devserver01"
bastionConnect(bastion, lanHost)
}
The last log line i see is Error trying to connect to devserver01 via bastion host with an error of
2020/02/03 14:40:17 ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey]
Pardon all the Printfs needed to see what's up.
In the second connect could the public key config be messing it up? I have also checked out this project, but seems like overkill.
The above code was fine, i was running into an authorized_keys issue on a box that i always connect to but forgot about my local .ssh/config :(
I wanted to expand on this a bit so it was not just whoops, i messed up post. For a full bastion to lanhost agent connection, I have updated a gist here

Dynamic port forward TCP traffic over reverse SSH tunnel

I have an SSH tunnel which dial into the server endpoint and starts up a remote listener on a port 1080. My goal is to dynamically forward any TCP traffic from that port to the client side of the tunnel. While I am able to accept TCP stream I am not able to find a way to Dial out on the client side of the SSH tunnel. It looks like the dialer I am passing is the serverConn server SSH end of the tunnel. And when I am attempting to remote, err := dialer.DialTCP("tcp4", local.RemoteAddr().(*net.TCPAddr), addr) I get the traffic dial on the remote end. I want to find a way to dial out at the client side over a reverse tunnel.
I am not looking to forward specific ports to a specific destination on local, but looking to do more dynamic port forwarding similar to ssh -D option. I am managing destination of TCP traffic myself, this is not a concern.
I have tried to create ssh.NewClient from the passed serverConn but it needs to perform an SSH handshake, and Iam only receiving raw TCP on the port 1080 so it's not a valid SSH client. Thanks.
func main(){
type Dialer interface {
DialTCP(net string, laddr, raddr *net.TCPAddr) (net.Conn, error)
}
// SSH server connection
sshConfig := &ssh.ClientConfig{
User: "tester",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Connect to SSH remote server using serverEndpoint (port 22)
serverConn, err := ssh.Dial("tcp", serverEndpoint.String(), sshConfig)
if err != nil {
log.Fatalln(fmt.Printf("Dial INTO remote server error: %s", err))
}
// Listen on remote server port (port 1080)
listener, err := serverConn.Listen("tcp",
remoteEndpoint.String())
if err != nil {
log.Fatalln(fmt.Printf("Listen open port ON remote server error: %s", err))
}
defer listener.Close()
acceptSLoop(listener, serverConn)
}
func acceptSLoop(listener net.Listener, sshClient *ssh.Client) {
fmt.Printf("Listener: %s\n", listener.Addr().String())
defer listener.Close()
for {
clientConn, err := listener.Accept()
fmt.Printf("local addr %s\n", clientConn.LocalAddr())
if err != nil {
log.Fatal(err)
}
fmt.Printf("New connection found on %s\n", listener.Addr().String())
listenSConnection(clientConn, sshClient, config)
}
}
func listenSConnection(SClientConn net.Conn, sshClient
*ssh.Client) {
// In a regular SSH I need to do
// sshConn, chans, reqs, err :=
ssh.NewServerConn(SClientConn, // config will be
passed in ..)
//
// But since it's TCP handoff I am passing socket data
directly
handleSConn(SClientConn, sshClient)
}
func handleSConn(local net.Conn, dialer Dialer) {
defer local.Close()
// Both of those establish socket from "remote" ssh side (Server) not "local" ssh side (client)
remote, err := dialer.DialTCP("tcp4", local.RemoteAddr().(*net.TCPAddr), addr)
//remote, err := net.Dial("tcp4", addr.String())
// transfer bytes from local SOCKS to remote desired endpoint over SSH
transfer(local, remote)
}
// in - local SOCKS conn, out - remote desired endpoint
func transfer(in, out net.Conn) {
//.... working
}

Go server not hearing remote requests

I've written a Go server that works perfectly as long as you send it requests from localhost (and addressed to localhost), but it doesn't work when you try to access it from a browser (from a different computer) or even just directed at the external IP address. I want to be able to access it as an external server, not just locally. Why can't it?
The (pared down) source code:
package main
import (
"fmt"
"net"
"os"
)
func main() {
// Listen for incoming connections.
l, err := net.Listen("tcp", "localhost:2082")
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
// Close the listener when the application closes.
defer l.Close()
for {
// Listen for an incoming connection.
_, err := l.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
fmt.Println("Incoming connection")
}
}
When you curl localhost:2082, it says "Incoming connection".
When you curl mydomain.com:2082, it does nothing.
The port is forwarded. I'm sure of this because I ran a (node.js) web server from that port, and it worked fine. If it's related, I'm running on Ubuntu 12.04 on an Amazon EC2 instance.
I'd appreciate any help. Thanks!
One way to listen to any incoming IP (not just localhost, mapped by default to 127.0.0.1) would be:
net.Listen("tcp", ":2082")
You also have the function net/http/#ListenAndServe, which allows you to trigger listen on multiple specific ip if you want.
go http.ListenAndServe("10.0.0.1:80", nil)
http.ListenAndServe("10.0.0.2:80", nil)
A good example can be seen in "A Recap of Request Handling in Go".

Resources