4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Firebaseの監査ログ監視とアラート通知を Google Apps Script で構築

Last updated at Posted at 2021-07-14

#はじめに
Google Apps Script 、GoogleFormと連携して、アンケートへの自動返信メールを構築できたりといろいろ便利ですよね。
今回は Google Apps Script を活用して、Firebaseの監査ログ収集とアラート通知を構築する手順について纏めています。

方針

Firebase Management にて監査ログが取得可能です。
また、ログは自動的に Cloud Loggingへ出力されています。

上記の前提のもと、

  • BigQueryへCloud Loggingのエクスポート設定を追加し、監査ログを永続化する
  • Google Apps Scriptから不正アクセスなどのイベントをBigQueryへクエリを実行して検知し、Slackなどに通知する

でログの保存、監視を構築していきます。

設計

1. ログの種類

リファレンス:

Firebase Management の監査ログ

監査ログの種類:

  • ログエントリ自体。LogEntry 型のオブジェクト
    • logName: プロジェクト ID と監査ログのタイプを保持
    • resource: 監査対象オペレーションのターゲットを保持
    • timeStamp: 監査対象オペレーションの時刻を保持
    • protoPayload: 監査対象情報を保持
  • 監査ログデータ。ログエントリの protoPayload フィールドに保持される AuditLog
  • サービス固有の監査情報(オプション)。AuditLog オブジェクトの serviceData フィールドに保持されるサービス固有のオブジェクト

料金:

  • Stackdriver Logging の無効にできない監査ログ(すべての管理アクティビティ監査ログを含む)は無料

ログのエクスポート:

Stackdriver Loggingから、ログを次の宛先にエクスポート

  • BigQuery → 永続化

データの無料枠:

  • Stackdriver : 30日間保存されるログサイズ50GB
  • BigQuery : クエリ使用量1TB, ログ保存サイズ10GB

2. ログの永続化

下記リファレンスを参照し、Stackdriver Loggingから、ログをBigQueryにエクスポートする設定を追加します。
Google Cloud Console でのログのエクスポート

3.ログの通知

Cloud Monitoring API

BigQueryへエクスポートしたログを、1時間毎に確認し、Slackへアラート通知する仕組みをGoogleAppsScriptを用いて構築していきます。

ログの永続化と通知のイメージ図
gcp_alert.jpeg

構築

1. ログの永続化

ログシンク

環境ごとにログシンクを作成し、ログをBigQueryに出力する設定します。

BigQuery:

環境ごとにデータセットを作成し、ログを永続化していきます。

テーブル構造:

永続化ログのテーブル概要と、アラート条件は下記の通りです。

テーブル名 概要 アラート条件
cloudbuild CI/CDサービスの実行ログ 対象外
bigquerydatatransfer_googleapis_com_transfer_config LogginなどからBigqueryへのデータ転送ログ 対象外
cloudaudit_googleapis_com_activity GCPリソースへのアクティビティログ 対象外
cloudaudit_googleapis_com_data_access GCPリソースへのアクセスログ 社外からのアクセスが監視間隔内に1回以上
cloudfunctions_googleapis_com_cloud_functions CloudFunctionsの実行ログ 監視間隔内の実行エラーが規定回数以上

2.ログの通知

設計

2件の監視対象に対し、アラート通知の仕組みを構築していきます。

1. GCPリソースへのアクセスログ

GCP上のデータアクセスのaudit_logの異常検知 して、Slack通知

対象テーブル:cloudaudit_googleapis_com_data_access(GCP上のデータアクセスのaudit_log)
エラーの条件:protopayload_auditlog.authenticationInfo.principalEmailが社外

1時間以内に社外メンバがリソースにアクセスした回数をカウントするクエリ

SELECT
  count(*) as result
FROM
  `[プロジェクト名].[データセット名].cloudaudit_googleapis_com_data_access`
WHERE
  TIMESTAMP(timestamp) >= TIMESTAMP_ADD(timestamp, INTERVAL 1 HOUR)
  AND ENDS_WITH(protopayload_auditlog.authenticationInfo.principalEmail, '[ドメイン]') IS FALSE

2.CloudFunctionsのエラーレートが上がっていることを検知して、Slack通知

CloudFunctions、通常はエラーが発生起きないので、エラーカウントが1時間以内に10以上発生している場合、
何らかの障害が起きていると判定する

対象テーブル:cloudfunctions_googleapis_com_cloud_functions(CloudFunctionsの実行ログ)
エラーの条件:textPayload の 「failureCount: 1,」のカウントが10以上

■1時間以内に、textPayload の 「failureCount: 1,」が発生した回数をカウントするクエリ

SELECT
  count(*) as result
FROM
  `[プロジェクト名].[データセット名].cloudfunctions_googleapis_com_cloud_functions`
