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), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。
-
今回のパッケージは
github.com/mattn/pqssh
の事実上の fork である。同パッケージに敬意を表して拙作の方も MIT ライセンスで提供している。感謝! ↩︎