SSH 越しに DB サーバにアクセスする
PostgreSQL や MySQL などの RDBMS サービスにアクセスするために Go では標準で database/sql
パッケージを用意している
(実際にサービスにアクセスするためには github.com/lib/pq
や github.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
をセットするとホスト認証をすっ飛ばしてくれる。
ブックマーク
- Using MySQL / MariaDB via SSH in Golang · GitHub
- Golang – How to write ssh.HostKeyCallback – cyruslab
- simukti/sqldb-logger: A logger for Go SQL database driver without modifying existing *sql.DB stdlib usage.
参考図書
- プログラミング言語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 言語の教科書と言ってもいいだろう。
- Go言語による分散サービス ―信頼性、拡張性、保守性の高いシステムの構築
- Travis Jeffery (著), 柴田 芳樹 (翻訳)
- オライリージャパン 2022-08-03
- 単行本(ソフトカバー)
- 4873119979 (ASIN), 9784873119977 (EAN), 4873119979 (ISBN)
- 評価
- デベロッパーゴースーパーゴラン Tシャツ
- Geek Go Super Golang Tees
- ウェア&シューズ
- B09C2XBC2F (ASIN)
- 評価
ついカッとなってポチった。反省はしない