コンテンツにスキップ

設定

Cloudflare GraphQL APIを使用して、GraphQL APIの現在の使用状況に関するデータを収集し、CloudflareのGraphQL悪意のあるクエリ保護を設定して、悪意のあるクエリをログまたはブロックします。

はじめに

クエリサイズは、クエリ内の端末フィールド(葉)の数として定義され、クエリの深さは葉が存在する最も深いレベルです。たとえば、このクエリのサイズは 4 (terminalField[1-4] がこのカウンターに寄与します) と報告され、深さは 3 (terminalField3 と terminalField4 が深さレベル3にあります) と報告されます。

GraphQLクエリ
{
terminalField1
nonTerminalField1(filter: 123) {
terminalField2
nonTerminalField2 {
terminalField3
terminalField4
}
}
}

GraphQL統計を収集する

Cloudflare GraphQL APIの新しい apiGatewayGraphqlQueryAnalyticsGroups ノードを使用して、apiGatewayGraphqlQuerySize および apiGatewayGraphqlQueryDepth の次元を取得できます。

GraphQLクエリ
query ApiGatewayGraphqlQueryAnalytics($zoneTag: string, $datetimeStart: Time, $datetimeEnd: Time) {
viewer {
zones(filter: {zoneTag: $zoneTag}) {
apiGatewayGraphqlQueryAnalyticsGroups(limit: 100, orderBy: [apiGatewayGraphqlQuerySize_DESC, apiGatewayGraphqlQueryDepth_DESC], filter: {datetime_geq:$datetimeStart, datetime_leq:$datetimeEnd}) {
count
dimensions {
apiGatewayGraphqlQuerySize
apiGatewayGraphqlQueryDepth
}
}
}
}
}

上記のクエリを使用すると、次のような応答が得られます。

応答
{
"data": {
"viewer": {
"zones": [
{
"apiGatewayGraphqlQueryAnalyticsGroups": [
{
"count": 10,
"dimensions": {
"apiGatewayGraphqlQueryDepth": 1,
"apiGatewayGraphqlQuerySize": 11
}
},
{
"count": 10,
"dimensions": {
"apiGatewayGraphqlQueryDepth": 1,
"apiGatewayGraphqlQuerySize": 2
}
}
]
}
]
}
},
"errors": null
}

応答の例では、Cloudflareは選択した時間枠内で深さ1およびサイズ11のリクエストを10件、深さ1およびサイズ2のリクエストを10件観測しました。

GraphQL統計を分析する

応答を使用して、属性にわたるパーセンタイルを計算し、許可されるしきい値を設定できます。たとえば、クエリサイズや深さに対して 1.5 * p99 のような単純なヒューリスティックを使用できます。

以下は、上記のGraphQL API応答出力(JSONファイル)を受け取ってクエリサイズと深さのpレベルを報告するシンプルなPythonスクリプトです。

Pythonスクリプト
#!/usr/bin/env python3
import json
import numpy as np
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--response", help="apiGatewayGraphqlQueryAnalyticsGroupsノードを含むAPI JSON応答ファイルへのパス", required=True)
args = parser.parse_args()
with open(args.response) as f:
query_sizes = np.array([], dtype=np.uint16)
query_depths = np.array([], dtype=np.uint8)
data = json.load(f)['data']['viewer']['zones'][0]['apiGatewayGraphqlQueryAnalyticsGroups']
for datapoint in data:
query_sizes = np.append(query_sizes, [datapoint['dimensions']['apiGatewayGraphqlQuerySize']] * datapoint['count'])
query_depths = np.append(query_depths, [datapoint['dimensions']['apiGatewayGraphqlQueryDepth']] * datapoint['count'])
quantiles = [0.99, 0.95, 0.75, 0.5]
print('\n'.join([f"クエリサイズ {int(q * 100)}パーセンタイルは {v}" for q, v in zip(quantiles, np.quantile(query_sizes, quantiles))]))
print('\n'.join([f"クエリ深さ {int(q * 100)}パーセンタイルは {v}" for q, v in zip(quantiles, np.quantile(query_depths, quantiles))]))

上記のクエリを使用すると、次のような出力が得られます。

例の出力
./calculator.py --response=response.json
クエリサイズ 99パーセンタイルは 11.0
クエリサイズ 95パーセンタイルは 11.0
クエリサイズ 75パーセンタイルは 11.0
クエリサイズ 50パーセンタイルは 6.5
クエリ深さ 99パーセンタイルは 1.0
クエリ深さ 95パーセンタイルは 1.0
クエリ深さ 75パーセンタイルは 1.0
クエリ深さ 50パーセンタイルは 1.0

受信するGraphQLクエリの制限を設定する

API Shieldの顧客は、カスタムルールで利用可能な3つの新しいフィールドを持っています。

  • cf.api_gateway.graphql.query_size は、GraphQLクエリのサイズを説明します。
  • cf.api_gateway.graphql.query_depth は、GraphQLクエリの深さを説明します。
  • cf.api_gateway.graphql.parsed_successfully は、Cloudflareがクエリを解析できたかどうかを説明します。現在、私たちは最善の努力で解析を行っており、一部の有効なクエリを解析できない場合があります。これは、GraphQLセキュリティルールを展開する際にカスタムルールで and cf.api_gateway.graphql.parsed_successfully フィルターを使用する必要があることを意味します。

たとえば、次のルールをAPIまたはダッシュボードを介して展開して、深くネストされたクエリをブロックし、いくつかのフィールドを要求することができます。

(cf.api_gateway.graphql.query_size < 3 and cf.api_gateway.graphql.query_depth > 15 and cf.api_gateway.graphql.parsed_successfully)