💡

Cloudflare API Shield の Schema Validation が無償化されたので改めて設定手順

2024/10/21に公開

Cloudflare は API Shield という API (Application Programing Interface) を保護するサービスを提供しています。APIの通信は通常のHTML通信と異なりプログラムからコールされるためWAFや対DDoSが提供しているクライアントブラウザを判別する仕組みとは別の保護が要求されます。またPublicに公開されるウェブサーバのHTMLやCSS、画像と異なりJSON等で機密性の高いデータが流れるケースも多く、その中身の文字列や構造を読み取ってセキュリティを実装することが求められます。それを実現するものが API Shield です。REST APIに対応しています。

主な機能として以下が提供されます。
1.認証と認可: APIへのアクセスを制限し、認証されたユーザーのみが利用できるようにします。
2.レート制限: 不正なアクセスやボットによる攻撃を防ぐため、API呼び出しの頻度を制限します。
3.Bot Management: 自動化されたボットからの攻撃を識別し、対策を講じます。
4.WAF(Web Application Firewall)連携: 悪意のあるリクエストを検出し、ブロックすることで、APIの安全性を高めます。
5. Schema Validation: APIが受け付けるリクエストの構造やタイプを定義し、定義外の通信を遮断します。

今回 Schema Validation の機能が無料プランを含めた全Cloudflareユーザーで利用可能になりましたのでその設定手順を以下にまとめます。

さっそくやってみる

1. APIの作成とCloudflare経由の通信の設定

検証のためにAPIを作成するのは難易度が高く手順が煩雑になるため、すでに存在しているものを借ります。
http://httpbin.org/
というウェブサイトが存在します。

POSTボタンをクリックします。

Try it outExecuteをクリックします。そうすると以下の通り何某かのレスポンスが出力されます。

次にnameageという2つのパラメータに値を投げ込む以下のcurlコマンドを実行します。

curl -X POST "http://httpbin.org/post" -H "accept: application/json" -d '{"name": "John Doe", "age": 30}' -H "Content-Type: application/json"

以下のようなエコーレスポンスが戻ってくれば成功です。POSTされた値が戻って出力されています。

{
  "args": {}, 
  "data": "{\"name\": \"John Doe\", \"age\": 30}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "application/json", 
    "Content-Length": "31", 
    "Content-Type": "application/json\u00e3\u0080\u0080-v", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/8.5.0", 
    "X-Amzn-Trace-Id": "Root=1-6715d838-3c20d3bd5bc237de657c5291"
  }, 
  "json": {
    "age": 30, 
    "name": "John Doe"
  }, 
  "origin": "18.183.160.174", 
  "url": "http://httpbin.org/post"
}

ヘッダーに出力されているX-Amzn-Trace-IdはおそらくこのサイトがAWSのApplication Load Balancerを使っていることを示唆していますが、一般的なRESTのResponseには含まれません。
このサイトが素晴らしいのはテスト用に外部の任意のドメインからのCNAME設定を許可していることです。ではCloudflare側でCNAMEを追加します。(この手順をためすにはCloudflare側でドメイン名を持っておく必要があります)

設定が出来たら再度curlを設定したドメインに対して実行します。

curl -X POST "http://a.harunobukameda.com/post" -H "accept: application/json" -d '{"name": "John Doe", "age": 30}' -H "Content-Type: application/json"

先ほどと同様にレスポンスが戻れば成功です。

2. API Shield の有効化とテスト

次にマネージメントコンソール左ペインでSecurityAPI Shieldをクリックします。

Add endpointsをクリックします。

以下のようにルート/に対するPOSTを保護対象と設定しAdd endpointsをクリックします。

この状態で先ほどのcurlコマンドを5回ほど実行して数分待ちます。
面倒な場合以下でcron実行も可能です。

watch -n 1 'curl -X POST "http://a.harunobukameda.com/post" -H "accept: application/json" -d "{\"name\": \"John Doe\", \"age\": 30}" -H "Content-Type: application/json"'

以下のようにアクセス解析が開始されました。

有償版であればさらに追加機能として、ウェブ管理者が認識していないAPIへの通信が存在していた場合、その検知も行われます。
次にSchema Validationのタブをクリックします。

現時点ではまだSchemaが登録されていないためno schemaとなっています。
Add validationをクリックします。

以下のJSONファイルをAPIスキーマとしてアップロードします。このスキーマはOpenAPI 3.0に準拠しているyamlかjsonを登録可能です。

{
  "openapi": "3.0.0",
  "info": {},
  "servers": [
    {
      "url": "https://a.harunobukameda.com"
    }
  ],
  "components": {},
  "paths": {
    "/post": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "age": {
                    "type": "integer"
                  }
                },
                "required": ["name", "age"],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    }
  }
}

シンプルにまとめると/postに対して以下が定義されています。
・POSTメソッドのみ受け付け
・content-typeはapplication/jsonのみ
・string型のnameとinteger型のageのみパラメータとして受付可能でこの2つは必須
・それ以外のパラメータは受け付け不可
という定義がされています。
set actionBlockを指定します。この定義に合致しないリクエストは受け付けない、という意味です。無償版ではLogモードは設定できません。

設定が完了するとデプロイが行われるまで数分待ちます。以下のようにActive schemaに先ほどのjsonがセットされていればデプロイ完了です。

先ほどのcurlコマンドを実行すると正しいレスポンスが引き続き戻ってきますが、例えば以下のように余分なパラメータを付け加えた場合エラーが戻ります。

curl -X POST "http://a.harunobukameda.com/post" -H "accept: application/json" -d '{"name": "John Doe", "age": 30、"test":"kameda"}' -H "Content-Type: application/json"
error code: 1020

マネージメントコンソールでは以下の通り不正なアクセスがあったことがわかります。

SecurityEventではどのような理由で通信がブロックされているか、を確認可能です。

Discussion