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

Firebase には、Security Rules を管理するためのツールがいくつか用意されています。これらのツールはそれぞれ特定のケースで役立ち、同じバックエンドの Firebase セキュリティ ルール管理 API を使用します。

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

  • ルール ソース(通常は Firebase Security Rules ステートメントを含むコードファイル)のセットを取り込みます。
  • 取り込まれたソースを変更不可のルールセットとして保存する。
  • 各ルールセットのデプロイをリリースでトラッキングする。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 Security Rules をテストすることをおすすめします。Local Emulator Suite を使用してローカルで更新内容をテストし、アプリの Security Rules が意図したとおりに動作することを確認します。

更新をデプロイする

Security Rules を更新してテストしたら、ソースを本番環境にデプロイします。

Cloud Firestore Security Rules の場合、firebase.json ファイルを確認して更新することで、.rules ファイルをデフォルトのデータベースと追加の名前付きデータベースに関連付けます。

次のコマンドを使用して、Security Rules のみをデプロイするか、通常のデプロイ プロセスの一部としてデプロイします。

Cloud Firestore

// Deploy rules for all databases configured in your firebase.json
firebase deploy --only firestore:rules
// Deploy rules for the specified database configured in your firebase.json firebase deploy --only firestore:<databaseId>

Realtime Database

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

Cloud Storage

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

Firebase コンソールの使用

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

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

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

更新をテストする

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

更新をデプロイする

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

Admin SDK の使用

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

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

ルールをプログラムで更新する場合は、アプリのアクセス制御に意図しない変更を加えないようにすることが非常に重要です。特にルールを更新またはデプロイする場合は、セキュリティを最優先に考慮して Admin SDK コードを記述してください。

Firebase Security Rules のリリースが完全に伝播するまでに数分かかることに注意してください。Admin SDK を使用してルールをデプロイする場合は、デプロイが完了していないルールにアプリがすぐに依存する競合状態を回避してください。ユースケースでアクセス制御ルールの頻繁な更新が必要な場合は、Cloud Firestore を使用するソリューションを検討してください。これは、頻繁な更新にもかかわらず競合状態を軽減するように設計されています。

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

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

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

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

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

SDK には、これらの手順を Cloud StorageCloud 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 を使用する

上記のツールは、プロジェクト内の複数の Cloud Firestore データベースの Firebase Security Rules 管理など、さまざまなワークフローに適していますが、管理 API 自体を使用して Firebase Security Rules の管理とデプロイを行うこともできます。管理 API は、最も柔軟性の高い方法です。

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

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

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

このセクションの例では Firestore Security Rules を使用していますが、Cloud Storage Security Rules にも適用されます。

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

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

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

ソースを作成します

secure_commerce Firebase プロジェクトで作業していて、ロックダウンされた Cloud Firestore Security Rules をプロジェクトの east_store という名前のデータベースにデプロイするとします。

これらのルールは firestore.rules ファイルに実装できます。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

ルールセットを作成する

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

トラッキングで、これを east_store データベースに関連付けるには、attachment_pointeast_store に設定します。

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

API は、検証レスポンスとルールセット名(projects/secure_commerce/rulesets/uuid123 など)を返します。

ルールセットをリリース(デプロイ)する

ルールセットが有効な場合は、最後のステップとして、名前付きリリースに新しいルールセットをデプロイします。

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

Firebase Security Rules リリースが完全に伝播されるまでには数分かかります。管理 REST API を使用してデプロイする場合は、デプロイがまだ完了していないルールにアプリがすぐに依存する競合状態を回避してください。

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

Realtime Database は、Security Rules を管理するための独自の REST インターフェースを提供します。REST で Firebase Realtime Database Security Rules を管理するをご覧ください。

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

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

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

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

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

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

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

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

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

 {
  "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 であれば、アクセスが正しく許可されています。

サービス間の Cloud Storage Security Rules の権限を管理する

Cloud Firestore ドキュメントのコンテンツを使用してセキュリティ条件を評価する Cloud Storage Security Rules を作成すると、Firebase コンソールまたは Firebase CLI で、2 つのプロダクトを接続する権限を有効にするように求められます。

このようなサービス間セキュリティを無効にするには、次のようにします。

  1. まず、この機能を無効にする前にルールを編集し、Security Rules 関数を使用して Cloud Firestore にアクセスするすべてのステートメントを削除します。そうしないと、この機能が無効になった後、Security Rules の評価によって Storage リクエストが失敗します。

  2. ロールの取り消しに関する Cloud ガイドに沿って、Google Cloud コンソールの IAM ページで「Firebase Rules Firestore サービス エージェント」のロールを削除します。

次回、Firebase CLI または Firebase コンソールでサービス間ルールを保存するときに、この機能を再度有効にするように求められます。