WHERE
  TIMESTAMP(timestamp) >= TIMESTAMP_ADD(timestamp, INTERVAL 1 HOUR)
  AND ENDS_WITH(textPayload, 'failureCount: 1,') IS TRUE

実装

Googleスプレッドシートでマスタを用意

下記のようにGoogleスプレッドシートで監視用マスタを設定していきます。

No project category limit query
1 DEV AccessCheck 0 SELECT count(*) as result FROM...
2 STG ErrorRateCheck 10 SELECT count(*) as result FROM...

監視用マスタを設定したGoogleスプレッドシートからスクリプトエディタを開き、Google Apps Scriptの編集画面を開きます。
GAS登録.jpg

事前にGoogle Apps ScriptからBigQueryを操作するためのサービスを追加しておきます。
BigQuery.jpg

Googleスプレッドシートの値を読み取ってBigQueryへクエリを発行し、閾値を超えていたらSlackへアラート通知する処理をGoogle Apps Script で構築します。

/**
 * GCP Alert main function
 */
function main(){
  const spreadsheet = SpreadsheetApp.getActive();

  const data = spreadsheet
  .getSheetByName('master')
  .getDataRange()
  .getValues();

  readMaster(data);
}

/**
 * Read Master Spreadsheet and call runQuery()
 */
function readMaster(data) {
  try {
    if (data.length > 1) {
      for (var i = 0; i < data.length; i++) {
        if(i == 0){
          continue; // 1行目はスキップ
        }
        runQuery(data[i][1], data[i][2], data[i][3], data[i][4]);
      }
    }
  }
  catch(e) {
    Logger.log(e);
  }
}

/**
 * Runs a BigQuery query and check logs
 */
function runQuery(project, category, limit, query) {

  const projectId = 'synq-production';

  const request = {
    query: query,
    useLegacySql: false
  };
  const queryResults = BigQuery.Jobs.query(request, projectId);
  const jobId = queryResults.jobReference.jobId;

  // Check on status of the Query Job.
  const sleepTimeMs = 500;
  while (!queryResults.jobComplete) {
    Utilities.sleep(sleepTimeMs);
    sleepTimeMs *= 2;
    queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId);
  }

  // Get all the rows of results.
  const rows = queryResults.rows;
  while (queryResults.pageToken) {
    queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId, {
      pageToken: queryResults.pageToken
    });
    rows = rows.concat(queryResults.rows);
  }

  if (rows) {
    // Append the results.
    const data = new Array(rows.length);
    for (var i = 0; i < rows.length; i++) {
      const cols = rows[i].f;
      data[i] = new Array(cols.length);
      for (var j = 0; j < cols.length; j++) {
        data[i][j] = cols[j].v;
      }
    }

    if(parseInt(data[0][0], 10) > parseInt(limit, 10)){
      Logger.log("gcp alert check comp ->(: " + "プロジェクト名: " + project + ", カテゴリ: " + category + ":) check gcp log!!");
      postSlack("GCPログを確認してください", project, category);

    }else{
      Logger.log("gcp alert check comp ->(: " + "プロジェクト名: " + project + ", カテゴリ: " + category + ":) no alert!!");
    }

  } else {
    Logger.log('No rows returned.');
  }
}

/**
 * Send Notification to Sack WebHook
 */
function postSlack(message, project, category) {

  //payload
  const payload  = { 
    'username'  : "Notification",
    'text'      : 'gcp alert check comp ->: ' + 'プロジェクト名: '+ project + ', カテゴリ: ' + category + ', メッセージ: ' +  message,
    'channel'   : "#alerts",
    'icon_emoji': ":chart_with_upwards_trend:"
  };

  const options = {
    'method'      : 'post'                 ,
    'contentType' : 'application/json'     ,
    'payload'     : JSON.stringify(payload),
  };

  // Webhook URL
  const url = '[SlackのWebhook URLを設定]'; 
  UrlFetchApp.fetch(url, options);
}

作成した処理をタイマーで実行できるように設定しておきます。

スクリーンショット_2021-07-14_12_49_07.jpg

#最後に

以上、Google Apps Scriptを用いてFirebaseの監査ログ収集とアラート通知を構築する手順でした。
処理を分岐したり、データ加工などが必要になってくると、Pub/SubCloudFunctison で構築したほうが良いですが、簡単な監視 → アラート通知ならGoogle Apps Scriptだけで構築できてしまいます。
うまく使い分けて行きたいところです。

私が所属している株式会社クアンドではエンジニア募集中です。
地域産業のアップデートをミッションに、製造業や建設業など「現場」の課題を解決するためのアプリケーションを開発しています!
私もフルフレックス&フルリモートで働いています。
興味のある方はWantedlyの募集ページをご覧ください!

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?