MongoDB AtlasのIPアドレスのホワイトリストにHerokuを追加するには?

2020-07-26

Udemy教材を参考にMERNスタックにて作った練習用のWebアプリケーションをHerokuにデプロイしました。

その後、当該URLにアクセスすると残念ながら次のエラーを目の当たりにしました。

以下はこの問題解決のメモになります。

Application error

原因

エラーメッセージに示された通りにログを見たら、次のエラーメッセージがありました。

2020-07-20T13:18:42.005447+00:00 app[web.1]: Could not connect to any servers in your MongoDB Atlas cluster. One common reason is that you're trying to access the database from an IP that isn't whitelisted. Make sure your current IP address is on your Atlas cluster's IP whitelist: https://docs.atlas.mongodb.com/security-whitelist/

Herokuにデプロイしたアプリケーションは、データをMongoDB Atlasに格納しています。

このエラーメッセージより、MongoDB Atlasが私のパソコン以外でアクセスしてよいIPアドレスのホワイトリストに登録していないことが原因であることはすぐわかりました。

対応方法

エラーメッセージに示されたURLを開いてみました。次のように書いています。

Atlas only allows client connections to the cluster from entries in the project’s whitelist.
Atlasは、プロジェクトのホワイトリストのエントリからクラスターへのクライアント接続のみを許可します。
Each entry is either a single IP address or a CIDR-notated range of addresses.
各エントリは、単一のIPアドレスまたはCIDR表記のアドレス範囲です。

CIDRとは”Classless Inter-Domain Routing”の略でサイダーと読むそうです(詳しくはWikipedia)。

MongoDB Atlasの解説”Whitelist Your Connection IP Address“に沿ってHerokuアプリケーションのIPアドレス又はCIDR表記のアドレス範囲を設定すればよいということになります。

安直な対応方法

  • MongoDB Atlasにて、あらゆるIPアドレスからのアクセスを可能にします。
  • これをおこなうに、CIDR表記にて 0.0.0.0/0 を設定します。
  • Network Access (https://cloud.mongodb.com/v2/◯◯◯#security/network/whitelist)より、[+ ADD IP ADDRESS]ボタンをクリックしておこないます。
  • 設定して反映されるまで、数分かかるようです。

HerokuアプリケーションのIPアドレスを設定する方法

HerokuのIPアドレスを知る方法は、”I need to add Heroku dynos to our allowlist – what are IP address ranges in use at Heroku?“にありました。

HerokuはAWS EC2インスタンスを基盤としているので、そのIP範囲のサブセットを使用しているとのことです。

以下のコマンドを実行します。

heroku regions --json

このコマンドで7つほど表示されました。以下は一個だけ示しています。

[
  {
    "country": "Ireland",
    "created_at": "2013-09-19T01:29:12Z",        
    "description": "Europe",
    "id": "ed30241c-ed8c-4bb6-9714-61953675d0b4",
    "locale": "Dublin",
    "name": "eu",
    "private_capable": false,
    "provider": {
      "name": "amazon-web-services",
      "region": "eu-west-1"
    },
    "updated_at": "2016-08-09T22:03:28Z"
  },
:
:

ここで provider.region が一致するもののidの値を取得すればよいです。

次のコマンド実行で、Region(ID?)が”us”と出ました。

> heroku info
=== pitang1965-contact-list
Auto Cert Mgmt: false
Dynos: web: 1
Git URL: https://git.heroku.com/pitang1965-contact-list.git
Owner: pitang1965@gmail.com
Region: us         <--- ここ
Repo Size: 451 KB
Slug Size: 60 MB
Stack: heroku-18
Web URL: https://pitang1965-contact-list.herokuapp.com/

先程のJSONはそれほど長くないので手作業、目視で大丈夫ですが、自動でやるならば、例えば次のJavaScriptファイルを作ります。

const targetJson = require('./regions.json');
const result = targetJson.filter((region) => region.provider.region.includes('us'))
  .map((region) => ({ 'id': region.id, 'region': region.provider.region }));
console.log(result);
> heroku regions --json > regions.json
> node filter-region.js ← 又はファイルに落とす。
[
{ id: '59accabd-516d-4f0e-83e6-6e3757701145', region: 'us-east-1' },
{ id: 'be90a7d3-570f-4ba7-bc42-f33c2cbece22', region: 'us-west-2' },
{ id: '3544427c-5b3b-4e1e-b01a-b66362573b26', region: 'us-east-1' }
]

regionとしてus-east-1とus-west-2があることがわかりました。

しかし、それに相当するIPアドレスはこちらから以下のプログラムで検索するとたぶん128個あるので諦めました。

const targetJson = require('./ip-ranges.json');
const util = require('util');
util.inspect.defaultOptions.maxArrayLength = null;
const result = targetJson.prefixes
  .filter((pre) => pre.service === 'EC2' && (pre.region === 'us-east-1'||pre.region === 'us-west-2'))
  .map((pre) => pre.ip_prefix);
console.log(result);