この記事はGraphQL Advent Calendar 2021アドベントカレンダー13日目の記事です。
はじめに
Hasuraという、DBテーブルから自動的にGraphQL APIを作成してくれるGraphQLサーバーがあります。
「Hasuraって何?」という方は、以下の記事を読んでいただけると幸いです。
Hasuraには、APIをさまざまな攻撃から守るための設定が数多く用意されています。
本記事では、HasuraでGraphQLサーバーを運用していくにあたり、設定しておくべきセキュリティ対策設定をチェックリストとしてまとめました。
大半はドキュメントに書いてあることなのですが、日本語情報が少なかったこと&実際に運用して気づいたドキュメントにも書いていないことも盛り込んでいますので、
Hasuraを使用している方に今一度本記事の内容をチェックしていただき、セキュリティ対策チェックの一助としていただければ幸いです。
執筆時点でのHasuraのバージョン
オンプレミス・Cloud版共通
ここでは、Hasuraが提供するCloud版を使用する場合と、Heroku
にデプロイしたりDocker
・Kubernetes
を使用して自前でセルフホストする場合共通してチェックすべき点について述べていきます。
✅ Hasura Admin Secretは設定されているか?
防ぐことができること
・Hasuraコンソール画面への不正アクセス・不正なリソース操作
・GraphQLエンドポイントへの不正なリクエスト
いかなる場合も、Hasura Admin Secretを設定しないという選択肢はありません。
この設定をしていないと、GraphQLサーバーのエンドポイントさえ知っていれば誰でもHasuraインスタンスを好き勝手操作できます。何がともあれ、まずはHasura Admin Secretを設定しましょう。ちなみに、cloud版はデフォルトで設定されています。
設定するには、HASURA_GRAPHQL_ADMIN_SECRET
という環境変数を設定します。この値は環境変数から取得するようにするなど、Gitリポジトリ上から閲覧できないようにすることも忘れずにしてください。
✅ リソース操作(CRUD)に対して、適切なロールが割り振られているか?
防ぐことができること
・未認証、適切ではない権限を持つユーザーリクエストからのリソース操作
例として、認証機能があるアプリケーションを考えます。DBには認証したユーザーに紐づく記事を管理するArticles
というテーブルがあります。もし、パーミッション設定を忘れた場合、クライアントから任意の他のユーザーの記事情報を書き換えられてしまいます。
このような事態を防ぐためには、**「User
ロールを持つ&認証情報のIDとArticles
テーブルのuser_id
が一致する場合にUPDATE
を許可する」**というようなルールを設定する必要があります。
GraphQL採用サービスで任意カラムを取得できる脆弱性を見つけた話のような、個人情報漏洩のリスクを防ぐためにも、全てのテーブルのCRUD
操作に適切な権限が付与されているか確認しましょう。
パーミッション設定は、[DATA]->[対象のテーブル]->[Permissions]から行うことができます。
ここで、DBテーブルへのパーミッションの付与方法にはいくつか種類があります。
✅ Without any checks
「User
ロールに対して、Articles
テーブルのSELECT
操作を許可する」
といったように、ロールに対してCRUD
操作許可を付与したい場合に設定します。
例:マスタテーブルなど、レコードに他から秘匿したい情報がないテーブルの操作
✅ With custom check
「User
ロールかつx-hasura-user-id
とArticle
テーブルのuser_id
が一致する場合にのみ、UPDATE
操作を許可する」
と言ったように、自分でルールをカスタムしたい場合に設定します。
設定できること
共通
- テーブルカラムへの操作権限の付与(Column Permissions)
INSERT
デフォルト値の設定(Column presets)
DBの値にデフォルト値を設定するのと同じ感覚で、ロールごとにINSERT時のデフォルト値を設定できます。「from session variable」にすることで、リクエストヘッダー内のSession Variableにある「x-hasura-**」から、値を読み取ってINSERT時にデフォルト値として設定されます。
「x-hasura-**」のヘッダーが読み取れなかった場合、実行時にエラーが発生します。
クライアントアクセス制限(Backend only)
backend onlyを指定することで、設定したロールはINSERT mutationに下記の条件が揃っている場合にのみ、アクセスできます。
リクエストヘッダ内の
- 「
x-hasura-use-backend-only-permissions
」がtrue - 「
x-hasura-admin-secret
」が設定されている場合
SELECT
取得できる行数の制限(Limit number of rows)
集計クエリの操作権限の付与(Aggregation queries permissions)
aggregate schema
をロールに対して公開するかどうかを設定できます。
ここでいうaggregate schema
とは、合計を返すsum
や平均を返すavg
など便利な集計用のリゾルバがセットになった、基本的なCRUDリゾルバと同じく自動生成してくれる集計用のスキーマのことです。
デフォルトはfalseになっています。
UPDATE
更新前の条件チェック(Pre-update check)
更新前に「idがx-hasura-user-idと一致するリクエストしかUPDATEできないようにする」といった設定ができます。
更新後の条件チェックPost-update check)
更新後に「idがx-hasura-user-idと一致するリクエストしかUPDATEできないようにする」といった設定ができます。
デフォルトで入れる値の設定(Column presets)
INSERTのColumn presetsと同じように、デフォルト値を設定できます。設定方法も同じです。
DELETE
特になし
(補足)Action(Remote Schema)のパーミッションについて
Action・Remote Schemaは、そのアクションの実行を許可するロールの設定しかできません。そのため、「ヘッダー情報のIDとInput
のID
が同じ場合のみ許可する」というような細かなパーミッション設定ができないことになります。
よって、細かな制限は接続先のGraphQLサーバー・REST(ful)サーバーで行う必要があります。接続したAPIサーバーへのリクエストボディのsession_variables
というフィールドにx-hasura-role
とx-hasura-user-id
が入っているので、この値を用いて認可処理を記述することになります。
{
"session_variables":
{
"x-hasura-role": "user",
"x-hasura-user-id": "635efa22-d373-3dc8-285f-490d0b80e0da",
}
}
✅ Hasura GraphQL cors domainは設定されているか?
防ぐことができること
・意図しないオリジンからのリクエスト
✅ 個人情報を含んだログを流していないか?
防ぐことができること
・個人情報を含んだ情報のログ流出
環境変数HASURA_GRAPHQL_ENABLED_LOG_TYPES
、もしくは起動オプション--enabled-log-types
を設定することで、Hasuraサーバー(HerokuやDockerコンテナのログなど)のログ出力を制御できます。
もし、ログタイプでquery-log
を設定している場合は注意が必要です。このログタイプはクライアントからのリクエスト内にあるGraphQLクエリの情報全てをログに出力します。
つまり、個人情報が特定されるようなパラメーターを持つInput・OutputのQueryまたはMutationが叩かれた場合、個人情報が全てログへ流されることになります。
サーバーに侵入された場合はもちろん、NewRelicなどの内部監視ツールと連携していた場合もツール側で何かあった場合に漏洩リスクに繋がるため、特別な事情がない限りログタイプをquery-log
にするのは避けましょう。
Hasura Cloudの場合
ここでは、Hasura Cloudを使用している場合にチェックするべき点について述べていきます。
✅ API Limitsは設定されているか?
防ぐことができること
・Dos攻撃によるサーバーの可用性侵害、サーバーダウン
・操作ミス、クエリ設計ミスによるサーバーの可用性侵害、サーバーダウン
GraphQLでは、重い計算をさせるクエリや大量にDBアクセスを発生させるクエリ、データを大量に取得するクエリなどDBにとって負荷が大きいクエリを簡単に書くことができます。Hasuraには負荷が大きいクエリを走らせないための機能がいくつか用意されているので、ここではそれぞれについて簡単に紹介します。
✅ Depth Limit
一度のリクエストで流すことができるクエリのネストの深さを指定できます。
GraphQLクエリでは、「Usersに紐づく小テーブルのArticlesを取って、そこからUsersを取得して...」というように、再帰的にネストを深くできます。
ネストの深さを指定していない場合、悪意のあるクエリを流されたりクエリの設計ミスをしてしまった際に、DBサーバーに負荷がかかり、最悪の場合ダウンしてしまいます。
必要以上にネストを深くしないよう、制限をかけておきましょう。
✅ Node Limit
一度のリクエストで取得できるノードの数を指定できます。
ノードについての詳しい説明については、https://docs.github.com/ja/graphql/guides/introduction-to-graphql#node こちらの記事等をご参照ください。
✅ Request Rate Limit(Requests Per Minute)
同じクライアントからの単位時間(分)あたりのリクエスト数を制限できます。
- IP Address
- X-hasura-user-id
- その他自分で設定したx-hasura-**のセッション変数
これらの条件を組み合わせて、より細かく設定できます。
✅ Allow Listsは設定されているか?
防ぐことができること
・公開したくしないクエリの実行
こちらは設定しても、しなくてもどちらでも良いと思っています。
Allow listという機能で、実行できるGraphQLクエリを制限できます。
制限するクエリは、過去実行したクエリから選んで指定できます。
叩かれたくないクエリがある場合は設定しておきましょう。
デフォルトで実装されているセキュリティ対策
✅ SQLインジェクション
・想定しない、悪意のあるSQL文の実行
HasuraでGraphQLクエリから生成されるSQLは、libpqのexecParamsまたはexecPreparedを介して実行されるため、Postgresのエスケープルールによってエスケープされます。そのため、たとえ悪意のあるSQLをQuery/mutationに埋め込まれたとしても実行されません。
参考: https://github.com/hasura/graphql-engine/issues/5251
おわりに
ここまで、Hasuraを使用する際にチェックすべきセキュリティ対策設定のチェックリストについて紹介しました。
Hasuraは比較的新しいため、今後もより機能が充実していくことでしょう(個人的にInherited rolesの自由度をもっとあげてほしい)。
Hasuraバージョンアップのリリースノートが公開されたら、ぜひセキュリティの変更や機能追加の部分もチェックしてみてください。