← 事例一覧に戻る
Zero Trust2026年4月3日読了 3613 views

Gateway アラート - 定期レポート

DD
DD
所属: Cloudflare Japan 株式会社|得意分野: Cloudflare のこと

Programmable SASE: Gateway 日次レポート Worker 構築ガイド

概要

Cloudflare Zero Trust Gateway のログを毎日自動分析し、セキュリティレポートを Slack / Google Chat に通知する Cloudflare Worker の構築説明です。

Cloudflare は Programable SASE と定義し、Workers などの Developer Platform を活用してお客様で機能を追加することができる、唯一のSASEベンダーです。
お客様のアイディアをソリューションズ・エンジニアにご相談ください。

以下、アーキテクチャなどです。

通知されるレポート (4セクション):

セクション 内容 データソース
📥 ストレージダウンロード Top 5 Google Drive, Box, Dropbox 等からのダウンロード件数 GraphQL Analytics API
🚫 HTTP ブロック Top 5 Gateway HTTP ポリシーによりブロックされたリクエスト件数 GraphQL Analytics API
🔍 DNS ブロック Top 5 DNS フィルタでブロックされた件数・ドメイン・ポリシー R2 Logpush (gateway_dns)
📁 ファイルアクティビティ Top 5 ダウンロード/アップロードされたファイル名一覧 R2 Logpush (gateway_http)

スケジュール: 毎日 8:30 JST (23:30 UTC)、過去24時間分を集計


アーキテクチャ

毎日 8:30 JST (Cron Trigger)
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Cloudflare Workers │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ gateway-alert │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────────────┐ │ │
│ │ │ gateway.ts │ │ logpush.ts │ │ │
│ │ │ │ │ │ │ │
│ │ │ GraphQL API │ │ R2 → gzip展開 → JSON解析│ │ │
│ │ │ ┌──────────────┐ │ │ ┌──────────────────────┐│ │ │
│ │ │ │DL Top5 │ │ │ │DNS ブロック Top5 ││ │ │
│ │ │ │HTTP Block Top5│ │ │ │ファイルアクティビティ││ │ │
│ │ │ └──────────────┘ │ │ └──────────────────────┘│ │ │
│ │ └────────┬─────────┘ └────────────┬────────────┘ │ │
│ │ └──────────┬───────────────┘ │ │
│ │ ▼ │ │
│ │ notify.ts │ │
│ │ Slack Block Kit / Google Chat Card │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ │
│ │ R2 Bucket│ ← Logpush (gateway_http + gateway_dns) │
│ │ gateway- │ 約5分間隔で gzip 圧縮 JSON Lines を自動配信 │
│ │ http-logs│ ライフサイクル: 90日で自動削除 │
│ └──────────┘ │
└────────────────────────────┬───────────────────────────────────┘
│
▼
┌───────────────────┐
│ Slack / Google │
│ Chat Webhook │
└───────────────────┘

データフロー

  1. Logpush が Gateway HTTP/DNS ログを約5分間隔で R2 バケットに gzip 圧縮配信
  2. Cron Trigger (毎日 23:30 UTC = 8:30 JST) で Worker が起動
  3. Worker が GraphQL Analytics APIR2 Logpush ログ を並列取得
  4. R2 ログは gzip 展開 → JSON Lines 解析 → HTTP/DNS に自動分類
  5. DNS ログ: ResolverDecisionblocked* のエントリをユーザー別集計
  6. HTTP ログ: DownloadedFileNames / UploadedFileNames からファイル名を抽出 (ノイズ除外・重複排除)
  7. 4セクションを Slack Block Kit または Google Chat Card 形式で Webhook 送信

前提条件

  • Cloudflare アカウント (Zero Trust Gateway 有効)
  • WARP クライアントでユーザーが Gateway に接続済み
  • Node.js 18+
  • Wrangler CLI (npm install -g wrangler)
  • 通知先の Webhook URL (Slack Incoming Webhook または Google Chat Webhook)

Step 1: Cloudflare Dashboard — API Token の作成

