1
\$\begingroup\$

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)

}

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.