Node.js アプリから Heroku Postgres に接続できなくなったので SSL 通信設定を直す
拙作の Neo's Hatebu は、Heroku 上で稼動させている Angular + Express.js アプリだ。データ永続化に Heroku Postgres を使用しており、Express.js からは Sequelize という O/R マッパーを利用して接続している。
2021-03-17 頃、いつものようにこのアプリにログインしようとすると、次のようなエラーメッセージが表示されてしまった。
{
"headers": {
"normalizedNames": {},
"lazyUpdate": null
},
"status": 401,
"statusText": "Unauthorized",
"url": "https://neos-hatebu.herokuapp.com/login",
"ok": false,
"name": "HttpErrorResponse",
"message": "Http failure response for https://neos-hatebu.herokuapp.com/login: 401 Unauthorized",
"error": "Unauthorized"
}
401 Unauthorized
とな。
Heroku 管理画面の「View logs」で動作中のアプリログを見てみると、no pg_hba.conf entry for host ...
といったエラーログが見えた。
調べてみると以下に当たった。
- 参考 : Seeing "FATAL: no pg_hba.conf entry" errors in Postgres - Heroku Help
-
The authentication failed because the connection didn't use SSL encryption: (
SSL off
). ]All Heroku Postgres production databases require using SSL connections](https://devcenter.heroku.com/articles/heroku-postgresql#heroku-postgres-ssl) to ensure that communications between applications and the database remain secure. If your client is not using SSL to connect to your database, you would see these errors even if you're using the right credentials to connect to it.
-
- 参考 : Heroku Postgres | Heroku Dev Center
公式のガイドでは次のようなコードを載せている。
const { Client } = require('pg');
const client = new Client({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false
}
});
ssl.rejectUnauthorized
とかいうプロパティを付けている。コレを Sequelize でも渡してやれば良いのかな?Sequelize は内部的に pg
パッケージを使っているので、何らか渡し方がありそうだ。
調べてみると、あった。
- 参考 : Node.js, PostgreSQL error: no pg_hba.conf entry for host - Stack Overflow
Include in config when initialize
new Sequelize(...)
const sequelize = new Sequelize({
database: "DB",
username: "root",
password: "pass",
host: "localhost",
port: 5432,
dialect: "postgres",
dialectOptions: {
ssl: {
require: true, // This will help you. But you will see nwe error
rejectUnauthorized: false // This line will fix new error
}
},
});
Sequelize の dialectOptions
という隠しオプションを使うと、裏にいる pg
パッケージに直接このオプションを渡せるようだ。ssl.rejectUnauthorized
を false
にする他、ssl.require: true
を渡すことで確実に SSL 通信にするみたい。
そういえば、環境変数 PGSSLMODE
は既に allow
にしていて、コレは require
にしなくても大丈夫だった。文献には no-verify
を使う、みたいな記事もあったんだけど、とりあえず allow
のままにした。
…ということで、自分のアプリ内だと以下のコード修正のみで対応が完了した。
const Sequelize = require('sequelize');
const connectionString = process.env.DATABASE_URL;
const sequelize = new Sequelize(connectionString, {
timezone: '+09:00', // JST タイムゾーン : Sequelize で SELECT すると全て UTC の ISO 形式になっており DB 上の記録と異なる
logging: false, // ログ出力
// SSL 接続のため指定する (↓ 以下を追加した)
dialect: 'postgres',
dialectOptions: {
ssl: true,
rejectUnauthorized: false
}
});
既存コードからの変更点は dialect
と dialectOptions
プロパティを追加したのみだ。ローカルで Express.js サーバを動かして Heroku Postgres に接続することも出来たし (別途 CORS 設定のみしておいた)、このコードを Heroku にデプロイすることで、正常に Heroku Postgres に接続できるようになった。
なお、接続時に次のようなワーニングメッセージが出力されるが、無視している。rejectUnauthorized: false
にしてるじゃん…?w
(node:23) DeprecationWarning: Implicit disabling of certificate verification is deprecated and will be removed in pg 8. Specify `rejectUnauthorized: true` to require a valid CA or `rejectUnauthorized: false` to explicitly opt out of MITM protection.
とりあえず問題は解消したんだけど、どうして数年間上手く動いていたのに、今回突然こんなことになったのかな?と思って、Heroku の Change Log を追ってみた。
- 参考 : All Heroku Postgres client connections require SSL | Heroku Dev Center
-
Change effective on 23 February 2021
-
2月23日に変更が反映されていくモノ、として Heroku Postgres への SSL 接続が必須になったようだが、自分が利用している Free プランは、3月17日までこの変更が反映されていなかったのか、のらりくらりとやり過ごせていたようだ。
以上。エンジニアだけど、SSL めんどくせーって思いしかない。w