SSH 越しに DB サーバにアクセスする

no extension

PostgreSQL や MySQL などの RDBMS サービスにアクセスするために Go では標準で database/sql パッケージを用意している (実際にサービスにアクセスするためには github.com/lib/pqgithub.com/go-sql-driver/mysql といったドライバ・パッケージを使う必要がある)。 たとえばこんな感じ。

db, err := sql.Open("postgres", "postgres://dbuser:dbpassword@dbserver:5432/example?sslmode=require")

ただし,これはクライアントからサービスに直結する場合で,たとえば VPS 内の RDBMS サービスに SSH 経由でアクセスする必要がある場合は少し工夫が必要である。 ありがたいことに PostgreSQL サービスに SSH 経由でアクセスするためのパッケージを mattn さんが公開して下さっている。

ありがたや 🙇

で,実は MySQL サービスに SSH 経由でアクセスする必要ができたので,上のパッケージを参考に自作してみた。

このパッケージを使ってこんな感じに書ける。

package main

import (
    "database/sql"
    "fmt"
    "os"

    "github.com/goark/sshql"
    "github.com/goark/sshql/mysqldrv"
)

func main() {
    dialer := &sshql.Dialer{
        Hostname:   "sshserver",
        Port:       22,
        Username:   "remoteuser",
        Password:   "passphraseforauthkey",
        PrivateKey: "/home/username/.ssh/id_eddsa",
    }
    mysqldrv.New(dialer).RegisterDial("ssh+tcp")

    db, err := sql.Open("mysql", "dbuser:dbpassword@ssh+tcp(localhost:3306)/dbname")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
    defer dialer.Close()
    defer db.Close()

    rows, err := db.Query("SELECT id, name FROM example ORDER BY id")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
    for rows.Next() {
        var id int64
        var name string
        if err := rows.Scan(&id, &name); err != nil {
            fmt.Fprintln(os.Stderr, err)
            break
        }
        fmt.Printf("ID: %d  Name: %s\n", id, name)
    }
    rows.Close()
}

MySQL の場合 SSH でアクセスするための Dialer を登録して,登録文字列を DSN に含める必要がある。

さらに,このパッケージを使った PostgreSQL への SSH 越しのアクセスはこんな感じに書ける。

package main

import (
    "database/sql"
    "fmt"
    "os"

    "github.com/goark/sshql"
    "github.com/goark/sshql/pgdrv"
)

func main() {
    dialer := &sshql.Dialer{
        Hostname:   "sshserver",
        Port:       22,
        Username:   "remoteuser",
        Password:   "passphraseforauthkey",
        PrivateKey: "/home/username/.ssh/id_eddsa",
    }
    pgdrv.New(dialer).Register("postgres+ssh")

    db, err := sql.Open("postgres+ssh", "postgres://dbuser:dbpassword@localhost:5432/example?sslmode=disable")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
    defer dialer.Close()
    defer db.Close()

    rows, err := db.Query("SELECT id, name FROM example ORDER BY id")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
    for rows.Next() {
        var id int64
        var name string
        if err := rows.Scan(&id, &name); err != nil {
            fmt.Fprintln(os.Stderr, err)
            break
        }
        fmt.Printf("ID: %d  Name: %s\n", id, name)
    }
    rows.Close()
}

やっぱ sql.Open() 関数の第1引数で専用ドライバを指定するほうがシンプルだよなぁ。 DSN 文字列をいじらなくて済むし。

【2022-09-30 追記】

github.com/jackc/pgx パッケージと組み合わせて使えるようにした。 詳しくは以下の記事を参考のこと。

InsecureIgnoreHostKey() 関数で叱られる

mattn さんの github.com/mattn/pqssh パッケージの中で

sshConfig := &ssh.ClientConfig{
    User:            d.Username,
    Auth:            []ssh.AuthMethod{},
    HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}

という記述があり,最初はそのまま真似してたのだが,例によって lint に「あかんがな!」と叱られた。

HostKeyCallback 項目は SSH ログイン時のホスト認証の動作をするもので, ssh.InsecureIgnoreHostKey() は何もせず nil を返却するだけの関数を渡している。

// InsecureIgnoreHostKey returns a function that can be used for
// ClientConfig.HostKeyCallback to accept any host key. It should
// not be used for production code.
func InsecureIgnoreHostKey() HostKeyCallback {
    return func(hostname string, remote net.Addr, key PublicKey) error {
        return nil
    }
}

こりゃあ,確かにあかんわ(笑)

最終的に今回の github.com/goark/sshql パッケージでは一応ホスト認証を行っているが ~/.ssh/known_hosts ファイルに登録されていないホストや登録されている鍵が異なる場合は問答無用でエラーを返すようにした。

$ go run sample.go 
ssh: handshake failed: knownhosts: key is unknown

まぁ,こういうパッケージはバッチ処理とかにしか使わないだろうし,ええじゃろう。

なお sshql.Dialer 構造体は

// Dialer is authentication provider information.
type Dialer struct {
    Hostname      string `json:"hostname"`
    Port          int    `json:"port"`
    Username      string `json:"username"`
    Password      string `json:"password"`
    PrivateKey    string `json:"privateKey"`
    IgnoreHostKey bool   `json:"IgnoreHostKey"`
    client        *ssh.Client
}

と定義しているけど IgnoreHostKey 要素に true をセットするとホスト認証をすっ飛ばしてくれる。

ブックマーク

参考図書

photo
プログラミング言語Go
アラン・ドノバン (著), ブライアン・カーニハン (著), 柴田芳樹 (著)
丸善出版 2016-06-20 (Release 2021-07-13)
Kindle版
B099928SJD (ASIN)
評価     

Kindle 版出た! 一部内容が古びてしまったが,この本は Go 言語の教科書と言ってもいいだろう。感想はこちら

reviewed by Spiegel on 2021-05-22 (powered by PA-APIv5)

photo
Go言語による分散サービス ―信頼性、拡張性、保守性の高いシステムの構築
Travis Jeffery (著), 柴田 芳樹 (翻訳)
オライリージャパン 2022-08-03
単行本(ソフトカバー)
4873119979 (ASIN), 9784873119977 (EAN), 4873119979 (ISBN)
評価     

版元でデジタル版を購入。読書会の課題図書。ハンズオンぽい構成でコードがたくさん書かれているのがよい。

reviewed by Spiegel on 2022-08-03 (powered by PA-APIv5)

photo
デベロッパーゴースーパーゴラン Tシャツ
Geek Go Super Golang Tees
ウェア&シューズ
B09C2XBC2F (ASIN)
評価     

ついカッとなってポチった。反省はしない

reviewed by Spiegel on 2022-04-10 (powered by PA-APIv5)