クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【Express】HerokuのPostgreSQLのデータを表示する

Expressの続きです。前回はHerokuとGitHubを連携して自動デプロイしました。環境変数をGitHubのソースから除外しつつHerokuで環境変数の値を表示しました。今回はHerokuの標準DBであるPostgreSQLのデータを表示したく思います。DBの設定情報は同じく環境変数に設定します。それではいきましょう!

【目次】

※参考:前回記事
【Express】HerokuとGitHubを連携して自動デプロイ(環境変数は除外) - クモのようにコツコツと

※参考:Node.js / Expressを習得するためにやったことまとめ
qiita.com

前回のおさらい

HerokuとGitHubを連携して自動デプロイ。Herokuの環境変数をGitHubのソースから除外しつつ値(ケンシロウ)をHerokuの画面で表示。 f:id:idr_zz:20201006185852j:plain

今回はHerokuの標準DBであるPostgreSQLのデータを表示したい。PostgreSQLの設定情報もGitHubには含めたくないので環境変数に設定する。


HerokuのPostgreSQLをターミナルからCRUD操作した記事も参照。

※参考:【SQL】ターミナルからHerokuのPostgreSQLに接続する - クモのようにコツコツと

※参考:【SQL】ターミナルからHerokuのPostgreSQLにCRUDする - クモのようにコツコツと

ExpressにPostgreSQLを追加すると環境設定が自動設定される

HerokuのExpressアプリでPostgreSQLを使用する方法、こちらのチュートリアルを参考に進める。

※参考:Heroku スターターガイド (Node.js) | Heroku Dev Center


以前、こちらの記事でPostgreSQLをアプリに追加した。

下記のコマンド。

$ heroku addons:create heroku-postgresql:hobby-dev

※参考:【SQL】ターミナルからHerokuのPostgreSQLに接続する - クモのようにコツコツと

上記を実行すると環境変数の中にPostgreSQLの設定情報「DATABASE_URL」が自動的に設定されるらしい。

環境変数のPostgreSQL設定情報を確認

まずターミナルのcdコマンドでアプリのあるフォルダに移動

$ cd /(パス)/node-js-getting-started

環境変数を確認するコマンドはこちら。

$ heroku config

※参考:【Express】HerokuとGitHubを連携して自動デプロイ(環境変数は除外) - クモのようにコツコツと

結果

=== aqueous-mountain-80366 Config Vars
DATABASE_URL: postgres://(ユーザ名):(パスワード)@(ホスト名):(ポート番号)/(DB名)
NAME:         ケンシロウ
TIMES:        10

DATABASE_URLが追加されている!これがPostgreSQLの設定情報のようだ。

DATABASE_URLの値はすごく長いが、DBのSettingを見ると意味がわかる。

「Database Credentials」の「View Credentials…」を開くと… f:id:idr_zz:20201027140348j:plain

DBの各項目の値が表示されている。 f:id:idr_zz:20201027140707j:plain

この値とURLを照合すると下記のような並びになっているのがわかる。

DATABASE_URL: postgres://(ユーザ名):(パスワード)@(ホスト名):(ポート番号)/(DB名)

Herokuの環境変数をローカルの.envファイルに同期

ブラウザのHerokuの「Config Vars」を見るとDATABASE_URLが追加されている! f:id:idr_zz:20201027141129j:plain

しかし、ローカル環境の.envファイルを開くとDATABASE_URLはまだない。

# this file was created automatically by heroku-config

NAME="ケンシロウ"
TIMES="10"

リモートのHerokuの環境変数をローカルの「.env」ファイルにプルする。

$ heroku config:pull

※参考:【Express】heroku configでHerokuに環境変数を設定(.envファイルとHerokuの同期) - クモのようにコツコツと

結果、おっ、成功したっぽい♪

Successfully wrote config to ".env"!

「.env」ファイルを確認すると、DATABASE_URLが同期された!

# this file was created automatically by heroku-config

DATABASE_URL="postgres://(ユーザ名):(パスワード)@(ホスト名):(ポート番号)/(DB名)"
NAME="ケンシロウ"
TIMES="10"

pgモジュールをインストール

ExpressでPostgreSQLを利用するにはpgモジュールが必要なようだ。

node-postgres
Non-blocking PostgreSQL client for Node.js. Pure JavaScript and optional native libpq bindings.

node-postgres
Node.js用のノンブロッキングPostgreSQLクライアント。 純粋なJavaScriptとオプションのネイティブlibpqバインディング。

正式名は「node-postgres」かな。

※参考:pg - npm

pgモジュールをインストールする。

$ npm install pg

package.jsonを確認。追加された!

  "dependencies": {
    "ejs": "^2.7.4",
    "express": "^4.17.1",
    "pg": "^8.4.1"
  },

index.jsでpgモジュールを設定

まず冒頭でpgモジュールのインポートする。

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

変数poolPoolを起動。オプション設定も。

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: true
});
  • 変数poolPool()のインスタンス作成
  • Pool()の引数はオブジェクト(連想配列)でオプションを設定する
  • connectionStringキーの値はprocess.env.DATABASE_URL
  • sslキーの値はtrue

環境変数の読み込み方(process.env.HOGE)は以前と同じだ。

※参考:【Express】heroku configでHerokuに環境変数を設定(.envファイルとHerokuの同期) - クモのようにコツコツと

ExpressでPostgreSQLデータを処理

ExpressでPostgreSQLのデータを読んでブラウザに返す設定

.get('/db', async (req, res) => {
    try {
      const client = await pool.connect()
      const result = await client.query('SELECT * FROM member');
      const results = { 'results': (result) ? result.rows : null};
      res.render('pages/db', results );
      client.release();
    } catch (err) {
      console.error(err);
      res.send("Error " + err);
    }
  })
  • get()メソッドの第一引数はパスで/db
    第二引数はasync/await構文で無名関数を実行(引数はreq, res
  • 無名関数の処理はtry...catch文を実行
    tryでは変数clientpoolconnect()
    その後(await)、変数resultclientからSELECT文でテーブルmemberのデータを読み込む
    その後(await)、変数resultsで無名関数作成
    無名関数のresultsキーの値はresultがあればそれを、無ければrowsnullと入れる
    res.render()でレンダリング実行。引数でpages/dbresultsを送る
    client.release()を実行
  • catch()の引数はerr
    コンソールにエラー内容errを表示
    res.send()で「Errorエラー内容err」というレスポンスを返す

基本的にはチュートリアル通りだがテーブル名のみmemberに変えている。以前下記の記事で作ったテーブル。

※参考:【SQL】ターミナルからHerokuのPostgreSQLにCRUDする - クモのようにコツコツと

async/await構文はthen()のように非同期で処理の順番をコントロールする構文。

※参考:【JS】async/await構文で書いたFetch APIからJSONデータを読み込む - クモのようにコツコツと

try...catch文は例外処理でtryの処理が失敗した時にcatchを実行する。

※参考:try...catch 文 - JavaScript | MDN

GitHubにコミットをプッシュ→Herokuにデプロイ

GitHubにコミットをプッシュする。下記の3つのコマンドを実行

$ git add .
$ git commit -m "PostgreSQLと接続"
$ git push -u origin main

GitHubに反映された! f:id:idr_zz:20201027144545j:plain

※参考:PostgreSQLと接続 · ryo-i/Express-Heroku-Test@3439663 · GitHub

GitHubとHerokuは連携しているため、Herokuの方にも修正内容がデプロイされる。

※参考:【Express】HerokuとGitHubを連携して自動デプロイ(環境変数は除外) - クモのようにコツコツと

SSL接続エラー発生!

Herokuアプリをブラウザで表示してみる。先ほどExpressで設定したように、トップページではなく下層パス「/db」を開く。

ありゃ?エラーになっているぞ。 f:id:idr_zz:20201027145131j:plain 「Error: self signed certificate(エラー:自己署名証明書)」と。

ローカルでも起動してみる。ポート番号は5000で下層パス「/db」

$ heroku local web

ローカルも同じエラーだ。 f:id:idr_zz:20201027145232j:plain

どうもエラーメッセージの意味を調べるとSSL通信のエラーのようだ。

SSL通信というとURLを「https」にしてサーバとのデータ通信を暗号化するイメージ。

※参考:SSL/TLSってなんだろう?|SSL/TLS-総合解説サイト

HerokuのPostgreSQLとの通信もこれが必要で、それがエラーになっていると。

SSL設定を変更(rejectUnauthorized: false )

調べてみて多く見られた方法は下記だった。

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  // ssl: true,
  ssl: { rejectUnauthorized: false }
});

