🐕

Cloudflare Turnstile の Analytics

2024/10/11に公開

はじめに

Turnstile の Analytics 機能を確認します。

現時点(2024年10月)でダッシュボードで確認できる項目は下記の通りです。
今後の機能拡張はあると思います

項目 概要
Visitor Solve Rate
訪問者解決率
チャレンジ成功数 / チャレンジ発行数
API Solve Rate
API 解決率
トークン検証成功数 / チャレンジ発行数
Widget Traffic
ウィジェットトラフィック
ウィジェットの解決数および未解決数
Top Actions
トップアクション
利用者がウィジェット用に作成したラベル(=アクション)ごとのチャレンジ数

また、これらの元データを直接クエリーできる GraphQL のデータセット turnstileAdaptiveGroups が提供されています。例えば、ダッシュボードをインスペクトすると、裏で出ているクエリーを確認できます。

いくつかの例を使い、それぞれを並行で確認してみます。


例1

状況

  • チャレンジ発行 = 2
  • チャレンジ成功 = 2
  • トークン検証成功 = 0

ダッシュボード

  • 訪問者解決率 100% (2/2)
  • API 解決率 0% (0/2)

GraphQL

データセットは turnstileAdaptiveGroups
Adaptive とあるように、内容はアダプティブサンプリングです。
リクエスト数が増えるにつれ、サンプルレートが低くなる

クエリ
query ($ACCOUNTTAG: string!, $DATETIMEG: Time, $DATETIMEL: Time, $SITEKEY: string!, $ACTION: string!){
  viewer {
    accounts(filter: {accountTag: $ACCOUNTTAG}) {
      turnstileAdaptiveGroups ( filter: {
        datetime_geq: $DATETIMEG
        datetime_lt: $DATETIMEL
        siteKey_like: $SITEKEY
        action_like: $ACTION
      }
      limit: 10
      orderBy: [count_DESC]
      ) 
      {
        count
        dimensions {
          eventType
          datetime
        }
      }
    }
  }
}
バリアブル
{
  "ACCOUNTTAG": "アカウントID",
  "DATETIMEG": "2024-10-09T14:00:00+09:00",
  "DATETIMEL": "2024-10-09T14:10:00+09:00",
  "SITEKEY" : "サイトキー",
  "ACTION": "ウィジェットのラベル名"
}

時刻の書式は +09:00 で JST にしています。

結果

ダッシュボードと同様に

challenge_issued(発行したチャレンジ)が 2回
challenge_non_interactive_solved(非インタラクティブな解決)が 2回

発生しいます。

{
  "data": {
    "viewer": {
      "accounts": [
        {
          "turnstileAdaptiveGroups": [
            {
              "count": 1,
              "dimensions": {
                "datetime": "2024-10-09T05:05:00Z",
                "eventType": "challenge_issued"
              }
            },
            {
              "count": 1,
              "dimensions": {
                "datetime": "2024-10-09T05:09:29Z",
                "eventType": "challenge_non_interactive_solved"
              }
            },
            {
              "count": 1,
              "dimensions": {
                "datetime": "2024-10-09T05:05:00Z",
                "eventType": "challenge_non_interactive_solved"
              }
            },
            {
              "count": 1,
              "dimensions": {
                "datetime": "2024-10-09T05:09:29Z",
                "eventType": "challenge_issued"
              }
            }
          ]
        }
      ]
    }
  },
  "errors": null
}

カウント属性からイベント発生時刻(datetime)を削除し、イベントタイプだけでまとめます。

クエリ
query ($ACCOUNTTAG: string!, $DATETIMEG: Time, $DATETIMEL: Time, $SITEKEY: string!, $ACTION: string!){
  viewer {
    accounts(filter: {accountTag: $ACCOUNTTAG}) {
      turnstileAdaptiveGroups ( filter: {
        datetime_geq: $DATETIMEG
        datetime_lt: $DATETIMEL
        siteKey_like: $SITEKEY
        action_like: $ACTION
      }
      limit: 10
      orderBy: [count_DESC]
      ) 
      {
        count
        dimensions {
          eventType
          #datetime
        }
      }
    }
  }
}

各イベントの発生回数が取れました。

