はじめに
AWS Systems Manager のポートフォワーディングで、ローカルの 5432 を開発環境の RDS(PostgreSQL)に転送して作業していたときの話です。
接続情報は正しいはずなのに認証エラーになる、という状況にハマりました。原因は、ローカルPCの PostgreSQL が 5432 を先に占有していて、SSM トンネルがバインドできていなかったことでした。
同じ踏み方をする人がいそうなので残しておきます。
問題
以下のようにトンネルを張ります。
aws ssm start-session --target i-0xxxxxxxxxxxxxxxx `
--document-name AWS-StartPortForwardingSessionToRemoteHost `
--parameters "portNumber=5432,localPortNumber=5432,host=db.example.cloud.local" `
--profile MyProfile
その上で localhost:5432 に接続すると、認証エラーになります。
28P01: ユーザー"appuser"のパスワード認証に失敗しました
Severity: FATAL
SqlState: 28P01
File: auth.c
Line: 326
Routine: auth_failed
28P01 は PostgreSQL のエラーコードで、パスワード認証の失敗を意味します。
しかし、ユーザー名もパスワードも間違っていません。Secrets Manager(AWS の機密情報管理サービス)に保存されている値と照合しても完全に一致していました。
aws secretsmanager get-secret-value --secret-id /dev-app/db/app-common/authentication `
--profile MyProfile --query "SecretString" --output text
{"password":"xxxxxxxxxxxxxxxx","username":"appuser"}
RDS のステータスも AWS コンソール上で「利用可能」。
そもそも 28P01 は、PostgreSQL サーバー自身が認証処理を行った上で返すエラーです。サーバーが停止していれば認証まで到達せず、接続タイムアウトや接続拒否になるはずなので、「どこかの PostgreSQL は確かに応答している」ことになります。
引っかかったのはエラーメッセージの言語
ここでヒントになったのが、エラーメッセージが日本語だったことです。
RDS は既定でメッセージが英語で返ります。日本語で返ってくるということは、日本語ロケールでインストールされた別の PostgreSQL が応答しているのではないか、と疑いました。
解決方法
5432 を Listen しているプロセスを確認します。
Get-NetTCPConnection -LocalPort 5432 -State Listen |
Select-Object LocalAddress,LocalPort,OwningProcess
LocalAddress LocalPort OwningProcess
------------ --------- -------------
:: 5432 8176
0.0.0.0 5432 8176
PID から実体を辿ります。
Get-Process -Id 8176
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
420 16 7796 23716 8176 0 postgres
犯人は postgres。ローカルにインストールされていた PostgreSQL が 5432 を掴んでおり、SSM トンネルはこのポートにバインドできていませんでした。
その結果、localhost:5432 への接続はすべてローカルの PostgreSQL に向かい、そこには該当ユーザーが存在しない(またはパスワードが異なる)ため 28P01 を返していた、というわけです。
対処A: ローカルの PostgreSQL を停止する
services.msc を開き、postgresql-x64-16 サービスを停止します。スタートアップの種類が「自動」だとPC再起動のたびに復活するので、「手動」に変えておくと安心です。
PowerShell(管理者権限)からでも停止できます。
Get-Service | Where-Object { $_.Name -like "*postgre*" }
Stop-Service -Name postgresql-x64-16
停止後にトンネルを張り直せば、5432 で RDS に接続できます。
対処B: 別ポートでトンネルを張る
ローカルの PostgreSQL を止めたくない場合は、競合しないポートを使います。
aws ssm start-session --target i-0xxxxxxxxxxxxxxxx `
--document-name AWS-StartPortForwardingSessionToRemoteHost `
--parameters "portNumber=5432,localPortNumber=15432,host=db.example.cloud.local" `
--profile MyProfile
接続側も 15432 に向けます。
psql "postgresql://appuser@localhost:15432/app_common"
こちらのほうが、ローカル環境を壊さず切り分けもできるので個人的にはおすすめです。
正常な状態を確認する
ローカルの PostgreSQL を停止してトンネルを張り直したあと、もう一度 5432 の持ち主を確認します。
Get-NetTCPConnection -LocalPort 5432 -State Listen |
Select-Object LocalAddress,LocalPort,OwningProcess
LocalAddress LocalPort OwningProcess
------------ --------- -------------
127.0.0.1 5432 38344
Get-Process -Id 38344
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
358 23 56464 24932 0.27 38344 2 session-manager-plugin
session-manager-plugin が 5432 を掴んでいれば、トンネルが正しくバインドできている状態です。これで RDS に接続できるようになりました。
ちなみに LocalAddress の違いも見分けるポイントになります。
| 状態 | LocalAddress |
|---|---|
| ローカルの PostgreSQL |
0.0.0.0 と ::
|
| SSM トンネル |
127.0.0.1 のみ |
SSM のポートフォワーディングはループバックアドレスにのみバインドするため、0.0.0.0 や :: が並んでいたらトンネル以外のプロセスを疑ってよさそうです。
おわりに
厄介なのは、SSM トンネルがバインドに失敗しても接続自体は成立してしまう点です。エラーで止まってくれれば気づけるのですが、静かに別の DB に繋がるため「設定は合っているのに認証だけ通らない」という紛らわしい状態になります。
今回の教訓は以下です。
-
28P01はサーバーに到達している証拠。ネットワーク経路や RDS の死活を疑う前に、どのサーバーが応答しているかを考える - エラーメッセージの言語やフォーマットは有力なヒントになる
- 「設定は正しいのに繋がらない」ときは、まず
Get-NetTCPConnectionでポートの持ち主を確認する
そもそもローカルの開発用ポートと、トンネル用のポートは最初から分けておくのが無難かもしれません。
参考
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼
https://projisou.jp