概要
上記を読んで、先日つくったGraphQLアプリに対策を施すことができるか確認した。
先日作ったGraphQLアプリ
やったこと
- introspection対策
- field suggestion対策
- 再帰的Fragment対策
- alias対策
やっていないこと
- persisted queryの導入
- 調査時間不足で全体像が分からず断念
- extensionsに実装は存在する
introspection対策、field suggestion対策
この2つはメソッドを呼ぶだけで対策が可能。
SchemaBuilder
型のメソッドに disable_introspection()
disable_suggestions()
があるので、それらを呼ぶだけ。
例えば、releaseビルドでのみ無効化したいときは、以下のようにする。
let s: SchemaBuilder<_, _, _> = ...; if cfg!(not(debug_assertions)) { s = s.disable_suggestions().disable_introspection(); }
再帰的Fragment対策
元々対策されている模様。
(……が、エラーメッセージが「再帰深度の最大は32です」なので確認したほうがよさそう)
query { user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { ...A } } fragment A on User { name ...B } fragment B on User { ...A }
{ "data": null, "errors": [ { "message": "The recursion depth of the query cannot be greater than `32`", "locations": [ { "line": 12, "column": 20 } ] } ] }
alias対策
aliasはどれだけ多くても許可するようになっている。以下のように10000個のaliasをリクエストしたが普通にresponseが返ってきた。
{ alias0: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { name } alias1: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { name } # ... alias9999: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { name } }
これに対策するには独自のExtensionの実装が必要。以下に記載がある。
この中の parse_query
を利用する。
let result = next.run(ctx, query, variables).await?;
とすると、resultの中からパース済みのGraphQL queryを取り出せるので、そこからaliasを見つけてカウントしていき、エラーを出すようにする。
今回は以下のように制限した。
- aliasは全部で10個まで
- 各階層(ネストの深さ)ごとに、aliasは合計3つまで
全体コードは以下。
実装のコアは以下。
#[async_trait] impl Extension for RestrictQueryAliasesImpl { async fn parse_query( &self, ctx: &ExtensionContext<'_>, query: &str, variables: &Variables, next: NextParseQuery<'_>, ) -> ServerResult<ExecutableDocument> { let result = next.run(ctx, query, variables).await?; // after parsed let query_selection_set = find_query(&result); let aliases = alias_count_recursive(&query_selection_set); if aliases > self.limit { Err(ServerError::new( format!("エイリアスは全部で{}個より多くできません", self.limit), None, ))?; } let aliases_per_level = alias_count_by_nested_level_recursive(&query_selection_set, 1); let max_aliases_per_level = max_value_or(aliases_per_level, 0); if max_aliases_per_level > self.limit_per_level { Err(ServerError::new( format!( "エイリアスは各階層で{}個より多くできません", self.limit_per_level ), None, ))?; } Ok(result) } }
このextensionをSchemaBuilderで s.extension(RestrictQueryAliases::default())
として作成すればOK。
同じ階層に4つ以上aliasをいれてリクエストをすると以下のようにエラーになる。
{ alias0: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { name } alias1: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { name } alias2: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { name } alias3: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { n0: name n1: name n2: name ownedBoards { b0: id b1: id b2: id owner { id } } } }
{ "data": null, "errors": [ { "message": "エイリアスは各階層で3個より多くできません" } ] }
同じ階層ではaliasを3つ以内に押さえていても、全体で10個以上になるリクエストは以下のようにエラーになる。
{ alias0: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { name } alias1: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { name } alias3: user(id: "user-01HBCCGK3MG5HA7GJG25BGV6PJ") { n0: name n1: name n2: name ownedBoards { b0: id b1: id b2: id owner { id i0: id i1: id } } } }
{ "data": null, "errors": [ { "message": "エイリアスは全部で10個より多くできません" } ] }