{
  "data": {
    "viewer": {
      "accounts": [
        {
          "turnstileAdaptiveGroups": [
            {
              "count": 2,
              "dimensions": {
                "eventType": "challenge_non_interactive_solved"
              }
            },
            {
              "count": 2,
              "dimensions": {
                "eventType": "challenge_issued"
              }
            }
          ]
        }
      ]
    }
  },
  "errors": null
}

例2

状況

  • チャレンジ発行 = 3
  • チャレンジ成功 = 3
  • トークン検証成功 = 2

ダッシュボード

訪問者解決率 100%(3/3)
API 解決率 66.67%(2/3)

GraphQL

項目 計算 結果
訪問者解決率 (challenge_non_interactive_solved + challenge_interactive_solved) / challenge_issued 100%(3/3)
API 解決率 (challenge_non_interactive_siteverify_solved + challenge_interactive_siteverify_solved) / challenge_issued 66.67%(2/3)
結果
{
  "data": {
    "viewer": {
      "accounts": [
        {
          "turnstileAdaptiveGroups": [
            {
              "count": 3,
              "dimensions": {
                "eventType": "challenge_non_interactive_solved"
              }
            },
            {
              "count": 3,
              "dimensions": {
                "eventType": "challenge_issued"
              }
            },
            {
              "count": 2,
              "dimensions": {
                "eventType": "challenge_non_interactive_siteverify_solved"
              }
            }
          ]
        }
      ]
    }
  },
  "errors": null
}

例3

状況

  • チャレンジ発行 = 13
  • チャレンジ成功 = 11
  • トークン検証成功 = 4

ダッシュボード

訪問者解決率 84.62%(11/13)
API 解決率 30.77%(4/13)

GraphQL

結果
{
  "data": {
    "viewer": {
      "accounts": [
        {
          "turnstileAdaptiveGroups": [
            {
              "count": 13,
              "dimensions": {
                "eventType": "challenge_issued"
              }
            },
            {
              "count": 11,
              "dimensions": {
                "eventType": "challenge_non_interactive_solved"
              }
            },
            {
              "count": 4,
              "dimensions": {
                "eventType": "challenge_non_interactive_siteverify_solved"
              }
            },
            {
              "count": 1,
              "dimensions": {
                "eventType": "challenge_siteverify_failed_double_redemption"
              }
            }
          ]
        }
      ]
    }
  },
  "errors": null
}

未解決の分析

未解決 が 2 あります。その属性を知りたいところです。
ただ、今のところ eventType未解決を示すものはないようにみえます。(継続調査中)

発行されたチャレンジ全体から、どれが未解決のものなのか、あぶり出すことができていません。
それができれば未解決チャレンジの IP アドレス、AS 番号、OS 、ブラウザー情報などを収集できます。

Dimesions すべて取ったら
          "turnstileAdaptiveGroups": [
            {
              "count": 2,
              "dimensions": {
                "action": "ウィジェットの名前",
                "asn": 13335,
                "browserMajor": 129,
                "browserName": "Chrome",
                "countryCode": "JP",
                "date": "2024-10-09",
                "datetime": "2024-10-09T09:55:18Z",
                "datetimeDay": "2024-10-09T00:00:00Z",
                "datetimeFifteenMinutes": "2024-10-09T09:45:00Z",
                "datetimeFiveMinutes": "2024-10-09T09:55:00Z",
                "datetimeHalfOfHour": "2024-10-09T09:30:00Z",
                "datetimeHour": "2024-10-09T09:00:00Z",
                "datetimeMinute": "2024-10-09T09:55:00Z",
                "eventType": "challenge_issued",
                "hostname": "接続先ののホスト名",
                "ipv4": "",
                "ipv6": "アクセス元の IPv6 アドレス",
                "osMajor": 0,
                "osName": "Mac OS X",
                "siteKey": "サイトキー",
                "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
              }
            },
補足

Cloudflare で HTTP リクエストをプロキシーする設計に変更すれば、チャレンジ(WAF)に失敗したリクエストの情報を詳細に得ることができたり、より多層な保護をアプリケーションに実装することができます。
https://developers.cloudflare.com/reference-architecture/architectures/security/
https://www.cloudflare.com/ja-jp/learning/security/what-is-web-application-security/

Discussion