Hasuraを使用するならチェック！ 確認すべきセキュリティ対策設定チェックリスト

この記事は[GraphQL Advent Calendar 2021アドベントカレンダー](https://qiita.com/advent-calendar/2021/graphql)13日目の記事です。

## はじめに
Hasuraという、DBテーブルから自動的にGraphQL APIを作成してくれるGraphQLサーバーがあります。

https://hasura.io/

「Hasuraって何？」という方は、以下の記事を読んでいただけると幸いです。

https://qiita.com/sho-hata/items/2dbd41be42662007071e

Hasuraには、APIをさまざまな攻撃から守るための設定が数多く用意されています。
**本記事では、HasuraでGraphQLサーバーを運用していくにあたり、設定しておくべきセキュリティ対策設定をチェックリストとしてまとめました。**
大半はドキュメントに書いてあることなのですが、日本語情報が少なかったこと＆実際に運用して気づいたドキュメントにも書いていないことも盛り込んでいますので、
Hasuraを使用している方に今一度本記事の内容をチェックしていただき、セキュリティ対策チェックの一助としていただければ幸いです。

### 執筆時点でのHasuraのバージョン
<img src="https://img.shields.io/badge/ Hasura GraphQL Engine-2.0.10-1EB4D4.svg?logo=hasura&style=plastic">　<img src="https://img.shields.io/badge/ Hasura Cloud-2.0.10_cloud.1-1EB4D4.svg?logo=hasura&style=plastic">　

## オンプレミス・Cloud版共通
ここでは、Hasuraが提供するCloud版を使用する場合と、`Heroku`にデプロイしたり`Docker`・`Kubernetes`を使用して自前でセルフホストする場合共通してチェックすべき点について述べていきます。
## ✅ Hasura Admin Secretは設定されているか？

**防ぐことができること**

:::note warn
・Hasuraコンソール画面への不正アクセス・不正なリソース操作
・GraphQLエンドポイントへの不正なリクエスト
:::

**いかなる場合も、Hasura Admin Secretを設定しないという選択肢はありません**。

この設定をしていないと、GraphQLサーバーのエンドポイントさえ知っていれば誰でもHasuraインスタンスを好き勝手操作できます。何がともあれ、まずはHasura Admin Secretを設定しましょう。ちなみに、cloud版はデフォルトで設定されています。

設定するには、`HASURA_GRAPHQL_ADMIN_SECRET`という環境変数を設定します。この値は環境変数から取得するようにするなど、Gitリポジトリ上から閲覧できないようにすることも忘れずにしてください。

## ✅ リソース操作(CRUD)に対して、適切なロールが割り振られているか？

**防ぐことができること**

:::note warn
・未認証、適切ではない権限を持つユーザーリクエストからのリソース操作
:::

例として、認証機能があるアプリケーションを考えます。DBには認証したユーザーに紐づく記事を管理する`Articles`というテーブルがあります。もし、パーミッション設定を忘れた場合、クライアントから任意の他のユーザーの記事情報を書き換えられてしまいます。

このような事態を防ぐためには、**「`User` ロールを持つ＆認証情報のIDと`Articles`テーブルの`user_id`が一致する場合に`UPDATE`を許可する」**というようなルールを設定する必要があります。

[GraphQL採用サービスで任意カラムを取得できる脆弱性を見つけた話](https://zenn.dev/mipsparc/articles/a818970a19ade6)のような、個人情報漏洩のリスクを防ぐためにも、全てのテーブルの`CRUD`操作に適切な権限が付与されているか確認しましょう。

パーミッション設定は、[DATA]->[対象のテーブル]->[Permissions]から行うことができます。

![Screenshot 2021-12-11 23.50.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/082131d8-354c-10d9-73c6-030bd6bcf549.png)

ここで、DBテーブルへのパーミッションの付与方法にはいくつか種類があります。

### ✅ Without any checks
「`User`ロールに対して、`Articles`テーブルの`SELECT`操作を許可する」
といったように、**ロールに対して`CRUD`操作許可を付与したい場合に設定します。**

例：マスタテーブルなど、レコードに他から秘匿したい情報がないテーブルの操作

### ✅ With custom check
「`User`ロールかつ`x-hasura-user-id`と`Article`テーブルの`user_id`が一致する場合にのみ、`UPDATE`操作を許可する」
と言ったように、**自分でルールをカスタムしたい場合に設定します。**

### 設定できること

#### 共通
- テーブルカラムへの操作権限の付与(Column Permissions)

#### INSERT
from sessionbariable![Screenshot 2021-12-12 9.35.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/b33cff9e-4b2e-2391-b550-c7b16308eb38.png)

##### **デフォルト値の設定(Column presets)**
DBの値にデフォルト値を設定するのと同じ感覚で、ロールごとにINSERT時のデフォルト値を設定できます。「from session variable」にすることで、リクエストヘッダー内のSession Variableにある「x-hasura-**」から、値を読み取ってINSERT時にデフォルト値として設定されます。

:::note warn
「x-hasura-**」のヘッダーが読み取れなかった場合、実行時にエラーが発生します。
:::

##### クライアントアクセス制限(Backend only)
backend onlyを指定することで、設定したロールはINSERT mutationに下記の条件が揃っている場合にのみ、アクセスできます。

リクエストヘッダ内の

  - 「`x-hasura-use-backend-only-permissions`」がtrue
  - 「`x-hasura-admin-secret`」が設定されている場合

#### SELECT
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/b2b128f8-8344-2982-c282-fa01286b7b86.png" width=75%>

##### 取得できる行数の制限(Limit number of rows)


##### 集計クエリの操作権限の付与(Aggregation queries permissions)
`aggregate schema`をロールに対して公開するかどうかを設定できます。
ここでいう`aggregate schema`とは、合計を返す`sum`や平均を返す`avg`など便利な集計用のリゾルバがセットになった、基本的なCRUDリゾルバと同じく自動生成してくれる集計用のスキーマのことです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/7ae5518b-92da-5e12-f09c-68d0e3b27054.png" width=25%>

デフォルトはfalseになっています。

#### UPDATE
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/0526e5ff-9cd0-ca5e-d8f7-b8b8c7e70b5c.png" width=50%>

##### 更新前の条件チェック(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`が入っているので、この値を用いて認可処理を記述することになります。

```json:接続したAPIサーバへのリクエストボディ
{
 "session_variables":
  {
    "x-hasura-role": "user",
    "x-hasura-user-id": "635efa22-d373-3dc8-285f-490d0b80e0da",
  }
}
```


## ✅ Hasura GraphQL cors domainは設定されているか？
**防ぐことができること**

:::note warn
・意図しないオリジンからのリクエスト
:::

## ✅ 個人情報を含んだログを流していないか？
**防ぐことができること**

:::note warn
・個人情報を含んだ情報のログ流出
:::

https://hasura.io/docs/latest/graphql/core/deployment/logging.html#log-types

環境変数`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は設定されているか？
**防ぐことができること**

:::note warn
・Dos攻撃によるサーバーの可用性侵害、サーバーダウン
・操作ミス、クエリ設計ミスによるサーバーの可用性侵害、サーバーダウン
:::

GraphQLでは、**重い計算をさせるクエリ**や**大量にDBアクセスを発生させるクエリ**、**データを大量に取得するクエリ**などDBにとって負荷が大きいクエリを簡単に書くことができます。Hasuraには負荷が大きいクエリを走らせないための機能がいくつか用意されているので、ここではそれぞれについて簡単に紹介します。
![Screenshot 2021-12-13 2.44.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/7aa11ab8-28aa-8de3-48da-338ac5bfd7c6.png)

### ✅ 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は設定されているか？
**防ぐことができること**

:::note warn
・公開したくしないクエリの実行
:::

こちらは設定しても、しなくてもどちらでも良いと思っています。
Allow listという機能で、実行できるGraphQLクエリを制限できます。

制限するクエリは、過去実行したクエリから選んで指定できます。
叩かれたくないクエリがある場合は設定しておきましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/402e857c-378f-cd4a-5599-665472d6d9c4.png" width=75%>



## デフォルトで実装されているセキュリティ対策
## ✅ SQLインジェクション

:::note warn
・想定しない、悪意のあるSQL文の実行
:::

HasuraでGraphQLクエリから生成されるSQLは、libpqの[execParams](https://www.postgresql.org/docs/current/libpq-exec.html#LIBPQ-PQEXECPARAMS)または[execPrepared](https://www.postgresql.org/docs/current/libpq-exec.html#LIBPQ-PQEXECPREPARED)を介して実行されるため、Postgresのエスケープルールによってエスケープされます。そのため、たとえ悪意のあるSQLをQuery/mutationに埋め込まれたとしても実行されません。

参考: https://github.com/hasura/graphql-engine/issues/5251

## おわりに
ここまで、Hasuraを使用する際にチェックすべきセキュリティ対策設定のチェックリストについて紹介しました。
Hasuraは比較的新しいため、今後もより機能が充実していくことでしょう（個人的に[Inherited rolesの自由度をもっとあげてほしい](https://github.com/hasura/graphql-engine/issues/6991))。
Hasuraバージョンアップのリリースノートが公開されたら、ぜひセキュリティの変更や機能追加の部分もチェックしてみてください。