FirebaseHosting で IP アクセス制限をする
こんにちは、Cloud Support の Rnrn です。FirebaseHosting は 2021 年 4 月現在 IP アドレスによるアクセス制限をサポートしていませんが、よくお問い合わせを頂きます。そこで、今回は公式ドキュメントで触れられているミドルウェアを使った IP によるアクセス管理の手順をご紹介したいと思います。
FirebaseHosting では通常静的コンテンツの配信のみをサポートしていますが、実は Cloud Functions for Firebase と組み合わせることで動的なコンテンツの配信などより多彩な動きができるようになります。今からご紹介する方法では、この仕組みを利用して IP に応じてアクセスを管理するミドルウェアを挟みます。1つ注意点として、現在 FirebaseHosting と統合するには function は us-central1 にある必要があります。
Hostingの準備
今回は FirebaseHosting でデプロイしたサイトの /controlled パスだけに IP 制限をかけていきたいと思います。まずは FirebaseHosting にデプロイするコードを用意し、Hosting を初期化 します。初期化後にはローカルでテストを行い、正しく Hosting が構成できているか確認しておくと安心です。
Functionの準備
Hosting の準備ができたら、次にアクセス制御を行う Function を準備しましょう。先程と同様に、まずは Function の初期化をします。初期化が終わると functions ディレクトリが作成されるので、ディレクトリに移動して生成された index.js (または index.ts )を編集していきます。
今回は Express.js のミドルウェアを使ってアクセス制御をおこなうので、まずは Express.js を導入します。主な手順はリンク先と同じなのでカブる部分もありますが、ここでは今回の設定に合わせたステップを1つずつ見ていきます。まずはインストールを行います。
npm install express --save
次に、Express.js のセットアップとエンドポイントの追加をします。今回は /controlled パスのみを Function にリライトするので、このパスに対応するハンドラーを追加します(ハンドラーのレスポンス内容はリンク先のサンプルそのままです)。最後の行で exports の後につけた名前がそのまま Function の名前としてコンソールに表示されたり、このあとの設定で使われたりするのでわかりやすい名前をつけておきましょう。
const functions = require('firebase-functions');
const express = require('express');
const app = express();app.get('/controlled', (req, res) => {
const date = new Date();
const hours = (date.getHours() % 12) + 1; // London is UTC + 1hr;
res.send(`
<!doctype html>
<head>
<title>Time</title>
<link rel="stylesheet" href="/style.css">
<script src="/script.js"></script>
</head>
<body>
<p>In London, the clock strikes:
<span id="bongs">${'BONG '.repeat(hours)}</span></p>
<button onClick="refresh(this)">Refresh</button>
</body>
</html>`);
});exports.middleware = functions.https.onRequest(app);
Function と Hosting の接続
アクセス制御をかける前に一度リライトが期待したとおりに動くか確認したいので、次に Hosting からのリライトの設定を行います。今回は /controlled パスのみに適用するため、firebase.json に以下の記述を追加します。
"hosting": {
... // Add the "rewrites" attribute within "hosting"
"rewrites": [ {
"source": "/controlled",
"function": "middleware"
} ]
}
この段階でリンク先のドキュメントにもあるようにエミュレータを起動します。今回は http://localhost:5000 にホストされたので、まず http://localhost:5000 にアクセスして Hosting の index.html が表示されることを確認します。次に http://localhost:5000/controlled にアクセスして期待通り function からレスポンスが返ってくることを確認します。
ミドルウェアの追加
リライトが正しく動いていることが確認できたところで、次はいよいよアクセス制御ロジックの追加です。Firebase は内部で Fastly を使っていて、fastly-client-ip ヘッダーからクライアントの IP を取得することができます。npm 等で配信されている IP を取得する Express.js ミドルウェアを使うこともできますが、Hosting のリクエストをリライトしていることなどから正しく IP を取得できない場合があるので気をつけて下さい。また、なんとなく察しのつく方もいると思いますがエミュレーターでは IP の取得ができません。
...
const app = express();const allowlist = ['127.0.0.1'];app.use((req, res, next) => {
const clientIP = req.headers['fastly-client-ip'];
if(allowlist.includes(clientIP)){
next()
}else{
const err = new Error('Not Found')
err.status = 404
next(err)
}
});app.get('/', (req, res) => {
...
今回は IP が allowList にない場合には 404 ページを返すことにしたので、ミドルウェア内でエラーをスローしておいて、最後にエラーハンドラーを追加しました。このあたりの実装について Express.js に馴染みのない方はミドルウェアやエラー処理のドキュメントを参照することをおすすめします。
...
</html>`);
});app.use('/', express.static('/public'))app.use((err, req, res, next) => {
res.status(err.status || 500).sendFile(`${__dirname}/public/404.html`)
})exports.middleware = functions.https.onRequest(app);
デプロイ
これで準備が終わりました。エミュレータを起動して正しく 404 ページが返ってくることが確認できたら、Function と Hosting をまとめてデプロイします。最後にデプロイされた URL にアクセスして、指定した IP アドレスからのみ /controlled ハンドラーから返却されるレスポンスが正しく見られることを確認して完了です。お疲れさまでした🎉