sslの設定をtrueからrejectUnauthorized: falseに変更。

「reject Unauthorized(不正を拒否する)」をfalse(無効)にするんだ…不正を拒否しない。。気になる項目だ(次項で掘り下げます)

※参考:Herokuアップデートを実行後、node-postgres(pg)のバージョンが上がりWEBアプリ(Express)が死んだ話 | 神戸システムデザイン

※参考:javascript - 'Self signed certificate' error during query the Heroku hosted Postgres database from the Node.js application - Stack Overflow


まずはローカルでHerokuを起動してみる。

$ heroku local web

おぉ、表示された! f:id:idr_zz:20201027153225j:plain

GitHubにコミットをプッシュ

$ git add .
$ git commit -m "SSL設定変更テスト"
$ git push -u origin main

ブラウザのHerokuでも表示された! f:id:idr_zz:20201027153345j:plain

※参考:ssl設定変更テスト · ryo-i/Express-Heroku-Test@1010c08 · GitHub


ちなみにこの画面はEJSテンプレートを表示している。

先ほどindex.jsのres.render()で指定しているpages/db.ejs

res.render('pages/db', results );

この部分でresultsidnameを読み込んでいる!

<ul>
    <% results.forEach(function(r) { %>
        <li><%= r.id %> - <%= r.name %></li>
    <% }); %>
</ul>

※参考:Express-Heroku-Test/db.ejs at 34396634017ae0e345b144dca2679134c20e7429 · ryo-i/Express-Heroku-Test · GitHub

rejectUnauthorized: falseについて調べたら怖いこと書いてある

rejectUnauthorized: false(「不正を拒否する」を無効化)でページが表示されたのはいいのが、これってSSL通信設定trueを無効にてるんじゃ??

気になるので調べてみる。

TLS what exactly does 'rejectUnauthorized' mean for me?

TLS「rejectUnauthorized」は私にとって正確にはどういう意味ですか?

By setting rejectUnauthorized: false, you're saying "I don't care if I can't verify the server's identity." Obviously this is not a good solution as it leaves you vulnerable to MITM attacks.

rejectUnauthorized: false を設定すると、「サーバーのIDを確認できなくてもかまいません」と表示されます。明らかに、これはMITM攻撃に対して脆弱なままになるため、適切なソリューションではありません。

怖いこと書いてるなー。。

※参考:https://stackoverrun.com/ja/q/8774469


「MITM攻撃」とは中間者攻撃(Man In The Middle Attack)のこと。

サイバー攻撃の中でも、中間者攻撃とは二者間の通信を特別なソフトウェアなどの不正な手段を用いて傍受、盗聴して内容を取得するといったものです。

※参考:中間者攻撃(Man In The Middle Attack)とは?仕組みや危険性、対策について徹底解説

怖いぞー。何とかならないか。

SSL設定するための試行錯誤(結果はうまくいかず)

SSL設定の成功法はHerokuを有料プランにすることで実現できるようだ。

Heroku「Settings」の「SSL Certificates」を見るとこうある。 f:id:idr_zz:20201029044052j:plain

Configure SSL
Upgrade to paid dynos to configure Heroku SSL

SSLを構成する
有料dynoにアップグレードして、HerokuSSLを構成します

フリープランでは「Let's Encrypt」や「Cloudflare」などの外部サービスと連携する方法があるらしい。

※参考:HerokuアプリにSSLを設定する方法 | Casual Developers Note

フリープランかつ、Heroku内で完結する方法がないか、もう少し調べる。


環境変数を追加する方法

Heroku 上の環境変数に PGSSLMODE=require もしくは PGSSLMODE=allow

※参考:「pg」パッケージを使ってローカルの PostgreSQL や Heroku Postgres に接続する - Corredor

index.jsのSSL設定を元に戻す

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: true,
  // ssl: { rejectUnauthorized: false }
});

環境変数にPGSSLMODEを設定してみる

$ heroku config:set PGSSLMODE=allow

結果、追加された! f:id:idr_zz:20201027170934j:plain

ローカルの.envファイルにもプル

heroku config:pull

結果、追加された!

# this file was created automatically by heroku-config

DATABASE_URL="postgres://(ユーザ名):(パスワード)@(ホスト名):(ポート番号)/(DB名)"
NAME="ケンシロウ"
PGSSLMODE="allow"
TIMES="10"

