Cloudflare GraphQL API を使い、GraphQL API の現在の利用状況を把握し、Cloudflare の GraphQL 悪意あるクエリ保護で悪意のあるクエリを記録またはブロックできます。
クエリサイズは、クエリ内の終端フィールド(リーフ)の数です。クエリの深さは、リーフが存在する最も深いレベルです。たとえば、このクエリのサイズは 4 (terminalField[1-4] all contribute to this counter) と報告され、深さは 3 (terminalField3 and terminalField4 are at depth level 3) と報告されます。
{
terminalField1
nonTerminalField1(filter: 123) {
terminalField2
nonTerminalField2 {
terminalField3
terminalField4
}
}
}Cloudflare GraphQL API の apiGatewayGraphqlQueryAnalyticsGroups ノードを使い、apiGatewayGraphqlQuerySize と apiGatewayGraphqlQueryDepth のディメンションを取得できます。
query ApiGatewayGraphqlQueryAnalytics(
$zoneTag: string
$start: Time
$end: Time
) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
apiGatewayGraphqlQueryAnalyticsGroups(
limit: 100
orderBy: [
apiGatewayGraphqlQuerySize_DESC
apiGatewayGraphqlQueryDepth_DESC
]
filter: { datetime_geq: $start, datetime_leq: $end }
) {
count
dimensions {
apiGatewayGraphqlQuerySize
apiGatewayGraphqlQueryDepth
}
}
}
}
}上のクエリを実行すると、次のレスポンスが返ります。
{
"data": {
"viewer": {
"zones": [
{
"apiGatewayGraphqlQueryAnalyticsGroups": [
{
"count": 10,
"dimensions": {
"apiGatewayGraphqlQueryDepth": 1,
"apiGatewayGraphqlQuerySize": 11
}
},
{
"count": 10,
"dimensions": {
"apiGatewayGraphqlQueryDepth": 1,
"apiGatewayGraphqlQuerySize": 2
}
}
]
}
]
}
},
"errors": null
}このレスポンス例では、選択した期間に、深さ 1・サイズ 11 のリクエストが 10 件、深さ 1・サイズ 2 のリクエストが 10 件観測されています。
レスポンスを使い、各属性のパーセンタイルを計算し、許可するしきい値を設定できます。たとえば、クエリサイズや深さに 1.5 * p99 のような単純なヒューリスティックを使えます。
上の GraphQL API レスポンス(JSON ファイル)を入力として、クエリサイズと深さのパーセンタイルを出力する、簡単な Python スクリプトです。
#!/usr/bin/env python3
import json
import numpy as np
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--response", help="Path to the API JSON response file with the apiGatewayGraphqlQueryAnalyticsGroups node", 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"Query size {int(q * 100)}th percentile is {v}" for q, v in zip(quantiles, np.quantile(query_sizes, quantiles))]))
print('\n'.join([f"Query depth {int(q * 100)}th percentile is {v}" for q, v in zip(quantiles, np.quantile(query_depths, quantiles))]))上のスクリプトを実行すると、次の出力が得られます。
./calculator.py --response=response.json
Query size 99th percentile is 11.0
Query size 95th percentile is 11.0
Query size 75th percentile is 11.0
Query size 50th percentile is 6.5
Query depth 99th percentile is 1.0
Query depth 95th percentile is 1.0
Query depth 75th percentile is 1.0
Query depth 50th percentile is 1.0API 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フィルターを含めてください。
たとえば、深く入れ子になっていて 30 を超えるフィールドを要求するクエリをブロックするには、API またはダッシュボードで次のルールをデプロイできます。
(cf.api_gateway.graphql.query_size > 30 and cf.api_gateway.graphql.query_depth > 7 and cf.api_gateway.graphql.parsed_successfully)