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

Firebase には、Security Rules を管理するツールがいくつか用意されています。各 ツールにはそれぞれに適したユースケースがありますが、すべて同じバックエンドの Firebase Security Rules management 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 の場合、.rules ファイルをデフォルトのデータベースと 追加の名前付きデータベースに関連付けるには、 firebase.json ファイルを確認して更新します。

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

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

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

更新をテストする

Firebase コンソールでは、エディタの UI で構文をテストできる以外に、セマンティック Security Rulesな動作を、プロジェクトのデータベースとストレージ リソースを使用して Firebase コンソールで、 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);

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

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

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

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

Realtime Database ルールセットを Admin SDK で更新するには、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 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 を使用する

ここまで、さまざまなワークフローに適したツールについて説明してきましたが、 Firebase Security Rules管理をプロジェクト内の複数のCloud Firestoreデータベースで行う場合など、管理 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 Explorer を使用して、この API を試すことができます。

管理 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 を使用して更新をテストする

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

Firebase Security Rules

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

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

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

  2. ロールの取り消しに関するCloud guide for revoking rolesに沿って、Google Cloud コンソールの [IAM]ページで「Firebase Rules Firestore Service Agent」のロールを削除します。

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