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 │
└───────────────────┘
データフロー
- Logpush が Gateway HTTP/DNS ログを約5分間隔で R2 バケットに gzip 圧縮配信
- Cron Trigger (毎日 23:30 UTC = 8:30 JST) で Worker が起動
- Worker が GraphQL Analytics API と R2 Logpush ログ を並列取得
- R2 ログは gzip 展開 → JSON Lines 解析 → HTTP/DNS に自動分類
- DNS ログ:
ResolverDecisionがblocked*のエントリをユーザー別集計 - HTTP ログ:
DownloadedFileNames/UploadedFileNamesからファイル名を抽出 (ノイズ除外・重複排除) - 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 の作成
- Cloudflare Dashboard → My Profile → API Tokens
- Create Token → Create Custom Token
- 以下の権限を付与:
| 権限カテゴリ | 権限名 | レベル | 用途 |
|---|---|---|---|
| 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 ジョブ管理 |
- Account Resources: 対象アカウントを選択
- Continue to summary → Create Token
- 表示されたトークンを安全に保存 (後で Worker のシークレットに設定)
注意: Token は一度しか表示されません。紛失した場合は再作成が必要です。
Step 2: Cloudflare Dashboard — R2 バケットの作成
2-1. R2 バケット作成
- Cloudflare Dashboard → R2 Object Storage → Create bucket
- バケット名:
gateway-http-logs - Location: Automatic (またはお好みのリージョン)
- Create bucket
2-2. ライフサイクルルール設定
古いログを自動削除するため、ライフサイクルルールを設定します。
- 作成したバケット → Settings タブ
- Object lifecycle rules → Add rule
- 設定:
- Rule name:
Delete logs after 90 days - Scope: All objects (バケット全体)
- Action: Delete objects after 90 days
- Save
これにより、90日より古いログファイルは自動的に削除されます。
Step 3: Cloudflare Dashboard — Logpush ジョブの作成
Gateway のログを R2 バケットに自動配信する Logpush ジョブを2つ作成します。
3-1. Gateway HTTP ログ Logpush ジョブ
- Cloudflare Dashboard → Analytics & Logs → Logs → Add Logpush job
- Select a dataset:
Gateway HTTPを選択 - Select a destination:
R2を選択 - Configure destination:
- Bucket:
gateway-http-logs - Bucket path: (空のまま — デフォルトで
YYYYMMDD/パスに配信)
- 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 |
- Create で Logpush ジョブを作成
3-2. Gateway DNS ログ Logpush ジョブ
- 同様に Add Logpush job
- Select a dataset:
Gateway DNSを選択 - Select a destination:
R2→ 同じバケットgateway-http-logsを選択 - Select fields: 以下のフィールドを選択:
| フィールド | 用途 |
|---|---|
Datetime |
タイムスタンプ |
Email |
ユーザー識別 |
ResolverDecision |
ブロック判定 (blockedRule 等) |
QueryName |
クエリされたドメイン名 |
PolicyName |
適用ポリシー名 |
QueryCategoryNames |
カテゴリ分類 |
DeviceName |
デバイス名 |
SrcIP |
送信元 IP |
- 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
- Google Chat でスペースを開く
- スペース名横の ▼ → Apps & integrations → Webhooks
- Add Webhook
- 名前:
Gateway Alert(任意) - アバター URL: (任意)
- 表示された Webhook URL をコピー
4-2. Slack Incoming Webhook
- Slack API → Create New App → From scratch
- Incoming Webhooks → Activate → Add New Webhook to Workspace
- 投稿先チャンネルを選択
- 表示された 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():
gatewayL7RequestsAdaptiveGroupsをapplicationNames_hasanyとaction: "allow"でフィルタ- ユーザーごとにダウンロード数を集計、Top 5 を返す
getTopBlockedUsers():
gatewayL7RequestsAdaptiveGroupsをaction: "block"でフィルタ- ユーザーごとにブロック数を集計、アクセス先ホスト名を付記して Top 5 を返す
src/logpush.ts — R2 Logpush ログ解析
R2 バケットから Logpush ログを読み取り、解析する中核モジュール。
readRecentR2Logs():
- 日付プレフィックス (
YYYYMMDD/) で R2 オブジェクトを一覧 obj.uploaded >= sinceで時間範囲フィルタ.log.gzファイルをDecompressionStream("gzip")で展開- JSON Lines を1行ずつパースして全エントリを返す
getTopDnsBlockedUsers():
ResolverDecisionフィールドの存在で DNS エントリを判別ResolverDecisionがblockedで始まるエントリをフィルタ- ユーザーごとにブロック数、上位ドメイン、ポリシー名を集計
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)
確認事項:
- Logpush ジョブが有効か → Dashboard の Analytics & Logs > Logs で確認
- R2 バケットに
.log.gzファイルが存在するか → R2 バケットの Objects タブで確認 - WARP クライアントで Gateway に接続中のユーザーがいるか
ファイルアクティビティが多すぎる / ノイズが多い
原因: Gateway は Grammarly、DeepL、YouTube 等の通常 Web 通信でも DownloadedFileNames を記録するため。
対処: src/logpush.ts の IGNORED_FILE_NAMES に除外したいファイル名を追加。
DNS ブロック数が 0
確認事項:
- Gateway DNS ポリシーでブロックルールが設定されているか
- DNS Logpush ジョブが有効か
- WARP クライアントが Gateway DNS を使用しているか
カスタマイズ
Cron スケジュールの変更
wrangler.toml の crons を変更:
# 例: 毎日 9:00 JST (00:00 UTC)
crons = ["0 0 * * *"]
# 例: 毎時
crons = ["0 * * * *"]
# 例: 5分ごと
crons = ["*/5 * * * *"]
MONITOR_INTERVAL_MINUTES もスケジュールに合わせて調整してください。
監視対象ストレージアプリの変更
wrangler.toml の STORAGE_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日 |