しかし、Herokuの画面の表示は 「Error Error: self signed certificate」のまま…


DBのパスにパラメータ?sslmode=requireを追加する方法

ほとんどのクライアントはデフォルトで SSL で接続しますが、Postgres 接続で ​sslmode=require​ パラメータを設定する必要がある場合もあります。環境設定を直接編集するのではなく、コードにこのパラメータを追加してください。特に ​Java​ クライアントまたは ​Node.js​ クライアントを使用している場合は、SSL の使用を強制していることを確認してください。

※参考:Heroku Postgres | Heroku Dev Center

環境変数DATABASE_URLの値にパラメータを追加

$ heroku config:set DATABASE_URL=postgres://(ユーザ名):(パスワード)@(ホスト名):(ポート番号)/(DB名)?sslmode=require

結果、エラーで追加できない。

Cannot overwrite attachment values DATABASE_URL.

ブラウザでやってみる。「Settings」の「Config Vars」で「DATABASE_URL」の鉛筆アイコンを押すと編集のポップアップが出る。 f:id:idr_zz:20201027172441j:plain

?sslmode=requireを追記してみるが… f:id:idr_zz:20201027172452j:plain

変更できない。。 f:id:idr_zz:20201027172532j:plain

どうやら「DATABASE_URL」は上書きできない仕様のようだ。

※参考:HerokuでDATABASE_URLを変更しようとしたらCannot overwrite attachment values DATABASE_URLと言われる


証明書および中間証明書の手動アップロードする方法

​certs:add​ コマンドで、証明書、中間証明書のバンドル、およびプライベートキーを追加します。

※参考:Heroku SSL | Heroku Dev Center

下記のコマンド

$ heroku certs:add server.crt server.key

結果、そんなファイルはないよ、と。。

ENOENT: ENOENT: no such file or directory, open 'server.key'

SSL設定に「sslmode: 'require'」を追加

おそらくフリープランで一番最善な方法はsslのオプションにsslmode: 'require'を追加することと思われる。

Is it safe to set rejectUnauthorized to false when using Heroku's Postgres database?

HerokuのPostgresデータベースを使用するときにrejectUnauthorizedをfalseに設定しても安全ですか?

My understanding is that in this case, the best you can do is:

  • Your connection is encrypted and you are protected from eavesdropping
  • Not protected against Man-In-The-Middle (MITM) attacks

This is the highest level without certificate validation, according to the table here:

私の理解では、この場合、あなたができる最善のことは次のとおりです。

* 接続は暗号化されており、盗聴から保護されています
* Man-In-The-Middle(MITM)攻撃から保護されていません

ここの表によると、これは証明書の検証なしの最高レベルです。

f:id:idr_zz:20201027173432p:plain

※参考:tls - Is it safe to set rejectUnauthorized to false when using Heroku's Postgres database? - Information Security Stack Exchange

requireモードの詳細についてはこちら。

※参考:PostgreSQL: Documentation: 13: 33.18. SSL Support


sslのオプションにsslmode: 'require'を追加してみる。

  ssl: { 
    sslmode: 'require',
    rejectUnauthorized: false
  }

※参考:SSL設定変更 · ryo-i/Express-Heroku-Test@42d9b23 · GitHub

GitHubにコミットをプッシュ

$ git add .
$ git commit -m "SSL設定変更"
$ git push -u origin main

ローカル環境のHeroku、表示が復活! f:id:idr_zz:20201027153225j:plain

ブラウザのHeroku、表示が復活! f:id:idr_zz:20201027153345j:plain

Herokuアプリ

※参考:Node.js Getting Started on Heroku

GitHub(PostgreSQLの設定情報は含まれていない!)

※参考:GitHub - ryo-i/Express-Heroku-Test at 42d9b23ca7d5dcf02c3003be3d8b2ba120272456

最後に

ということでHerokuのExpressアプリとPostgreSQLを連携できましたー。GitHubにはPostgreSQLを含まずにアプリの画面にDBのデータを表示することができました。

SSLの設定がフリープランだとうまくできなかったのが残念でしたが、SSLやセキュリティのことを調べる機会にはなりました。

今回はHerokuのチュートリアルとほぼ同じ内容でしたが、次回からは独自のコードでブラウザのフォームと連携したCRUD処理をやってみたく思います。

それではまた!


※参考:Node.js / Expressを習得するためにやったことまとめ
qiita.com