sshql — SSH 越しに RDBMS にアクセスする

no extension

拙作 github.com/goark/sshql は SSH 経由で MySQL や PostgreSQL といった RDBMS に接続するための Go パッケージである。

github.com/goark/sshql パッケージでは以下の Dialer 型の構造体を用意し,これを使って SSH に接続する1

// 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
}

パスワード認証の場合は UsernamePassword にそれぞれ値をセットする。 PrivateKey に認証用の秘密鍵へのパスを指定する場合は Password に秘密鍵のパスフレーズをセットする。 また ssh-agent 等を使って $SSH_AUTH_SOCK ソケットから秘密鍵を取得できる場合は,こちらを優先するようになっている。

IgnoreHostKeytrue をセットするとホスト認証を無視するようになっているが,セキュリティ上お勧めできないのでご注意を。 ホスト認証は $HOME/.ssh/known_hosts ファイルにホスト鍵が登録されていることが前提で, known_hosts ファイルがない,または known_hosts にホスト鍵がない場合は

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

という感じにエラーになる。

では,これを使って実際にコードを書いてみる。

github.com/lib/pq + github.com/goark/sshql

まずは PostgreSQL 用ドライバ github.com/lib/pqgithub.com/goark/sshql を組み合わせるところから。 こんな感じに書ける。

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()
}

"postgres+ssh" としてドライバを登録し(名前は何でもOK),この名前を sql.Open() 関数で指定しているのがポイントである。 あとは database/sql の機能をそのまま使うことができる。

github.com/goark/sshql/pgdrv パッケージ内部で github.com/lib/pq パッケージを呼び出しているため

import _ "github.com/lib/pq"

のようなインポートは不要である。

github.com/jackc/pgx + github.com/goark/sshql

github.com/jackc/pgxgithub.com/lib/pq の後継とも言えるパッケージである。 標準 database/sql パッケージと組み合わせて使うことも可能だが,自身が database/sql 互換のインタフェースを持っていて更に独自の機能も搭載している。 PostgreSQL にアクセスするなら,個人的にはこちらのほうがお勧めである。

拙作 github.com/goark/sshql と組み合わせて使うにはちょっと変則的な初期化コードを書く。 こんな感じ。

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/goark/sshql"
    "github.com/jackc/pgx/v4/pgxpool"
)

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

    cfg, err := pgxpool.ParseConfig("postgres://dbuser:dbpassword@localhost:5432/example?sslmode=disable")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
    cfg.ConnConfig.DialFunc = dialer.DialContext

    if err := dialer.Connect(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
    defer dialer.Close()

    pool, err := pgxpool.ConnectConfig(context.TODO(), cfg)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
    defer pool.Close()

    rows, err := pool.Query(context.TODO(), "SELECT id, name FROM example ORDER BY id")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
    rows.Close()

    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)
    }
}

まず pgx.ConnConfig を含む pgx/pgxpool.Config 構造体を作って,そこに dialer.DialContext() メソッドを登録するイメージ。 pgx ドライバーをオープンする前に dialer.Connect() つまり SSH 接続を明示的に記述する必要がある。

一見まどろこしく見えるが, pgx/pgxpool.Config 構造体を使って logger 登録を含むカスタマイズができるので,実用上はそれほど迂遠なコードではなかったりする。 接続ごとにインスタンスを作れるので,グローバルに名前をつけてドライバーを登録するより合理的かもしれない。

github.com/go-sql-driver/mysql + github.com/goark/sshql

MySQL 用ドライバ github.com/go-sql-driver/mysqlgithub.com/goark/sshql との組み合わせはこんな感じ。

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()
}

github.com/lib/pq のときとは違いドライバー名ではなくプロトコル名(通常は "tcp" など)として登録し DSN に登録したプロトコル名(ここでは ssh+tcp)を含める。 あとは database/sql の機能をそのまま使うことができる。

github.com/goark/sshql/mysqldrv パッケージ内部で github.com/go-sql-driver/mysql パッケージを呼び出しているため

import _ "github.com/go-sql-driver/mysql"

のようなインポートは不要である。

参考図書

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)


  1. 今回のパッケージは github.com/mattn/pqssh の事実上の fork である。同パッケージに敬意を表して拙作の方も MIT ライセンスで提供している。感謝! ↩︎