1-1. API Token の作成

  1. Cloudflare DashboardMy ProfileAPI Tokens
  2. Create TokenCreate Custom Token
  3. 以下の権限を付与:
権限カテゴリ 権限名 レベル 用途
Account Zero Trust Edit Gateway ログへのアクセス
Account Account Analytics Read GraphQL Analytics API
Account Workers R2 Storage Edit R2 バケット操作
Account Workers R2 データカタログ Edit R2 メタデータ
Account Logs Edit Logpush ジョブ管理
  1. Account Resources: 対象アカウントを選択
  2. Continue to summaryCreate Token
  3. 表示されたトークンを安全に保存 (後で Worker のシークレットに設定)

注意: Token は一度しか表示されません。紛失した場合は再作成が必要です。


Step 2: Cloudflare Dashboard — R2 バケットの作成

2-1. R2 バケット作成

  1. Cloudflare DashboardR2 Object StorageCreate bucket
  2. バケット名: gateway-http-logs
  3. Location: Automatic (またはお好みのリージョン)
  4. Create bucket

2-2. ライフサイクルルール設定

古いログを自動削除するため、ライフサイクルルールを設定します。

  1. 作成したバケット → Settings タブ
  2. Object lifecycle rulesAdd rule
  3. 設定:
  • Rule name: Delete logs after 90 days
  • Scope: All objects (バケット全体)
  • Action: Delete objects after 90 days
  1. Save

これにより、90日より古いログファイルは自動的に削除されます。


Step 3: Cloudflare Dashboard — Logpush ジョブの作成

Gateway のログを R2 バケットに自動配信する Logpush ジョブを2つ作成します。

3-1. Gateway HTTP ログ Logpush ジョブ

  1. Cloudflare DashboardAnalytics & LogsLogsAdd Logpush job
  2. Select a dataset: Gateway HTTP を選択
  3. Select a destination: R2 を選択
  4. Configure destination:
  • Bucket: gateway-http-logs
  • Bucket path: (空のまま — デフォルトで YYYYMMDD/ パスに配信)
  1. Select fields: 以下のフィールドを選択 (全選択推奨):
フィールド 用途
Datetime タイムスタンプ
Email ユーザー識別
Action allow / block の判定
URL アクセス先 URL
HTTPHost ホスト名
HTTPMethod HTTP メソッド
ApplicationNames L7 アプリ分類 (Google Drive 等)
PolicyName 適用ポリシー名
DownloadedFileNames ダウンロードファイル名
UploadedFileNames アップロードファイル名
FileInfo 詳細ファイル情報 (サイズ等)
BlockedFileName ブロックされたファイル名
BlockedFileSize ブロックされたファイルサイズ
DeviceName デバイス名
SourceIP 送信元 IP
  1. Create で Logpush ジョブを作成

3-2. Gateway DNS ログ Logpush ジョブ

  1. 同様に Add Logpush job
  2. Select a dataset: Gateway DNS を選択
  3. Select a destination: R2同じバケット gateway-http-logs を選択
  4. Select fields: 以下のフィールドを選択:
フィールド 用途
Datetime タイムスタンプ
Email ユーザー識別
ResolverDecision ブロック判定 (blockedRule 等)
QueryName クエリされたドメイン名
PolicyName 適用ポリシー名
QueryCategoryNames カテゴリ分類
DeviceName デバイス名
SrcIP 送信元 IP
  1. Create で Logpush ジョブを作成

3-3. 確認

作成後、R2 バケットにログファイルが配信されることを確認します:

  • R2 バケット → Objects タブ
  • YYYYMMDD/ プレフィックスのフォルダ内に .log.gz ファイルが作成されていれば成功
  • 配信間隔は約5分 (Cloudflare 側で固定、変更不可)

注意: Logpush の配信間隔は Cloudflare 側で管理されており、ユーザーが変更することはできません。約5分ごとにバッチでログが配信されます。


Step 4: 通知先 Webhook の設定(Google Chat と Slack で例を記載)

