Skip to content

Instantly share code, notes, and snippets.

@netmiller
Last active January 27, 2017 11:17
Show Gist options
  • Save netmiller/15ceb0ddf2d6e37ed7f86d5bba771f1a to your computer and use it in GitHub Desktop.
Save netmiller/15ceb0ddf2d6e37ed7f86d5bba771f1a to your computer and use it in GitHub Desktop.
golang versio SSH-tunnelista (yhteys MySQL-kantaan; notty oletuksena)
package main
import (
"os"
"log"
"net"
"io"
"io/ioutil"
"bytes"
"time"
// "errors"
"fmt"
"bufio"
"strconv"
s "strings"
"golang.org/x/crypto/ssh"
"crypto/md5"
"encoding/hex"
// "encoding/base64"
// "encoding/json"
)
type config struct {
server string
localPort int
user string
authKey string
debug bool
md5hash string
}
type Endpoint struct {
Host string
Port int
}
// palauttaa Endpoint-struct:n kentät (host:port muodossa)
func (ep *Endpoint) get() string {
return fmt.Sprintf("%s:%d", ep.Host, ep.Port)
}
type SSHtunnel struct {
Local *Endpoint
Server *Endpoint
Database *Endpoint
Config *ssh.ClientConfig
}
// luetaan config-tiedosto, josta saadaan tarvittavia muita tietoja
func readConf(path string) *config {
inFile,err := os.Open(path)
if err != nil {
log.Fatal("\n** ERROR: Tiedostoa ei löydy:",path)
}
conf := new(config)
defer inFile.Close()
scanner := bufio.NewScanner(inFile)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
rivi := s.ToLower(scanner.Text())
// ohitetaan kommentit ja tyhjät rivit
if (s.HasPrefix(rivi,"#") || len(rivi)==0) { continue }
fmt.Println("*",rivi)
// haetaan asetukset ja palautetaan config_struct
haku := s.Split(rivi,":")
if s.HasPrefix(haku[0],"user") { conf.user = s.TrimSpace(haku[1]) }
if s.HasPrefix(haku[0],"auth") { conf.authKey = s.TrimSpace(haku[1]) }
if s.HasPrefix(haku[0],"serv") { conf.server = s.TrimSpace(haku[1]) }
if s.HasPrefix(haku[0],"localp") { conf.localPort, _ = strconv.Atoi(s.TrimSpace(haku[1])) }
if s.HasPrefix(haku[0],"debug") { conf.debug, _ = strconv.ParseBool(s.TrimSpace(haku[1])) }
}
return conf
}
func readAuth(conf *config) []byte {
// luetaan private-key tiedostosta
buf, err := ioutil.ReadFile(conf.authKey)
if err != nil {
fmt.Println("\n** ERROR: AuthKey_tiedostoa ei löydy !")
fmt.Println("** Tarkista löytyykö tiedosto : [", conf.authKey, "]")
fmt.Println("** OHJELMA KESKEYTETÄÄN. Tarkista virheet ja yritä uudelleen.\n")
log.Fatal("** EOF **")
}
return buf
}
func getMD5Hash(buf []byte) string {
hasher := md5.New()
hasher.Write(buf)
return hex.EncodeToString(hasher.Sum(nil))
}
func (tunnel *SSHtunnel) Start() error {
listener, err := net.Listen("tcp", tunnel.Local.get())
if err != nil { return err }
fmt.Println("\n* Winvakan tietokantayhteys valmiina!")
defer listener.Close()
for {
conn,err := listener.Accept()
if err != nil { return err }
// asetetaan conn objektille "SetDaedline" 0 joka toivottavasti pitää yhteyden auki ilman aikarajaa
conn.SetDeadline(time.Time{})
go tunnel.forward(conn)
}
}
func (tunnel *SSHtunnel) test(cmd string) string {
defer func() {
// recover from panic if one occured. Set err to nil otherwise.
if (recover() != nil) {
fmt.Println("\n* --------------------------------------------------------------------- ")
fmt.Println("* ERROR: Winvaka:n tietokantayhteyttä ei saatu auki !! ")
fmt.Println("* Tarkista tietoliikenneyhteys ja tarvittaessa ota yhteys ylläpitoon. ")
fmt.Println("* --------------------------------------------------------------------- ")
// fmt.Println("\n")
log.Fatal("** EOF **")
// os.Exit(9)
}
}()
fmt.Println("* Connection test [",tunnel.Server.get(),"] ->")
testConn,_ := ssh.Dial("tcp", tunnel.Server.get(), tunnel.Config)
// if err != nil { log.Fatal("\n** ERROR: no connection ") }
session, _ := testConn.NewSession()
defer session.Close()
var stdoutBuf bytes.Buffer
session.Stdout = &stdoutBuf
session.Run(cmd)
// fmt.Println("cmd-id :", stdoutBuf.String())
// "id" komennosta voisi ottaa userid:n talteen lokeja varten ?
fmt.Println("* Yhteys serverille OK")
session.Close()
return stdoutBuf.String()
}
func (tunnel *SSHtunnel) forward(localConn net.Conn) {
serverConn, err := ssh.Dial("tcp", tunnel.Server.get(), tunnel.Config)
if err != nil {
fmt.Println("* Server dial error: %s\n", err)
return
}
dbConn, err := serverConn.Dial("tcp", tunnel.Database.get())
if err != nil {
fmt.Println("Database dial error: %s\n", err)
return
}
fmt.Println("\n* connection start ...")
copyConn := func(writer, reader net.Conn) {
_, err := io.Copy(writer, reader)
if err != nil { fmt.Printf("io.Copy error: %s", err) }
}
go copyConn(localConn, dbConn)
go copyConn(dbConn, localConn)
}
// tällä funktiolla voisi toteuttaa "known-host" logiikan mikäli tekee siihen oman ratkaisun
// func KeyPrint(dialAddr string, addr net.Addr, key ssh.PublicKey) error {
// // fmt.Printf("%s %s %s\n", strings.Split(dialAddr, ":")[0], key.Type(), base64.StdEncoding.EncodeToString(key.Marshal()))
// fmt.Printf("%s\n", base64.StdEncoding.EncodeToString(key.Marshal()))
// return nil
// }
// ---
func main() {
cfile := "./config"
fmt.Println()
log.Println("Tarkistetaan asetukset tiedostosta: [",cfile,"]")
// luetaan aluksi config-tiedosto
var conf *config = readConf(cfile)
fmt.Println()
// luetaan myös private-key mukaan tiedostosta (annettu conf-asetuksissa)
// var key ssh.Signer = readAuth(conf.authKey)
// var key ssh.Signer = readAuth(conf)
var keybuf []byte = readAuth(conf)
// tarkistetaan avaimen sisältö
// fmt.Printf("pub-key-luettu:\n %s \n", keybuf)
// lasketaan private-key avaimesta md5, jolla voisi tarkistaa jostain?? oikeellisuuden
// (ei ehkä ole hyötyä; joku api-key-systeemi voisi olla järkevämpi)
hash := getMD5Hash(keybuf)
conf.md5hash = hash
// if conf.debug { fmt.Println("* md5(key) ->",hash) }
var key, _ = ssh.ParsePrivateKey(keybuf)
// debug-ehdolla näytetään koko config-struct sisältö
// if conf.debug { fmt.Printf("* config -> %+v \n",conf) }
// out,_ := json.Marshal(conf)
// oo,_ := json.MarshalIndent(out, "", " ")
// avataan ssh-port-forward tietokantaserverille
localEndpoint := &Endpoint{
Host: "localhost",
// Port: 9000,
Port: conf.localPort,
}
serverEndpoint := &Endpoint{
// Host: "178.62.209.165", // turgon
Host: conf.server,
Port: 22,
}
// nämä ovat vielä toistaiseksi vakioita
dbEndpoint := &Endpoint{
// Host: "localhost",
Host: "127.0.0.1",
Port: 3306,
}
sshConfig := &ssh.ClientConfig {
User: conf.user,
Auth: []ssh.AuthMethod{ ssh.PublicKeys(key) },
// HostKeyCallback: KeyPrint, // funktiota ei toistaiseksi tarvita koska sille syötetään suoraan "nil"
HostKeyCallback: nil,
Timeout: 0,
}
tunnel := &SSHtunnel{
Config: sshConfig,
Local: localEndpoint,
Server: serverEndpoint,
Database: dbEndpoint,
}
// testataan muodostuuko yhteys
tunnel.test("id")
// avataan forward-tunneli
tunnel.Start()
// log.Print("Suljetaan yhteys.\n\n")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment