🕌

Cloudflare API Shield を JWT 門番にする

2024/12/02に公開

Cloudflare の API Shield では mTLS での認証のほかにも JWT 検証の機能があるということで、その動作確認を行います。

手っ取り早く、下記の手順で行います。

  1. /login エンドポイントは Cloudflare Access の Service Token で認証し、認可も OK なら JWT を返す(認証認可されなければブロック)
  2. /private エンドポイントは JWT を Cloudflare API Shield で検証する(検証できなければブロック)
  3. WAF Custom Rules でこれら以外のパスはブロック

今回 1 は JWT 取るためのオマケで(他の環境のほうがより現実的と思います)、2 が本編です。

1. Access で認証認可、 JWT 入手

Access に認証認可をさせ、JWT を渡すことができます。
ここでは、あらかじめ作成した Service Token(ID と Secret)を HTTP リクエストに含んで認証する方法を使ってみます。

1-1. Access Service Token を作る

1-2. Access Applications の Self-hosted アプリを /login で定義し、Service Auth のポリシーを付与

Service Auth rules enforce authentication flows that do not require an identity provider IdP login, such as service tokens and mutual TLS.

Service Token の ID と Secret をヘッダーにつけて /login に接続すると、認証認可が成功し、 CF_Authorization Cookie に JWT が格納されました。

$ curl "https://api0.oymk.work/login" -H "CF-Access-Client-Id: $CID" -H "CF-Access-Client-Secret: $SCR" -c access.cookie

$ cat access.cookie

#HttpOnly_api0.oymk.work	FALSE	/	TRUE	1733131099	CF_Authorization	eyJ....

2. API Shield で JWT を検証

一方 /private では Access で署名された適正な JWT がないと接続できないようにします。
これを API Shield でやってみます。

2-1. JWK の入手

Cloudflare Access の場合https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/certs で公開されます。
一世代前と現行の鍵が表示されるので現行(public_certkid)に合致する key を取ります。

As shown in the example below, https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/certs contains two public keys: the current key used to sign all new tokens, and the previous key that has been rotated out.

keys: both keys in JWK format
public_cert: current key in PEM format
public_certs: both keys in PEM format

2-2. API Shield で JWT 検証設定

JWT の格納場所は Cookie あるいはヘッダーを指定できます(2024年12月時点)。
今回はヘッダーを利用してみました(cf_authorization_test という名前)。

Token Key に 2-1 で入手した公開鍵の情報を貼り付け、追加します。

追加した鍵情報やエンドポイントを指定し、ルールを定義します。
Token がない場合には non-complient 扱いにし、その場合の Action は Block としました。


2-3. API Shield 動作確認

まずば正当なアクセス。
200 OK が返ります。

# curl の cookie 出力から JWT 値を得る
$ TKN=`awk '/CF_Authorization/ {print $7}' access.cookie`

# 値をヘッダーに入れて送る
$ curl -v "https://api0.oymk.work/private" -H "cf_authorization_test: $TKN"

< HTTP/2 200

次に、ヘッダー無しで送ってみます。

$ curl -v "https://api0.oymk.work/private"

< HTTP/2 403

error code: 1020%

error code: 1020 という応答です。
Firewall Event を見ると API Shield - Token Validation でブロックされていることがわかります。

API Shield の公開鍵を一世代前に入れ替えてみます。
期待値は現行の鍵で署名された JWT がブロックされることです。

入れ替えると、即座にブロックされ始めました。

$ curl -v "https://api0.oymk.work/private" -H "cf_authorization_test: $TKN"

< HTTP/2 403

error code: 1020%

その他、検証の詳細や Cloudflare API からの操作は Dev docs が参考になります。

2-4. 鍵のローテーションに対応

運用に際しては、JWK のアップデートへの追随が重要になります。
(鍵は 4 つ登録できるので、実際の運用でのスムーズな移行が考慮されています)

API Shield での鍵ローテーションについては Workers を使って Cloudflare API から更新する例が紹介されていますので、他の ID プロバイダーでも流用できると思います。

おわりに

以上、Workers/Snippets や Access 背後のオリジンでの JWT 検証に加え API Shield でも簡素にきちんとシールドできることを確認できました。

ほかにも API Shiled の JWT 検証を利用して user クレームが admin のときにリクエストの WAF アタックスコアが低ければマネージドチャレンジを掛けるというような WAF Custom Rule の例もありましたので、ユースケースに柔軟に対応できそうです。

Discussion