4-1. Google Chat Webhook

  1. Google Chat でスペースを開く
  2. スペース名横の Apps & integrationsWebhooks
  3. Add Webhook
  • 名前: Gateway Alert (任意)
  • アバター URL: (任意)
  1. 表示された Webhook URL をコピー

4-2. Slack Incoming Webhook

  1. Slack APICreate New AppFrom scratch
  2. Incoming WebhooksActivateAdd New Webhook to Workspace
  3. 投稿先チャンネルを選択
  4. 表示された Webhook URL をコピー

Step 5: Worker プロジェクトの構成

5-1. プロジェクト構造

workers/gateway-alert/
├── wrangler.toml # Worker 設定 (Cron, 環境変数, R2 バインディング)
├── package.json # 依存関係
├── tsconfig.json # TypeScript 設定
├── .dev.vars.example # シークレットのテンプレート
└── src/
├── index.ts # エントリポイント (Cron handler + HTTP handler)
├── gateway.ts # GraphQL Analytics API クエリ
├── logpush.ts # R2 Logpush ログ読み取り・解析
├── notify.ts # Slack / Google Chat 通知フォーマット
└── types.ts # TypeScript 型定義

5-2. 各ファイルの役割

wrangler.toml — Worker 設定

name = "gateway-alert"
main = "src/index.ts"
compatibility_date = "2024-12-05"
account_id = "<YOUR_ACCOUNT_ID>"

# 毎日 8:30 JST (23:30 UTC) に実行
[triggers]
crons = ["30 23 * * *"]

[vars]
CF_ACCOUNT_ID = "<YOUR_ACCOUNT_ID>"
STORAGE_APP_NAMES = "Google Drive,Box,Dropbox,Microsoft OneDrive,Microsoft SharePoint Online"
MONITOR_INTERVAL_MINUTES = "1440" # 24時間
NOTIFY_TARGET = "google_chat" # "slack", "google_chat", "both"

# R2 バケットバインディング
[[r2_buckets]]
binding = "GATEWAY_LOGS_BUCKET"
bucket_name = "gateway-http-logs"
変数 説明 デフォルト
CF_ACCOUNT_ID Cloudflare アカウント ID — (必須)
STORAGE_APP_NAMES 監視対象ストレージアプリ名 (カンマ区切り) Google Drive, Box, Dropbox, OneDrive, SharePoint
MONITOR_INTERVAL_MINUTES 集計期間 (分) 1440 (24時間)
NOTIFY_TARGET 通知先 google_chat

src/index.ts — エントリポイント

Cron Trigger と HTTP ハンドラの両方を実装。

  • Cron handler (scheduled): 定時実行。GraphQL + R2 ログを並列取得 → 集計 → 通知送信。データがなければ通知スキップ。
  • HTTP handler (fetch): 手動テスト用。
  • GET / — ヘルスチェック
  • GET /dry-run — データ取得のみ (通知なし)、JSON レスポンス
  • GET /test — データ取得 + 実際に通知送信
// 処理フロー
1. GraphQL API (DL Top5, Block Top5) と R2 ログ読み取りを Promise.all で並列実行
2. R2 ログから HTTP エントリと DNS エントリを分類
3. DNS: ResolverDecision が "blocked*" のエントリをユーザー別集計
4. HTTP: DownloadedFileNames / UploadedFileNames からファイル名抽出
5. 4セクションの AlertData を構築
6. 設定に応じて Slack / Google Chat に通知

src/gateway.ts — GraphQL Analytics API

Cloudflare GraphQL Analytics API (https://api.cloudflare.com/client/v4/graphql) を使用。

getTopStorageDownloaders():

  • gatewayL7RequestsAdaptiveGroupsapplicationNames_hasanyaction: "allow" でフィルタ
  • ユーザーごとにダウンロード数を集計、Top 5 を返す

getTopBlockedUsers():

  • gatewayL7RequestsAdaptiveGroupsaction: "block" でフィルタ
  • ユーザーごとにブロック数を集計、アクセス先ホスト名を付記して Top 5 を返す

src/logpush.ts — R2 Logpush ログ解析

R2 バケットから Logpush ログを読み取り、解析する中核モジュール。

readRecentR2Logs():

  1. 日付プレフィックス (YYYYMMDD/) で R2 オブジェクトを一覧
  2. obj.uploaded >= since で時間範囲フィルタ
  3. .log.gz ファイルを DecompressionStream("gzip") で展開
  4. JSON Lines を1行ずつパースして全エントリを返す

getTopDnsBlockedUsers():

  • ResolverDecision フィールドの存在で DNS エントリを判別
  • ResolverDecisionblocked で始まるエントリをフィルタ
  • ユーザーごとにブロック数、上位ドメイン、ポリシー名を集計

getTopFileActivities():

  • Action フィールドの存在 + ResolverDecision なしで HTTP エントリを判別
  • DownloadedFileNames, UploadedFileNames, FileInfo, BlockedFileName からファイル情報を抽出
  • ノイズフィルタ + ファイル名重複排除を適用

ファイル名のノイズフィルタリング:

Gateway HTTP ログは通常の Web 通信でもファイル名を記録するため、以下を自動除外:

除外ファイル名 理由
<unknown file name> Gateway がファイル名を検出できなかった場合のプレースホルダー
json.txt Google Drive のリダイレクトレスポンス (実ファイルではない)
response.txt, response.bin API レスポンスボディ
config.json ブラウザ拡張の設定取得
cleardot.gif トラッキングピクセル
dynamicConfig.json Grammarly 等の拡張設定

さらに、同一ユーザー・同一ファイル名・同一方向 (DL/UL) は重複排除されます。

src/notify.ts — 通知フォーマット

Slack: Block Kit 形式 (mrkdwn)
Google Chat: Card V2 形式 (decoratedText + sections)

両方とも同じ4セクション構成で、ユーザーメール・件数・詳細情報を表示。

src/types.ts — 型定義

説明
Env Worker 環境変数・シークレット・R2 バインディング
AlertData 4セクションの集計結果をまとめた通知データ
UserActivity GraphQL 由来のユーザーアクティビティ (email, count, details)
FileActivity Logpush 由来のファイルアクティビティ (email, files[], totalFiles)
DnsBlockUserActivity DNS ブロック集計 (email, blockCount, topDomains, policies)
SlackMessage Slack Block Kit メッセージ型
GoogleChatMessage Google Chat Card V2 メッセージ型

Step 6: デプロイ

6-1. 依存関係インストール

cd workers/gateway-alert
npm install

6-2. シークレット設定

# Cloudflare API Token (Step 1 で作成したもの)
npx wrangler secret put CF_API_TOKEN

# Google Chat Webhook URL (Step 4 で取得)
npx wrangler secret put GOOGLE_CHAT_WEBHOOK_URL

# Slack Webhook URL (使用する場合)
npx wrangler secret put SLACK_WEBHOOK_URL

6-3. デプロイ

npx wrangler deploy

デプロイ成功時の出力例:

Total Upload: 26.72 KiB / gzip: 6.28 KiB
Uploaded gateway-alert
Deployed gateway-alert triggers
https://gateway-alert.<YOUR_SUBDOMAIN>.workers.dev
schedule: 30 23 * * *

Step 7: 動作確認

7-1. ドライラン (通知なし)

curl https://gateway-alert.<YOUR_SUBDOMAIN>.workers.dev/dry-run

JSON レスポンスでデータを確認:

{
"timestamp": "2026/4/3 10:50:59",
"intervalMinutes": 1440,
"topDownloaders": [...],
"topBlocked": [...],
"topFileActivities": [...],
"topDnsBlockedUsers": [...]
}

7-2. テスト通知送信

curl https://gateway-alert.<YOUR_SUBDOMAIN>.workers.dev/test

Google Chat / Slack に実際に通知が送信されます。

7-3. Worker ログの確認

npx wrangler tail --format=pretty

リアルタイムで Worker のログを確認できます。R2 ログの読み取り状況や集計結果が出力されます。


トラブルシューティング

GraphQL API が "not authorized for that account" エラーを返す

原因: API Token に Account Analytics > Read 権限がない。

対処: Cloudflare Dashboard → My Profile → API Tokens で権限を追加。

R2 ログが空 (0 entries)

確認事項:

  1. Logpush ジョブが有効か → Dashboard の Analytics & Logs > Logs で確認
  2. R2 バケットに .log.gz ファイルが存在するか → R2 バケットの Objects タブで確認
  3. WARP クライアントで Gateway に接続中のユーザーがいるか

ファイルアクティビティが多すぎる / ノイズが多い

原因: Gateway は Grammarly、DeepL、YouTube 等の通常 Web 通信でも DownloadedFileNames を記録するため。

対処: src/logpush.tsIGNORED_FILE_NAMES に除外したいファイル名を追加。

DNS ブロック数が 0

確認事項:

  1. Gateway DNS ポリシーでブロックルールが設定されているか
  2. DNS Logpush ジョブが有効か
  3. WARP クライアントが Gateway DNS を使用しているか

カスタマイズ

Cron スケジュールの変更

wrangler.tomlcrons を変更:

# 例: 毎日 9:00 JST (00:00 UTC)
crons = ["0 0 * * *"]

# 例: 毎時
crons = ["0 * * * *"]

# 例: 5分ごと
crons = ["*/5 * * * *"]

MONITOR_INTERVAL_MINUTES もスケジュールに合わせて調整してください。

監視対象ストレージアプリの変更

wrangler.tomlSTORAGE_APP_NAMES を変更:

STORAGE_APP_NAMES = "Google Drive,Box,Dropbox"

アプリ名は Cloudflare の L7 DPI 分類名と一致させる必要があります。

Top N の変更

各関数の .slice(0, 5) を変更することで表示件数を変更できます (例: Top 10)。


技術仕様

使用 API

API エンドポイント 認証方式
GraphQL Analytics https://api.cloudflare.com/client/v4/graphql Bearer Token
R2 (Workers Binding) Worker 内から env.GATEWAY_LOGS_BUCKET で直接アクセス 不要 (バインディング)
Slack Webhook https://hooks.slack.com/services/... URL に含まれる
Google Chat Webhook https://chat.googleapis.com/v1/spaces/... URL に含まれる

GraphQL クエリ

ストレージダウンロード Top 5:

query GatewayStorageDownloads($accountId: String!, $since: String!, $until: String!, $appNames: [String!]) {
viewer {
accounts(filter: { accountTag: $accountId }) {
gatewayL7RequestsAdaptiveGroups(
filter: {
datetime_geq: $since
datetime_leq: $until
applicationNames_hasany: $appNames
action: "allow"
}
limit: 500
orderBy: [count_DESC]
) {
dimensions { email, applicationNames }
count
}
}
}
}

HTTP ブロック Top 5:

query GatewayBlockedUsers($accountId: String!, $since: String!, $until: String!) {
viewer {
accounts(filter: { accountTag: $accountId }) {
gatewayL7RequestsAdaptiveGroups(
filter: {
datetime_geq: $since
datetime_leq: $until
action: "block"
}
limit: 500
orderBy: [count_DESC]
) {
dimensions { email, httpHost }
count
}
}
}
}

Logpush ログ形式

R2 に配信されるログは以下の形式:

  • パス: YYYYMMDD/<timestamp>_<hash>.log.gz
  • 圧縮: gzip
  • 内容: JSON Lines (1行1エントリ)
  • 配信間隔: 約5分ごと (Cloudflare 側で固定)

R2 ライフサイクルルール

ルール 対象 アクション 期間
Delete logs after 90 days バケット全体 オブジェクト削除 90日

免責事項

本記事は Cloudflare Japan のソリューションエンジニアが技術的な知見の共有を目的として個人の立場で執筆したものです。記事の内容は執筆時点の情報および執筆者の主観的な見解を含んでおり、Cloudflare, Inc. の公式な見解・推奨・仕様保証を構成するものではありません。また、記載された構成や手順がすべてのお客様環境で同様に適用できることを保証するものでもありません。製品の正式な仕様や技術サポートについては、Cloudflare 公式ドキュメントまたは担当チームにお問い合わせください。