👻

【Go】pq: password authentication failed for user "xxx" にハマった話

に公開3

はじめに

Private IPを持ったCloud SQLに、Cloud Run Jobsから、Direct VPC Egressで接続しようとしていました。
しかし、接続がうまくいきません。

ユーザー名やパスワードはあっているはずなのに、以下のエラーが発生しました。

pq: password authentication failed for user "xxx"

なぜでしょうか?

修正前のコード

以下を参考に、TCPで接続する方法を選択しました。

https://cloud.google.com/sql/docs/postgres/connect-run?hl=ja#private-ip_1

package cloudsql

import (
	"database/sql"
	"fmt"
	"os"

	_ "github.com/lib/pq"
)

func connectTCPSocket() (*sql.DB, error) {
	dbURI := fmt.Sprintf("host=%s user=%s password=%s port=%s database=%s",
		os.Getenv("INSTANCE_HOST"), os.Getenv("DB_USER"), os.Getenv("DB_PASS"), os.Getenv("DB_PORT"), os.Getenv("DB_NAME"))

	dbPool, err := sql.Open("postgres", dbURI)
	if err != nil {
		return nil, fmt.Errorf("sql.Open: %w", err)
	}

	// ...

	if err := dbPool.Ping(); err != nil {
		return nil, err
	}

	return dbPool, nil
}

原因

DBのパスワードに、バックスラッシュ(\)を含んでいたことが原因でした。
公式ドキュメントにも、キーバリュー形式の接続文字列では、値中のシングルクォート(')とバックスラッシュ(\)を バックスラッシュでエスケープする必要があるとあります

To write an empty value, or a value containing spaces, surround it with single quotes, for example keyword = 'a value'. Single quotes and backslashes within a value must be escaped with a backslash, i.e., ' and \. [1]

また、URI 形式の接続文字列では、スキームやパス、パラメータ部に “特別な意味を持つ記号” を含む場合は パーセントエンコーディング が必要です。

The connection URI needs to be encoded with percent-encoding if it includes symbols with special meaning in any of its parts. [2]

解決

net/urlを使って、URI 形式の接続文字列を使用することにしました。
url.UserPasswordを使用して、ユーザー情報をパーセントエンコードします。

package cloudsql

import (
	"database/sql"
	"fmt"
	"net/url"
	"os"

	_ "github.com/lib/pq"
)

func connectTCPSocket() (*sql.DB, error) {
	u := &url.URL{
		Scheme: "postgres",
		User:   url.UserPassword(os.Getenv("DB_USER"), os.Getenv("DB_PASS")),
		Host:   fmt.Sprintf("%s:%s", os.Getenv("INSTANCE_HOST"), os.Getenv("DB_PORT")),
		Path:   os.Getenv("DB_NAME"),
	}

	dbPool, err := sql.Open("postgres", u.String())
	if err != nil {
		return nil, fmt.Errorf("sql.Open: %w", err)
	}

	if err := dbPool.Ping(); err != nil {
		return nil, err
	}

	// ...

	return dbPool, nil
}

最後に

実は、今回のエラーに遭遇するのは、2回目でした。
前回遭遇したのは、社会人になりたての頃でした。
当時は、自分だけのDBで作業していたので、DBのパスワードを変更して、根本解決はしませんでした。
しかし今回は、一次情報から問題の原因を見つけて、アウトプットまでしたので、我ながら成長が感じられて、なんだか感慨深いです。

脚注
  1. https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING ↩︎

  2. https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS ↩︎

Discussion