0
0

Firebase FunctionsなどをCloud Load Balancingの配下に設定する

Posted at

はじめに

Google Cloud Platformを使い始めました。

備忘録として、作業の流れを記録していきます。

フロントエンドWebサイト

まずは何でもいいのでフロントエンドのWebサイトを作ります。

Vueで作ります。

$ npm create vue@latest
$ cd <your-project>
$ npm i
$ npm run dev

image.png

Firebase Hostingにデプロイ

以下のGitHub Actionsでデプロイします。

.github/workflows/deploy-firebase.yml
name: Deploy to Firebase

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm install

      - name: Build project
        run: npm run build

      - name: Install Firebase CLI
        run: npm install -g firebase-tools

      - name: Deploy to Firebase Hosting
        run: firebase deploy --only hosting --token ${{ secrets.FIREBASE_TOKEN }}

…Node.js v22だと npm install -g firebase-toolsがうまくいかんかった

image.png

Firebase Functionsを作る

Functionsを作ります。

$ firebase init functions

TypeScriptを選びます。

できたら、以下のファイルを編集したり追加したりします。

functions/src/index.ts
export * from './hello';
export * from './foo';

/foo/barのルートを作ります。{ "message": "foo, bar!"}を返します。

functions/src/foo/index.ts
const functions = require('firebase-functions');
const express = require('express');

const app = express();
const router = express.Router();

// @ts-ignore
router.get('/bar', async (req, res, next) => {
    console.log(req.headers);
    res.send({
        message: 'foo, bar!'
    });
});

app.use('/', router);

export const foo = functions
    .region('asia-northeast1')
    .runWith({ memory: '128MB',  timeoutSeconds: 2 })
    .https
    .onRequest(app);

こちらは/hello/worldのルートを作ります。

IPアドレスチェックのためのミドルウェアが入っています。あとで説明します。

functions/src/hello/index.ts
const functions = require('firebase-functions');
const express = require('express');

const app = express();
const router = express.Router();

// @ts-ignore
app.use((req, res, next) => {
    const forwardedFor = req.headers['x-forwarded-for'];
    if (!forwardedFor) {
        res.status(403).send('Forbidden');
        return;
    }

    const forwardedIps = forwardedFor.split(',');

    // x-forwarded-forは偽装が可能
    // 偽装しない場合、<your-ip>,<load-balancer-ip>の順になる
    // <your-ip>,<load-balancer-ip>と偽装した場合、<your-ip>,<load-balancer-ip>,<your-ip>の順になった
    // LoadBalancerは追記するが、Firebase Functionsは追記しない?
    // <load-balancer-ip>と偽装した場合、<load-balancer-ip>,<your-ip>の順になる
    // なので、最後の要素が<load-balancer-ip>であるかどうかで判定する
    if (forwardedIps.length >= 2 && forwardedIps[forwardedIps.length - 1].trim() === '34.54.110.179') {
        next();
    } else {
        res.status(403).send('Forbidden');
    }
});

// @ts-ignore
router.get('/world', async (req, res, next) => {
    console.log(req.headers);
    res.send({
        message: 'Hello, World!'
    });
});

app.use('/', router);

export const hello = functions
    .region('asia-northeast1')
    .runWith({ memory: '128MB',  timeoutSeconds: 2 })
    .https
    .onRequest(app);

Firebase Functionsをデプロイする

GitHub Actionsを修正

.github/workflows/deploy-firebase.yml
name: Deploy to Firebase

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm install

+     - name: Install dependencies ./functions
+       run: npm install
+       working-directory: functions
+
      - name: Build project
        run: npm run build

      - name: Install Firebase CLI
        run: npm install -g firebase-tools

      - name: Deploy to Firebase Hosting
-       run: firebase deploy --only hosting --token ${{ secrets.FIREBASE_TOKEN }}
+       run: firebase deploy --only hosting,functions --token ${{ secrets.FIREBASE_TOKEN }}

デプロイできました。

image.png

/foo/bar

image.png

/hello/worldも機能していることはわかります。

image.png

Cloud Load Balancingを作成

ロードバランサを作成していきます。

image.png

ここは全部左側を選択。

image.png

フロントエンドは、HTTPSで。

image.png

image.png

バックエンドサービスを作成する

image.png

バックエンドタイプに、サーバーレスネットワークエンドポイントグループを指定。新しいものを作成する。

image.png

ここで、Firebase Functionsの関数が選択できる。

image.png

同じ要領で、あるやつすべて作る。今回は2つ。

image.png

ルーティングはいったんこのままで。firebase-helloのバックエンドサービスは、後で。

image.png

作成します。

DNSの設定

ロードバランサができたら、IPアドレスがわかるので、これを先ほど指定したドメインに設定します。

image.png

証明書の状態をみます。

まだなので、待ちます。

image.png

完了しました。

image.png

アクセスしてみるが、思った通りではない。

/foo/barにアクセスしてみます。うまくいきません。

image.png

/barならうまくいきます。

image.png

今回、すべてのルートをFirebase Functionsのfooへ転送するようにしました。fooからすれば、ルートとして持っているのは/barということなのでしょう。

ルーティングに、/hello/worldのFunctionsを追加

先ほどの学びを生かして、/worldをFunctionsのworldに転送するようにします。

image.png

/worldでいけました。

image.png

Firebase Hostingへのルートを追加したいがうまくいかんかった

まずは、ロードバランサの編集から、バックエンドサービスを追加します。バックエンドタイプに、インターネットネットワークエンドポイントグループを指定。そして、新しいインターネットネットワークエンドポイントグループを作成します。

image.png

で、完全修飾ドメイン名に、Firebase HostingのURLを入力します。

image.png

これで、Firebase Hostingのバックエンドサービスが追加され、3つになりました。

image.png

次に、ロードバランサのルーティングを設定します。デフォルトのルートをFirebase Hostingにします。

image.png

これで設定は完了です(たぶん)。アクセスしてみます。

image.png

うまくいきません。Firebaseにアクセスされているのはわかりましたが。

ちょっとこれはどうすればいいのかはわかりませんでした。

Cloud Storageを使えということか。

Firebase FunctionsにIPアドレス制限を入れたい

さて、ロードバランサの背後にFirebase Functionsを置いたので、Firebase FunctionsのURLへ直接アクセスすることを禁止したいです。

そのような設定はなさそうなので、コードで書いていきます。それを書いたのが、/hello/worldです。再掲します。

functions/src/hello/index.ts
const functions = require('firebase-functions');
const express = require('express');

const app = express();
const router = express.Router();

// @ts-ignore
app.use((req, res, next) => {
    const forwardedFor = req.headers['x-forwarded-for'];
    if (!forwardedFor) {
        res.status(403).send('Forbidden');
        return;
    }

    const forwardedIps = forwardedFor.split(',');

    // x-forwarded-forは偽装が可能
    // 偽装しない場合、<your-ip>,<load-balancer-ip>の順になる
    // <your-ip>,<load-balancer-ip>と偽装した場合、<your-ip>,<load-balancer-ip>,<your-ip>の順になった
    // LoadBalancerは追記するが、Firebase Functionsは追記しない?
    // <load-balancer-ip>と偽装した場合、<load-balancer-ip>,<your-ip>の順になる
    // なので、最後の要素が<load-balancer-ip>であるかどうかで判定する
    if (forwardedIps.length >= 2 && forwardedIps[forwardedIps.length - 1].trim() === '34.54.110.179') {
        next();
    } else {
        res.status(403).send('Forbidden');
    }
});

// @ts-ignore
router.get('/world', async (req, res, next) => {
    console.log(req.headers);
    res.send({
        message: 'Hello, World!'
    });
});

app.use('/', router);

export const hello = functions
    .region('asia-northeast1')
    .runWith({ memory: '128MB',  timeoutSeconds: 2 })
    .https
    .onRequest(app);

基本的には、X-Forwarded-Forヘッダを見ることになるかと思います。しかし、このヘッダーはアクセスする人が自由に付加することができます。いわゆる偽装が可能です。

軽く偽装(アクセス元で付与)をしてみる実験をしてみた結果、Firebase Functions側では、X-Forwarded-Forヘッダは以下のような規則で取得できました。

  • 何も付与しない場合、<your-ip>,<load-balancer-ip>の順になる
  • <your-ip>,<load-balancer-ip>と偽装した場合、<your-ip>,<load-balancer-ip>,<your-ip>の順になった
  • <load-balancer-ip>と偽装した場合、<load-balancer-ip>,<your-ip>の順になる

というわけで、これだけを見た感じですが、最後の要素が<load-balancer-ip>であれば、処理を続行する、というルールにすればいいことがわかります。

それをコードであらわしたのが、以下になります。(今回はロードバランサのIPが34.54.110.179でした。)

app.use((req, res, next) => {
    const forwardedFor = req.headers['x-forwarded-for'];
    if (!forwardedFor) {
        res.status(403).send('Forbidden');
        return;
    }

    const forwardedIps = forwardedFor.split(',');

    // 最後の要素が<load-balancer-ip>であるかどうかで判定する
    if (forwardedIps.length >= 2 && forwardedIps[forwardedIps.length - 1].trim() === '34.54.110.179') {
        next();
    } else {
        res.status(403).send('Forbidden');
    }
});

こうすることで、ロードバランサを経由した場合はアクセスできるようになりました。

image.png

image.png

おわりに

おわり!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0