Skip to content

Instantly share code, notes, and snippets.

@xor-gate
Forked from codref/go-ssh-reverse-tunnel.go
Created August 12, 2024 16:54
Show Gist options
  • Save xor-gate/c35b81baa4b07d21a7755c1d7be34250 to your computer and use it in GitHub Desktop.
Save xor-gate/c35b81baa4b07d21a7755c1d7be34250 to your computer and use it in GitHub Desktop.
Go SSH reverse tunnel implementation (SSH -R)
/*
Go-Language implementation of an SSH Reverse Tunnel, the equivalent of below SSH command:
ssh -R 8080:127.0.0.1:8080 [email protected]
which opens a tunnel between the two endpoints and permit to exchange information on this direction:
server:8080 -----> client:8080
once authenticated a process on the SSH server can interact with the service answering to port 8080 of the client
without any NAT rule via firewall
Copyright 2017, Davide Dal Farra
MIT License, http://www.opensource.org/licenses/mit-license.php
*/
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"net"
"golang.org/x/crypto/ssh"
)
type Endpoint struct {
Host string
Port int
}
func (endpoint *Endpoint) String() string {
return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port)
}
// From https://sosedoff.com/2015/05/25/ssh-port-forwarding-with-go.html
// Handle local client connections and tunnel data to the remote server
// Will use io.Copy - http://golang.org/pkg/io/#Copy
func handleClient(client net.Conn, remote net.Conn) {
defer client.Close()
chDone := make(chan bool)
// Start remote -> local data transfer
go func() {
_, err := io.Copy(client, remote)
if err != nil {
log.Println(fmt.Sprintf("error while copy remote->local: %s", err))
}
chDone <- true
}()
// Start local -> remote data transfer
go func() {
_, err := io.Copy(remote, client)
if err != nil {
log.Println(fmt.Sprintf("error while copy local->remote: %s", err))
}
chDone <- true
}()
<-chDone
}
func publicKeyFile(file string) ssh.AuthMethod {
buffer, err := ioutil.ReadFile(file)
if err != nil {
log.Fatalln(fmt.Sprintf("Cannot read SSH public key file %s", file))
return nil
}
key, err := ssh.ParsePrivateKey(buffer)
if err != nil {
log.Fatalln(fmt.Sprintf("Cannot parse SSH public key file %s", file))
return nil
}
return ssh.PublicKeys(key)
}
// local service to be forwarded
var localEndpoint = Endpoint{
Host: "localhost",
Port: 8080,
}
// remote SSH server
var serverEndpoint = Endpoint{
Host: "146.148.22.123",
Port: 22,
}
// remote forwarding port (on remote SSH server network)
var remoteEndpoint = Endpoint{
Host: "localhost",
Port: 8080,
}
func main() {
// refer to https://godoc.org/golang.org/x/crypto/ssh for other authentication types
sshConfig := &ssh.ClientConfig{
// SSH connection username
User: "operatore",
Auth: []ssh.AuthMethod{
// put here your private key path
publicKeyFile("/home/operatore/.ssh/id_rsa"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Connect to SSH remote server using serverEndpoint
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
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()
// handle incoming connections on reverse forwarded tunnel
for {
// Open a (local) connection to localEndpoint whose content will be forwarded so serverEndpoint
local, err := net.Dial("tcp", localEndpoint.String())
if err != nil {
log.Fatalln(fmt.Printf("Dial INTO local service error: %s", err))
}
client, err := listener.Accept()
if err != nil {
log.Fatalln(err)
}
handleClient(client, local)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment