Firebase セキュリティ ルールの管理とデプロイ

Firebase には、ルールを管理するツールがいくつか用意されています。各ツールにはそれぞれに適したユースケースがありますが、すべて同じバックエンドの Firebase セキュリティ ルール管理 API を使用しています。

どのツールを使用して管理 API を呼び出しても、次の操作を行うことができます。

  • ルールのソースを取り込む。これは一連のルールを定義したもので、通常は Firebase セキュリティ ルールのステートメントを含むコードファイルです。
  • 取り込まれたソースを変更不可のルールセットとして保存する。
  • 各ルールセットのデプロイをリリースでトラッキングする。Firebase セキュリティ ルールが有効になっているサービスは、プロジェクトのリリースを検索して、保護されたリソースに対する各リクエストを評価します。
  • ルールセットの構文テストとセマンティック テストを実施する。

Firebase CLI を使用する

Firebase CLI を使用して、ローカルのソースをアップロードし、リリースをデプロイできます。CLI の Firebase Local Emulator Suite を使用すると、ソースの完全なローカルテストを行うことができます。

CLI を使用すると、アプリケーション コードとともにルールがバージョン管理下のもとで維持されます。また、既存のデプロイ プロセスの一環としてルールをデプロイできます。

構成ファイルを生成する

Firebase CLI を使用して Firebase プロジェクトを構成する際に、プロジェクト ディレクトリ内に .rules 構成ファイルを作成します。Firebase プロジェクトの構成を開始するには、以下のコマンドを使用します。

Cloud Firestore

// Set up Firestore in your project directory, creates a .rules file
firebase init firestore

Realtime Database

// Set up Realtime Database in your project directory, creates a .rules file
firebase init database

Cloud Storage

// Set up Storage in your project directory, creates a .rules file
firebase init storage

ルールを編集して更新する

.rules 構成ファイル内でルールのソースを直接編集します。Firebase CLI で行った編集がすべて Firebase コンソールに反映されるようにしてください。あるいは、Firebase コンソールか Firebase CLI のいずれかを一貫して使用し更新を行います。このようにしなければ、Firebase コンソールで行った更新を上書きしてしまう可能性があります。

更新をテストする

Local Emulator Suite は、セキュリティ ルールに対応しているすべてのプロダクトのエミュレータを提供します。各エミュレータのセキュリティ ルール エンジンには、ルールについて構文的な評価とセマンティックな評価を行う機能が備わっており、セキュリティ ルール管理 API で行われる構文テストよりも優れています。

CLI を使用する場合は、Local Emulator Suite を使用して Firebase セキュリティ ルールをテストすることをおすすめします。Local Emulator Suite を使用して更新内容をローカルでテストし、アプリのルールが意図したとおりに動作することを確認します。

更新をデプロイする

セキュリティ ルールを更新してテストしたら、ソースを本番環境にデプロイします。以下のコマンドを使用して、セキュリティ ルールだけを選択的にデプロイするか、通常のデプロイ プロセスの一環としてデプロイします。

Cloud Firestore

// Deploy your .rules file
firebase deploy --only firestore:rules

Realtime Database

// Deploy your .rules file
firebase deploy --only database

Cloud Storage

// Deploy your .rules file
firebase deploy --only storage

Firebase コンソールを使用する

Firebase コンソールを使用して、ルールのソースを編集し、リリースとしてデプロイすることもできます。Firebase コンソールの UI を使用した編集時に構文のテストが行われます。セマンティックなテストは、ルール プレイグラウンドで行うことができます。

ルールを編集して更新する

  1. Firebase コンソールを開き、プロジェクトを選択します。
  2. 次に、プロダクト ナビゲーションから [Realtime Database]、[Cloud Firestore]、[Storage] のいずれかを選択し、[ルール] をクリックしてルールエディタを開きます。
  3. エディタでルールを直接編集します。

更新をテストする

Firebase コンソールでは、エディタの UI で構文をテストできる以外に、ルール プレイグラウンドを使用して、プロジェクトのデータベースとストレージ リソースを使用してセマンティックなルールの動作を直接テストできます。ルールエディタで [ルール プレイグラウンド] 画面を開き、設定を変更して [実行] をクリックします。エディタの上部に確認メッセージが表示されます。

更新をデプロイする

更新が意図したとおりのものであることを確認したら、[Publish] をクリックします。

Admin SDK の使用

Node.js ルールセットでは、Admin SDK を使用できます。このプログラムによるアクセスを通じて、次の操作を行うことができます。

  • ルールを管理するカスタムツール、スクリプト、ダッシュボード、CI / CD パイプラインを実装する。
  • 複数の Firebase プロジェクト間でルールを管理する。

プログラムでルールを更新する場合は、アプリのアクセス制御に不要な変更を行わないように注意してください。特にルールを更新またはデプロイする場合は、セキュリティを最優先事項として Admin SDK コードを記述する必要があります。

Firebase セキュリティ ルールのリリースが完全に伝播するまでに数分かかることに注意してください。デプロイがまだ完了していないルールをアプリで使用すると競合状態が発生します。Admin SDK を使用してルールをデプロイする場合は、こうした競合が発生しないように注意してください。アクセス制御ルールを頻繁に更新する必要がある場合は Cloud Firestore の使用を検討してください。このソリューションでは、頻繁に更新が発生しても競合状態を減らすことができます。

また、次のような制限もあります。

  • シリアル化する場合、ルールは 256 KiB 未満にし、UTF-8 でエンコードされたテキストにする必要があります。
  • 1 つのプロジェクトにデプロイできるルールセットの最大数は 2,500 です。この上限に達したら、新しいルールセットを作成する前に古いルールセットを削除する必要があります。

Cloud Storage または Cloud Firestore のルールセットを作成してデプロイする

Admin SDK でセキュリティ ルールを管理する標準的なワークフローでは、次の 3 つのステップを行います。

  1. ルール ファイル ソースを作成する(省略可)
  2. ルールセットを作成する
  3. 新しいルールセットをリリースまたはデプロイする

この SDK には、これらのステップを組み合わせ、Cloud Storage と Cloud Firestore のセキュリティ ルールに対する 1 回の API 呼び出しで処理を実行できるメソッドが用意されています。次に例を示します。

    const source = `service cloud.firestore {
      match /databases/{database}/documents {
        match /carts/{cartID} {
          allow create: if request.auth != null && request.auth.uid == request.resource.data.ownerUID;
          allow read, update, delete: if request.auth != null && request.auth.uid == resource.data.ownerUID;
        }
      }
    }`;
    // Alternatively, load rules from a file
    // const fs = require('fs');
    // const source = fs.readFileSync('path/to/firestore.rules', 'utf8');

    await admin.securityRules().releaseFirestoreRulesetFromSource(source);

このパターンは、releaseFirestoreRulesetFromSource() を使用した Cloud Storage ルールでも機能します。

また、ルールファイルをメモリ内オブジェクトとして作成し、作成したルールセットを個別にデプロイすると、これらのイベントを細かく制御できます。次に例を示します。

    const rf = admin.securityRules().createRulesFileFromSource('firestore.rules', source);
    const rs = await admin.securityRules().createRuleset(rf);
    await admin.securityRules().releaseFirestoreRuleset(rs);

Realtime Database ルールセットを更新する

Admin SDK で Realtime Database ルールセットを更新するには、admin.databasegetRules() メソッドと setRules() メソッドを使用します。ルールセットは、JSON 形式またはコメント付きの文字列として取得できます。

ルールセットを更新するには:

    const source = `{
      "rules": {
        "scores": {
          ".indexOn": "score",
          "$uid": {
            ".read": "$uid == auth.uid",
            ".write": "$uid == auth.uid"
          }
        }
      }
    }`;
    await admin.database().setRules(source);

ルールセットを管理する

Admin SDK で大規模なルールセットを管理するには、admin.securityRules().listRulesetMetadata を使用して既存のルールを一覧表示します。次に例を示します。

    const allRulesets = [];
    let pageToken = null;
    while (true) {
      const result = await admin.securityRules().listRulesetMetadata(pageToken: pageToken);
      allRulesets.push(...result.rulesets);
      pageToken = result.nextPageToken;
      if (!pageToken) {
        break;
      }
    }

時間が経つと 2,500 のルールセット数の上限に達するような非常に大規模なデプロイでは、一定の周期で古いルールを削除するロジックを作成できます。たとえば、デプロイされてから 30 日を経過したルールセットをすべて削除するには、次のようにします。

    const thirtyDays = new Date(Date.now() - THIRTY_DAYS_IN_MILLIS);
    const promises = [];
    allRulesets.forEach((rs) => {
      if (new Date(rs.createTime) < thirtyDays) {
        promises.push(admin.securityRules().deleteRuleset(rs.name));
      }
    });
    await Promise.all(promises);
    console.log(`Deleted ${promises.length} rulesets.`);

REST API の使用

ここまで、さまざまなワークフローに適したツールについて説明してきましたが、管理 API 自体を使用して Firebase セキュリティ ルールの管理とデプロイを行うこともできます。管理 API を使用する方法が最も柔軟性に優れています。

Firebase セキュリティ ルールのリリースが完全に伝播するまでに数分かかることに注意してください。デプロイがまだ完了していないルールをアプリで使用すると競合状態が発生します。管理 REST API を使用してデプロイする場合は、こうした競合が発生しないように注意してください。

また、次のような制限もあります。

  • シリアル化する場合、ルールは 256 KiB 未満にし、UTF-8 でエンコードされたテキストにする必要があります。
  • 1 つのプロジェクトにデプロイできるルールセットの最大数は 2,500 です。この上限に達したら、新しいルールセットを作成する前に古いルールセットを削除する必要があります。

REST を使用して Cloud Storage または Cloud Firestore のルールセットを作成してデプロイする

このセクションの例では Cloud Storage のルールを使用していますが、Cloud Firestore でも同じ方法を使用できます。

また、例では cURL を使用して API を呼び出しています。認証トークンを設定して渡す手順は省略しています。リファレンス ドキュメントに統合された API Explorer を使用して、この API を試すことができます。

管理 API を使用してルールセットを作成し、デプロイする一般的な手順は次のとおりです。

  1. ルール ファイル ソースを作成する
  2. ルールセットを作成する
  3. 新しいルールセットをリリース(デプロイ)する

たとえば、secure_commerce Firebase プロジェクトを使用して、すべてのアクセスを禁止する Cloud Storage ルールをデプロイするとします。これらのルールは storage.rules ファイルに実装できます。

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}

次に、このファイルの Base64 エンコード フィンガープリントを生成します。次に、このファイル内のソースを使用して、projects.rulesets.create REST 呼び出しでルールセットを作成するために必要なペイロードを入力できます。ここでは、cat コマンドを使用して、storage.rules のコンテンツを REST ペイロードに挿入します。

curl -X POST -d '{
  "source": {
    {
      "files": [
        {
          "content": "' $(cat storage.rules) '",
          "name": "storage.rules",
          "fingerprint": <sha fingerprint>
        }
      ]
    }
  }
}' 'https://firebaserules.googleapis.com/v1/projects/secure_commerce/rulesets'

API は検証レスポンスとルールセット名(例: projects/secure_commerce/rulesets/uuid123)を返します。ルールセットが有効な場合、最後のステップは名前付きリリースで新しいルールセットをデプロイすることです。

curl -X POST -d '{
  "name": "projects/secure_commerce/releases/prod/v23   "  ,
  "rulesetName": "projects/secure_commerce/rulesets/uuid123",
}' 'https://firebaserules.googleapis.com/v1/projects/secure_commerce/releases'

REST で Realtime Database ルールセットを更新する

Realtime Database には、ルールを管理するための独自の REST インターフェースがあります。REST で Firebase Realtime Database ルールを管理するを参照してください。

REST でルールセットを管理する

大規模なルールのデプロイを管理しやすいように、管理 API には、ルールセットやリリースを作成する REST メソッドに加えて、次のメソッドが用意されています。

  • ルールセットのリスト取得、取得、削除
  • ルールのリリースのリスト取得、取得、削除

時間が経つと 2,500 のルールセット数の上限に達するような非常に大規模なデプロイでは、一定の周期で古いルールを削除するロジックを作成できます。たとえば、デプロイされてから 30 日を経過したルールセットをすべて削除するには、projects.rulesets.list メソッドを呼び出し、Ruleset オブジェクトの JSON リストの createTime キーを解析して、対応するルールセットの ruleset_id を指定して project.rulesets.delete を呼び出します。

REST を使用して更新をテストする

最後に、管理 API を使用して、本番環境のプロジェクトの Cloud Firestore リソースと Cloud Storage リソースに対して構文テストとセマンティックなテストを実施できます。

API のコンポーネントを使用したテストの手順は次のとおりです。

  1. 一連の TestCase オブジェクトを表す TestSuite JSON オブジェクトを定義する
  2. TestSuite を送信する
  3. 返された TestResult オブジェクトを解析する

testcase.json ファイルに、TestCase を 1 つ含む TestSuite オブジェクトを定義してみましょう。この例では、REST ペイロードを使用して、ルールの言語ソースと、それらのルールに対して実施するテストスイートをインラインで渡します。期待されるルールの評価結果と、ルールセットのテスト対象となるクライアント リクエストを指定します。どの程度詳しいテストレポートを生成するかを指定することもできます。「FULL」の値を指定すると、リクエストに一致しない式も含めて、ルールのすべての言語式の結果をレポートに含めることができます。

 {
  "source":
  {
    "files":
    [
      {
        "name": "firestore.rules",
        "content": "service cloud.firestore {
          match /databases/{database}/documents {
            match /users/{userId}{
              allow read: if (request.auth.uid == userId);
            }
            function doc(subpath) {
              return get(/databases/$(database)/documents/$(subpath)).data;
            }
            function isAccountOwner(accountId) {
              return request.auth.uid == accountId
                  || doc(/users/$(request.auth.uid)).accountId == accountId;
            }
            match /licenses/{accountId} {
              allow read: if isAccountOwner(accountId);
            }
          }
        }"
      }
    ]
  },
  "testSuite":
  {
    "testCases":
    [
      {
        "expectation": "ALLOW",
        "request": {
           "auth": {"uid": "123"},
           "path": "/databases/(default)/documents/licenses/abcd",
           "method": "get"},
        "functionMocks": [
            {
            "function": "get",
            "args": [{"exact_value": "/databases/(default)/documents/users/123"}],
            "result": {"value": {"data": {"accountId": "abcd"}}}
            }
          ]
      }
    ]
  }
}

その後、projects.test メソッドを使用してこの TestSuite を送信し、評価を行うことができます。

curl -X POST -d '{
    ' $(cat testcase.json) '
}' 'https://firebaserules.googleapis.com/v1/projects/secure_commerce/rulesets/uuid123:test'

返された TestReport(テストの SUCCESS(成功) / FAILURE(失敗)のステータス、デバッグ メッセージのリスト、アクセスしたルール式のリスト、評価レポートを含む)のステータスが SUCCESS であれば、アクセスが正しく許可されています。