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\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.