Go言語からSSHを踏み台にしてMySQL等に接続する

author potpro(ぼとぷろ)
2019/02/06

Go言語からSSHを踏み台にしてMySQL等に接続する

割と最近はGo言語を結構使ってきて構築することも多くなり、WebということでRDBの接続が基本的に不可欠になってきます。 しかし、セキュリティ的な問題で、データベースを外部に公開できない場合が多いです。

というより、大体そうなっているものだと思います。

そこでGo言語は簡単にバッチを書け、データベースの接続も可能ですぐバイナリに出来るという言語ということで、非常に好みだったりするのですが、

そうなるとデータベースに外部から接続できないのが問題となるので、SSHでデータベース内に入ってから、localhostで接続したりします。

自分のPC --->(SSH TCP22) MySQLが動いているサーバ --->(MySQL 3306) MySQL という感じに。

しかし、当然プログラムなのでそういった処理を書かなくてはなりません。

それを解消したときの記事。Go言語はsshもsqlも大体用意されているので、割と簡単に行けます。

コード

参考にしたURL Using MySQL / MariaDB via SSH in Golang

Go言語で踏み台サーバー越しにDBからデータを取得する

とりあえずググって来たものを参考に書き直し。

基本的にワンライナーで書かれてしまっているので、上手く外部から読み込んで動くようにします。 悲しいけど全部パスワード認証となっております。

type DbServerWithSSH struct {
    Host        string
    SSHPort     uint
    SSHUser     string
    SSHPassword string
    DBPort      uint
    DBUser      string
    DBPassword  string
    Database    string
}

func Init(database config.DbServerWithSSH) (*sql.DB, error) {
    sshConfig := &ssh.ClientConfig{
        User: database.SSHUser,
        Auth: []ssh.AuthMethod{
            ssh.Password(database.SSHPassword),
        },
    }
    var err error
    conn, err = ssh.Dial("tcp", fmt.Sprintf("%s:%d", database.Host, database.SSHPort), sshConfig)
    if err != nil {
        Close()
        return nil, err
    }
    dialNet := "mysql+tcp"
    dialFunc := func(addr string) (net.Conn, error) {
        return conn.Dial("tcp", addr)
    }
    mysql.RegisterDial(dialNet, dialFunc)

    dsn := fmt.Sprintf("%s:%s@%s(localhost:%d)/%s", database.DBUser, database.DBPassword, dialNet, database.DBPort, database.Database)
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        Close()
        return nil, err
    }
    //Ping Check
    err = db.Ping()
    if err != nil {
        return nil, err
    }
    return db, err

}

// Close SSH含めデータベースから切断する
func Close() {
    if db != nil {
        db.Close()
    }
    if conn != nil {
        conn.Close()
    }
}

これを実行するテストコードがこれです。

func TestInit(t *testing.T) {
    config := config.DbServerWithSSH{
        Host:        "[ホスト名]",
        SSHPort:     22,
        SSHUser:     "[SSHユーザー名]",
        SSHPassword: "[SSHパスワード]",
        DBPort:      3306,
        DBUser:      "[MySQLユーザー名]",
        DBPassword:  "[MySQLパスワード]",
        Database:    "[MySQLデータベース名]",
    }
    db, err := Init(config)
  defer Close()
    if err != nil {
        t.Error(err)
    }
    if rows, err := db.Query("SELECT id,name FROM products LIMIT 10"); err == nil {
        for rows.Next() {
            var id int64
      var name string
            rows.Scan(&id,&name)
            fmt.Printf("ID: %d Name:%s\n", id, name)
        }
        rows.Close()
    } else {
        t.Error(err)
    }
}

出力

ID: 1 Name:A
ID: 2 Name:B
ID: 3 Name:C

mysql.RegisterDial?

RegisterDial

RegisterDial registers a custom dial function. It can then be used by the network address mynet(addr), where mynet is the registered new network. addr is passed as a parameter to the dial function.

RegisterDialはカスタムダイヤルfunctionを登録します。その後、ネットワークアドレス mynet(addr) で使用できます。ここで、mynetは、登録された新しいネットワークです。 addrはパラメータとしてダイヤルfunctionに渡されます。

説明が全くわからなかったのですが、RegisterDialにはダイアル名とnet.Conn, errorを返す関数を渡すと、関数内の処理を接続時に行い、独自の方法で接続するようです。 登録したダイアル名はDSNに設定することで使用可能。

ここでは、conn.Dial("tcp", addr)を渡しているので、ssh内で実行しているように動くということですね。

今回、mysql+tcpという名前にしていますが、被らなかったら多分何でもいいのかも。 これをうまく使うことで、TCP以外での独自の接続もできるわけですね。

しかしこれ、MySQL側にある関数なので他のRDBとかには存在するのだろうか?

と調べたらPostgreSQLなんかにはあった。挙動はほぼ同じだがsql.Registerを使うと行けるみたい。 これはsqlパッケージなので、むしろMySQLだけが独自実装?

Go言語は軽くバッチでも作りたいなというときにマルチプラットフォームかつスタンドアローンで動いてくれるので非常に便利で、PHPやJSとは特色も違うのでこれからも全然使っていきます。

というより、ようやくGoの記事書けた。ネタに悩んで書いていなかった。