I developed this small code to make my life easier when I work with remote machines over ssh tunnel crossing a bastion host. I would like to publich it so others could benefit/learn from it.
So I would appreciate your opinions for improvement or maybe I overlooked some issues in this approach.
Goal:
I have a postgres VM in a private subnet running on default local host with port 5432. I want to be able to reach this machine from my go code or to let PgAdming GUI reach it from my local machine.
the workflow as following:
- create ssh client to Bastion machine
- from bastion client create a new client fo the postgres machine
- forward the remote port to local listner
Thanks!
sshHelper
package sshHelper
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"golang.org/x/crypto/ssh"
)
var home, err = os.UserHomeDir()
func SignKey(KeyName string) ssh.Signer {
pathToPrivateKey := filepath.FromSlash(fmt.Sprintf("%s/.ssh/%s", home, KeyName))
key, err := ioutil.ReadFile(pathToPrivateKey)
if err != nil {
panic(err)
}
// Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
panic(err)
}
return signer
}
func CreateClient(user string, signer ssh.Signer, address string, port string) *ssh.Client {
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
cleint, err := ssh.Dial("tcp", address+":"+port, config)
if err != nil {
panic(err)
}
return cleint
}
func ViaBastionConn(cleint *ssh.Client, user string, signer ssh.Signer, address string, port string) *ssh.Client {
conn, err := cleint.Dial("tcp", address+":"+port)
if err != nil {
log.Fatal(err)
}
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
ncc, chans, reqs, err := ssh.NewClientConn(conn, address+":"+port, config)
if err != nil {
log.Fatal(err)
}
newCleint := ssh.NewClient(ncc, chans, reqs)
return newCleint
}
func localListner(cleintBroker *ssh.Client, localAddress string, remoteAddress string) {
listener, err := net.Listen("tcp", localAddress)
if err != nil {
fmt.Println(err)
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err)
}
forward(cleintBroker, conn, remoteAddress)
}
}
func forward(cleintBroker *ssh.Client, localConn net.Conn, remoteAddress string) {
remoteConn, err := cleintBroker.Dial("tcp", remoteAddress)
if err != nil {
fmt.Printf("Remote dial error: %s\n", err)
return
}
copyConn := func(writer, reader net.Conn) {
_, err := io.Copy(writer, reader)
if err != nil {
fmt.Println("")
fmt.Printf("io.Copy error: %s", err)
fmt.Println("")
}
}
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
}
main.go
package main
import (
sshM "lib/sshHelper"
)
const (
sshPort = "22"
postgresUser = "ubuntu"
bastionUser = "ubuntu"
bastionAddress = "54.xxx.xxx.xxx"
postgresAddress = "10.0.x.xxx"
localAddr = "localhost:5432"
remoteAddr= "localhost:5432"
)
func main() {
bastionKey := "bastion_key.pem"
postgresKey := "postgres_key.pem"
signerBastion := sshM.SignKey(bastionKey)
signerPostgres := sshM.SignKey(postgresKey)
clientBastion := sshM.CreateClient(bastionUser, signerBastion, bastionAddress, sshPort)
cleintPostgres:= sshM.ViaBastionConn(clientBastion, brokerUser, signerPostgres, postgresAddress, sshPort)
go localListner(cleintPostgres, localAddr, remoteAddr)
}