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 ... といったエラーログが見えた。

調べてみると以下に当たった。

公式のガイドでは次のようなコードを載せている。

const { Client } = require('pg');

const client = new Client({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: false
  }
});

ssl.rejectUnauthorized とかいうプロパティを付けている。コレを Sequelize でも渡してやれば良いのかな?Sequelize は内部的に pg パッケージを使っているので、何らか渡し方がありそうだ。

調べてみると、あった。

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.rejectUnauthorizedfalse にする他、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
  }
});

既存コードからの変更点は dialectdialectOptions プロパティを追加したのみだ。ローカルで 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 を追ってみた。

2月23日に変更が反映されていくモノ、として Heroku Postgres への SSL 接続が必須になったようだが、自分が利用している Free プランは、3月17日までこの変更が反映されていなかったのか、のらりくらりとやり過ごせていたようだ。

以上。エンジニアだけど、SSL めんどくせーって思いしかない。w