設定
Cloudflare GraphQL APIを使用して、GraphQL APIの現在の使用状況に関するデータを収集し、CloudflareのGraphQL悪意のあるクエリ保護を設定して、悪意のあるクエリをログまたはブロックします。
クエリサイズは、クエリ内の端末フィールド(葉)の数として定義され、クエリの深さは葉が存在する最も深いレベルです。たとえば、このクエリのサイズは 4 (terminalField[1-4] がこのカウンターに寄与します) と報告され、深さは 3 (terminalField3 と terminalField4 が深さレベル3にあります) と報告されます。
{ terminalField1 nonTerminalField1(filter: 123) { terminalField2 nonTerminalField2 { terminalField3 terminalField4 } }}Cloudflare GraphQL APIの新しい apiGatewayGraphqlQueryAnalyticsGroups ノードを使用して、apiGatewayGraphqlQuerySize および apiGatewayGraphqlQueryDepth の次元を取得できます。
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件観測しました。
応答を使用して、属性にわたるパーセンタイルを計算し、許可されるしきい値を設定できます。たとえば、クエリサイズや深さに対して 1.5 * p99 のような単純なヒューリスティックを使用できます。
以下は、上記のGraphQL API応答出力(JSONファイル)を受け取ってクエリサイズと深さのpレベルを報告するシンプルなPythonスクリプトです。
#!/usr/bin/env python3
import jsonimport numpy as npimport 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.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フィルターを使用する必要があることを意味します。
たとえば、次のルールをAPIまたはダッシュボードを介して展開して、深くネストされたクエリをブロックし、いくつかのフィールドを要求することができます。
(cf.api_gateway.graphql.query_size < 3 and cf.api_gateway.graphql.query_depth > 15 and cf.api_gateway.graphql.parsed_successfully)