🛝

Cloudflare の設定を時間で切り替える

2024/07/18に公開

Cloudflare の Custom Pages(WAF のブロックページ)を時間帯によって切り替えたいというお話がありました。

とりええず思いつくのは

  1. TerraformPulmi
  2. Workers Cron Triggers

でした。 2 を試します。

準備

Cloudflare 設定方針の決定

Custom Pages(WAF)の変更は Account レベル・Zone レベルどちらにも設定項目があります。
Zone レベルで行きます。

WAF 側の設定

WAF ルールの Action で、Block の際に Custome Pages を使うようにしておきます。

Custome Pages の設定


ダッシュボードから Help を見ると、指定した URL にブロックページの素材を取りに行くという動きになります。なので、同じ URL でコンテンツを変えた場合は、その URL に対して再取得が必要になります。

ページ切り替え方針

毎時 0 分と 30 分でブロックページを切り替えます。
下記 2 つの方法が取れそうですが、今回は 1 で行きます。

  1. 違う URL を指定
  2. 同じ URL を指定、コンテンツ側で時間ごとに差し替え

Cloudflare API エンドポイントの確認

API エンドポイントを見つける

方針は決まりました。
次は Custom Pages(WAF)を変更する API を知る必要があります。

どの API エンドポイントを使えばいいかはダッシュボードの API をクリックすると表示されます。

Full API documentation をクリックすると Cloudfoare API へ移動するので、簡単にエンドポイントのドキュメントにたどり着けます。

Cloudflare API ページに直で行く場合は、検索窓で探せます。
Custom pages for a zone が見つかります。

API エンドポイントを知る

たどり着いたエンドポイントの説明ドキュメントを読みます。

リクエストを作ろうとしているので、ページ中の Request の部分に注目します。
Path ParametersBody にある Required 項目を事前に用意する必要があります。

ただ、具体的に何を入れたらいいのかわからないところがあります。
indentifierstate 何ですかね?

API エンドポイントを試して深堀る

ドキュメントに詳細がなかったり、具体的な内容を知りたい場合は、一度ダッシュボードで仮の設定を入れて、API で戻り値を見るのもアリです。

今回の場合、パスの identifier に何を入れればいいのかわからないので、List エンドポイントから GET してみます。


identifierwaf_blockstatecustomized にすればよさそうです。

Cloudflare API エンドポイント向け Token の作成

見つかった API エンドポイントを操作できる権限の Token を作成します。
Custom Token を選択します。

Custome Pages の Edit 権限を付与します。

作成された Token をコピーします。

Workers 実装

さあ、作りましょう。

プロジェクトの作成

npm create cloudflare@latest

type は Scheduled Worker (Cron Trigger) ですね。

output

using create-cloudflare version 2.22.0

╭ Create an application with Cloudflare Step 1 of 3

├ In which directory do you want to create your application?
│ dir ./cf-config-test

├ What type of application do you want to create?
│ type Scheduled Worker (Cron Trigger)

├ Do you want to use TypeScript?
│ no typescript

├ Copying template files
│ files copied to project directory

├ Updating name in package.json
│ updated package.json

├ Installing dependencies
│ installed via npm install

╰ Application created

╭ Configuring your application for Cloudflare Step 2 of 3

├ Retrieving current workerd compatibility date
│ compatibility date 2024-07-12

├ Do you want to use git for version control?
│ no git

╰ Application configured

╭ Deploy with Cloudflare Step 3 of 3

├ Do you want to deploy your application?
│ no deploy via npm run deploy

├ APPLICATION CREATED Deploy your application with npm run deploy

│ Navigate to the new directory cd cf-config-test
│ Run the development server npm run start
│ Deploy your application npm run deploy
│ Read the documentation https://developers.cloudflare.com/workers
│ Stuck? Join us at https://discord.cloudflare.com

╰ See you again soon!

変数の準備

Cron Triggers から Cloudflare の API を叩くので、秘匿な内容を secret で登録し、変数で呼べるようにしておきます。

  • 認証トークン
  • Zone ID

を対象にします。

cd cf-config-test/
npx wrangler secret put CUSTOM_PAGE_RW
npx wrangler secret put ZONE_ID
output

⛅️ wrangler 3.64.0
✔ Select an account › 0_Example
✔ Enter a secret value: … ****************************************
🌀 Creating the secret for the Worker "cf-config-test"
✨ Success! Uploaded secret CUSTOM_PAGE_RW

wrangler toml

triggers毎時 0 分と 30 分で発火🔥時刻を設定します。

name = "cf-config-test"
main = "src/index.js"
compatibility_date = "2024-07-12"
[triggers]
crons = ["0 * * * *", "30 * * * *"]

src/index.js

triggers で指定した条件で、それぞれ異なるブロックページを指定します。
複数の Cron Trigger 指定なので、こちらを参考にしました。

export default {
  async scheduled(event, env, ctx) {
   const apiToken = env.CUSTOM_PAGE_RW;
   const zoneApi = "https://api.cloudflare.com/client/v4/zones/";
   const zoneId = env.ZONE_ID;
   const pageId = "waf_block";
   const apiEp = zoneApi + zoneId + "/custom_pages/" + pageId;
   async function callApi(blockpage) {
    const body = {
      url: blockpage,
      state: "customized"
    };
    const init = {
      body: JSON.stringify(body),
      method: "PUT",
      headers: {
        "content-type": "application/json",
        "authorization": "Bearer " + apiToken
      }
    };
    try {
      const response = await fetch(apiEp, init);
      console.log(response.status);
      if (!response.ok) {
        const errorJson = await response.json();
        console.log(errorJson);
      }
      return response.status;
    } catch (error) {
      console.error(`error: ${error}`);
    }
  }
  switch (event.cron) {
    case "0 * * * *":
      ctx.waitUntil(callApi("https://block.oymk.work/first.html/"));
      break;
    case "30 * * * *":
      ctx.waitUntil(callApi("https://block.oymk.work/second.html"));
      break;
  }
  console.log("cron processed");
  },
};

デプロイ

デプロイします。

npm run deploy
output

⛅️ wrangler 3.64.0

Total Upload: 1.31 KiB / gzip: 0.61 KiB
Uploaded cf-config-test (2.03 sec)
Published cf-config-test (0.93 sec)
schedule: 0 * * * *
schedule: 30 * * * *
Current Deployment ID:
Current Version ID:

ローカルでのテストはこちらに記載があります。

結果

時間によって切り替わることが確認できました。

トラブルシュート

当初、うまく設定が入りませんでした。
切り分けのためにログを拾うことを考えました。
API ドキュメントを見ると、失敗時 4xx も Body は JSON とのことです。

console.log() を途中に入れて、動作を確認しました。

npx wrangler tail

ブロックページの Body が 100 文字以下ってことでエラーになってました。

失敗 4xx

(log) cron processed
(log) 400
(log) {
result: null,
success: false,
errors: [
{
code: 1203,
message: 'Your custom page must be larger than 100 characters.'
}
],
messages: []
}

原因がわかれば、対応できますね。

成功 200

"30 * * * *" @ 7/16/2024, 9:30:45 PM - Ok
(log) cron processed
(log) 200

以上です。

他の設定項目でも、時間に応じて設定を変更したいときには Cron Triggers 使えそうです。

Discussion