sshql — SSH 越しに RDBMS にアクセスする
拙作 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
}
パスワード認証の場合は Username と Password にそれぞれ値をセットする。
PrivateKey に認証用の秘密鍵へのパスを指定する場合は Password に秘密鍵のパスフレーズをセットする。
また ssh-agent 等を使って $SSH_AUTH_SOCK ソケットから秘密鍵を取得できる場合は,こちらを優先するようになっている。
IgnoreHostKey に true をセットするとホスト認証を無視するようになっているが,セキュリティ上お勧めできないのでご注意を。
ホスト認証は $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/pq と github.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/pgx は github.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/mysql と github.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"
のようなインポートは不要である。
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。と思ったら絶版状態らしい(2025-01 現在)。復刊を望む!
-
今回のパッケージは
github.com/mattn/pqsshの事実上の fork である。同パッケージに敬意を表して拙作の方も MIT ライセンスで提供している。感謝! ↩︎

