AlloyDBのConnectorやAuthProxyはなにをしているのか
TL;DR
- AuthProxyはConnectorを利用している
- ConnectorはAlloyDBのIPアドレス取得やクライアント証明書の発行をしている
- AuthProxyやConnectorを利用している場合でも、AlloyDBに接続するにはネットワーク的に接続できる経路が必要
はじめに
AlloyDBを利用するにあたり、VPC内から接続するだけでなく外部からマイグレーションやデータ確認をしたい場合があります。
公式ドキュメントを読む限り、接続するためにはAlloyDBConnectorやAuthProxyを利用することが推奨されています。
これらツールにどのような違いがあり、どのような仕組みで接続しているのか調査しました。
公式ドキュメントに記載されている内容
AlloyDB Language Connectors
- AlloyDB for PostgreSQLインスタンスに接続する際、mTLSを提供するライブラリ
- ユーザーアプリケーションに代わってプロキシサーバーへの承認済み接続を作成する
- Java・Python・Goのコネクタがある
- IAM権限でインスタンスに接続できる
利用例
import (
"context"
"fmt"
"net"
"cloud.google.com/go/alloydbconn"
"github.com/jackc/pgx/v5/pgxpool"
)
// connectPgx establishes a connection to your database using pgxpool and the
// AlloyDB Go Connector (aka alloydbconn.Dialer)
//
// The function takes an instance URI, a username, a password, and a database
// name. Usage looks like this:
//
// pool, cleanup, err := connectPgx(
// context.Background(),
// "projects/myproject/locations/us-central1/clusters/mycluster/instances/myinstance",
// "postgres",
// "secretpassword",
// "mydb",
// )
//
// In addition to a *pgxpool.Pool type, the function returns a cleanup function
// that should be called when you're done with the database connection.
func connectPgx(
ctx context.Context, instURI, user, pass, dbname string,
) (*pgxpool.Pool, func() error, error) {
// First initialize the dialer. alloydbconn.NewDialer accepts additional
// options to configure credentials, timeouts, etc.
//
// For details, see:
// https://pkg.go.dev/cloud.google.com/go/alloydbconn#Option
d, err := alloydbconn.NewDialer(ctx)
if err != nil {
noop := func() error { return nil }
return nil, noop, fmt.Errorf("failed to init Dialer: %v", err)
}
// The cleanup function will stop the dialer's background refresh
// goroutines. Call it when you're done with your database connection to
// avoid a goroutine leak.
cleanup := func() error { return d.Close() }
dsn := fmt.Sprintf(
// sslmode is disabled, because the Dialer will handle the SSL
// connection instead.
"user=%s password=%s dbname=%s sslmode=disable",
user, pass, dbname,
)
// Prefer pgxpool for applications.
// For more information, see:
// https://github.com/jackc/pgx/wiki/Getting-started-with-pgx#using-a-connection-pool
config, err := pgxpool.ParseConfig(dsn)
if err != nil {
return nil, cleanup, fmt.Errorf("failed to parse pgx config: %v", err)
}
// Tell pgx to use alloydbconn.Dialer to connect to the instance.
config.ConnConfig.DialFunc = func(ctx context.Context, _ string, _ string) (net.Conn, error) {
return d.Dial(ctx, instURI)
}
// Establish the connection.
pool, connErr := pgxpool.NewWithConfig(ctx, config)
if connErr != nil {
return nil, cleanup, fmt.Errorf("failed to connect: %s", connErr)
}
return pool, cleanup, nil
}
AlloyDB Auth Proxy
- IAMベースの接続認証
- TLS1.3接続を自動的に作成、使用、維持し、クライアントとサーバーのIDを検証してデータトラフィックを暗号化する
利用例
./alloydb-auth-proxy \
"projects/myproject/locations/us-central1/clusters/mycluster/instances/myprimary"
ソースコードを読んでわかったこと
ソースは下記になります
AuthProxyはConnector(Go)を利用している
-
AuthProxyにおいてDBへのアクセス周りはConnectorを利用しているので、DB接続周りの挙動はConnectorと同様になりそうです
-
利用箇所としては…
-
実際にAuthProxyで受けたリクエストをDBに渡しているのは下記になります
- Proxyコード
- クライアントかDB側からEOFかエラーが返却されるまで、データの橋渡しを続けるコードになってますね
AuthProxyでは通信を仲介するだけで、特別な処理はしていない
- クライアント(アプリ)からの通信をTCPかUnixDomainSocket経由で受信し、Connectorを呼び出しています
- その他には、デバッグ用のエンドポイントが存在しています
- 自分としてはAlloyDBにPublicIPを設定しなくても、トンネルを作って接続してくれないかと期待していましたが、そんなことはなさそうです
- よって、VPC外部から接続できるようにするには、PublicIPを設定するかPrivateIPにアクセスできる経路を準備する必要がありますね
- PrivateIPに対しVPC外部から接続する場合は下記ドキュメントが参考になります
- AuthProxyの使い所としては下記になると思います
- Connectorが対応していない言語で開発したい
- PGAdminなどのDBクライアントでアクセスしたい
ConnectorはIAM権限を利用して、DBのIPアドレス取得やクライアント証明書の発行をしている
- インスタンス識別子(例:projects/myproject/locations/us-central1/clusters/mycluster/instances/myprimary)からPublicIP・PrivateIPの取得をしています
- クライアント証明書の発行をしています
- 証明書発行コード
- 有効期限1時間のクライアント証明書を発行
- IAMにはalloydb.clusters.generateClientCertificateのpermissionsが必要
- AlloyDB側で
AlloyDB コネクタを通じて mTLS を適用(最も安全)
というオプションがあるが、これはこのクライアント証明書を利用した接続のみ受け付けるという意味だと思います- この証明書を発行できるIAMしかDBアクセスできないのでPublicIPでも安心して使えるという感じ
- これら情報はキャッシュされ、現在時刻から証明書期限までの半分の時間が過ぎたら再取得されてそうです
- 再取得時間の算出コード
- この取得方法はオプションで変更できて、クライアントからリクエストがきたとき(LazyRefreshCache)や静的設定(StaticConnectionInfoCache)が選択できそうです
余談
- AuthProxyを使いPublicIPでアクセスする際、オプションが必要なことに気がつかずにハマってました
./alloydb-auth-proxy \
"projects/myproject/locations/us-central1/clusters/mycluster/instances/myprimary" --public-ip