Cloudflare Workersを利用してmTLS + OCSP失効確認
概要
このブログでは、Cloudflare環境での持ち込みCA(BYOCA)を利用してクライアント証明書認証+O/OUチェック、OCSPレスポンダーへの失効確認する方法説明します。
2026年1月現在、CloudflareのマネージドCAにてクライアント証明書認証とクライアント証明書の失効には対応していますが、持ち込みCA(BYOCA)の手動失効確認、さらにOCSPレスポンダーの失効確認は、Workersを利用する必要があります。本ブログではWorkersを利用した形を試してみたいと思います。
アクセス制御の仕組み:
https://www.example.com/ → 証明書不要(通常アクセス)
https://www.example.com/public/* → 証明書不要(通常アクセス)
https://www.example.com/mtls/* → 証明書必須(mTLS認証)
https://www.example.com/api/* → 証明書必須(mTLS認証)
設定の概念

前提条件
| 項目 | 要件 |
|---|---|
| Cloudflareプラン | Enterprise |
| 対象ドメイン | Cloudflareでプロキシ済み |
| Node.js | v18以上 |
| Wrangler | v3以上 |
必要な情報一覧
| 項目 | 説明 | 例 |
|---|---|---|
| Zone ID | 対象ドメインのZone ID | 1234567890abcdef... |
| Account ID | CloudflareアカウントID | abcdef1234567890... |
| API Token | 必要権限付きトークン | Bearer xxxxx |
| Root CA証明書 | ルートCA (PEM形式) | root-ca.crt |
| 対象FQDN | mTLSを有効にするホスト名 | www.spt-lab.net |
| 対象パス | 証明書必須とするパス | /mtls/* |
| 許可するO | Organization値 | Networld Demo |
| 許可するOU | Organizational Unit値 | Sect.2 |
認証フロー(シーケンス図)






検証フローチャート


デプロイ手順チェックリスト
□ Step 1: APIトークン作成
□ Step 2: CA証明書アップロード(BYOCA)
□ Step 3: ホスト名関連付け
□ Step 4: クライアント証明書ヘッダー転送有効化
□ Step 5: WAFカスタムルール作成(パス制限)
□ Step 6: Workersプロジェクト作成
□ Step 7: wrangler.toml設定
□ Step 8: シークレット設定
□ Step 9: Workersデプロイ
□ Step 10: 動作確認
Step 1: APIトークン作成
1-1. Cloudflare Dashboardでトークン作成
- My Profile → API Tokens → Create Token
- Create Custom Token を選択
- 以下の権限を設定:
| Permission | Access |
|---|---|
| Account > Account: SSL and Certificates | Edit |
| Account > Access: mTLS証明書 | Edit |
| Account > Workersスクリプト | Edit |
| Zone > SSL and Certificates | Edit |
| Zone > Zone | Read |
| Zone > Wokers Root | Edit |
- Account Resources: Include > 対象アカウント
- Zone Resources: Include > Specific zone > 対象ドメイン
- Create Token → トークンをコピー
1-2. 環境変数を設定
# 環境変数を設定
export CF_API_TOKEN="your_api_token_here"
export CF_ACCOUNT_ID="your_account_id_here"
export CF_ZONE_ID="your_zone_id_here"
# 設定確認
echo "Account ID: ${CF_ACCOUNT_ID}"
echo "Zone ID: ${CF_ZONE_ID}"
1-3. トークン検証
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json"
期待されるレスポンス:
{
"success": true,
"result": { "status": "active" }
}
Step 2: CA証明書アップロード(BYOCA)
2-1. CA証明書をアップロード
ここで言うCA証明書は、サーバ証明書ではなくてクライアント証明書認証で利用するCAです。
検証では中間証明書はなしでやりましたが、中間認証局がある場合は1ファイルにまとめておく必要があると思います。
エンドポイント: POST /accounts/{account_id}/mtls_certificates
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/mtls_certificates" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{
\"name\": \"my-client-ca\",
\"ca\": true,
\"certificates\": \"$(cat root-ca.crt | sed ':a;N;$!ba;s/\n/\\n/g')\"
}"
2-2. レスポンスからIDを取得
成功時のレスポンス:
{
"success": true,
"result": {
"id": "2458ce5a-0c35-4c7f-82c7-8e9487d3ff60", ← この ID をメモ
"name": "my-client-ca",
"issuer": "CN=My Root CA,O=Example,C=JP",
"ca": true
}
}
# 環境変数に設定
export CF_MTLS_CERT_ID="2458ce5a-0c35-4c7f-82c7-8e9487d3ff60"
2-3. アップロード確認
curl -X GET "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/mtls_certificates" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json"
Step 3: ホスト名関連付け
3-1. ホスト名とCA証明書を関連付け
エンドポイント: PUT /zones/{zone_id}/certificate_authorities/hostname_associations
curl -X PUT "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/certificate_authorities/hostname_associations" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{
\"hostnames\": [\"www.spt-lab.net\"],
\"mtls_certificate_id\": \"${CF_MTLS_CERT_ID}\"
}"
3-2. 関連付け確認
curl -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/certificate_authorities/hostname_associations" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json"
この時点での動作:
www.spt-lab.netへのアクセス時にクライアント証明書を「受け入れる」- 証明書なしでもアクセス可能(まだブロックされない)
Step 4: クライアント証明書ヘッダー転送有効化
クライアント証明書をHTTPヘッダーとしてオリジンサーバーに転送する設定を入れておきます。
すべてのリクエストに証明書を追加することを避けるため、証明書は mTLS 接続の最初のリクエストでのみ転送されます。
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/client_certificate_forwarding" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{
"value": {
"enabled": true
}
}'
確認:
curl -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/client_certificate_forwarding" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json"
Step 5: WAFカスタムルール作成(パス制限)
重要: このステップで特定パスのみ証明書必須にする
5-1. ダッシュボードで設定する場合
- Cloudflare Dashboard → 対象ドメイン
- Security → WAF → Custom rules
- Create rule をクリック
- 以下を設定:
| 項目 | 値 |
|---|---|
| Rule name | mTLS Required for /mtls/* |
| Expression | 下記参照 |
| Action | Block |
Expression(式):
(http.host eq "www.spt-lab.net" and starts_with(http.request.uri.path, "/mtls/") and not cf.tls_client_auth.cert_verified)
- Deploy をクリック
5-2. APIで設定する場合
curl -X POST "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/rulesets/phases/http_request_firewall_custom/entrypoint" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{
"rules": [
{
"expression": "(http.host eq \"www.spt-lab.net\" and starts_with(http.request.uri.path, \"/mtls/\") and not cf.tls_client_auth.cert_verified)",
"action": "block",
"description": "mTLS Required for /mtls/*"
}
]
}'
5-3. 複数パスを保護する場合
(http.host eq "www.spt-lab.net" and
(starts_with(http.request.uri.path, "/mtls/") or
starts_with(http.request.uri.path, "/api/") or
starts_with(http.request.uri.path, "/secure/")) and
not cf.tls_client_auth.cert_verified)
5-4. この時点での動作確認
# /mtls/ パスに証明書なしでアクセス → ブロックされる
curl -v https://www.spt-lab.net/mtls/test
# 期待: 403 Forbidden
# / パスに証明書なしでアクセス → 許可される
curl -v https://www.spt-lab.net/
# 期待: 200 OK
# /mtls/ パスに証明書ありでアクセス → 許可される
curl -v --cert client.crt --key client.key https://www.spt-lab.net/mtls/test
# 期待: 200 OK
Step 6: Workersプロジェクト作成
6-1. プロジェクト作成
mkdir mtls-ocsp-worker
cd mtls-ocsp-worker
npm init -y
6-2. 依存パッケージインストール
npm install wrangler --save-dev
npm install asn1js pkijs
6-3. ディレクトリ構成
mtls-ocsp-worker/
├── package.json
├── wrangler.toml
└── src/
└── index.js
6-4. Workersスクリプト配置
src/index.js を作成します。
*最新のindex.jsをご確認ください。
########## 抜粋 ############
import * as asn1js from 'asn1js';
import { getRandomValues, Certificate, Extension, OCSPRequest, OCSPResponse, GeneralName, BasicOCSPResponse } from 'pkijs';
/**
* Cloudflare Workers: mTLS Authentication with OCSP + O/OU Validation
*
* 機能:
* - クライアント証明書のOCSP検証(証明書のAIA拡張からOCSP URLを取得)
* - Subject DN の Organization (O) / Organizational Unit (OU) 検証
* - デバッグモード対応
* - 認証結果ヘッダーの付与
* - OCSP AIA URL の書き換えオプション
*
* 環境変数:
* - CA_CLIENT_ISSUER: クライアント証明書発行CA (Base64 DER) [Secret]
* - CA_OCSP_ROOT: OCSPレスポンダー検証用ルートCA (Base64 DER) [Secret]
* - VALID_O: 許可するOrganization (カンマ区切りで複数指定可)
* - VALID_OU: 許可するOrganizational Unit (カンマ区切りで複数指定可)
* - DEBUG_HEADER_NAME: デバッグモード有効化ヘッダー名 (デフォルト: X-CC-DEBUG)
* - DEBUG_HEADER_VALUE: デバッグモード有効化ヘッダー値 (デフォルト: enabled)
* - OCSP_HOST_OVERRIDE: OCSP サーバー (AIA URL) を書き換える場合に指定
*/
export default {
async fetch(request, env, ctx) {
// ============================================================
// Configuration
// ============================================================
const CONFIG = {
// 許可するO/OU (カンマ区切りで複数指定可能)
VALID_O: (env.VALID_O || 'TEST-O).split(',').map(s => s.trim()),
VALID_OU: (env.VALID_OU || 'TEST-OU').split(',').map(s => s.trim()),
// デバッグモード設定
DEBUG_HEADER_NAME: env.DEBUG_HEADER_NAME || 'X-CC-DEBUG',
DEBUG_HEADER_VALUE: env.DEBUG_HEADER_VALUE || 'enabled',
// OCSP AIA を無視して接続先を指定する場合の変数
OCSP_HOST_OVERRIDE: env.OCSP_HOST_OVERRIDE || null,
};
// ============================================================
// Helper Functions
// ============================================================
########## 抜粋 ############
Step 7: wrangler.toml設定
*最新のwrangler.tomlを参照してください。
# ============================================================
# 共通設定
# ============================================================
name = "mtls-ocsp-worker"
main = "src/index.js"
compatibility_date = "2024-01-01"
# ============================================================
# 本番環境 (production)
# ============================================================
[env.production]
name = "mtls-ocsp-worker-prod"
account_id = "your_prod_account_id"
# クライアント証明書認証のを実施するホスト名を記載
routes = [
{ pattern = "www.spt-lab.net/mtls/*", zone_name = "spt-lab.net" }
]
[env.production.vars]
VALID_O = "Networld Demo"
VALID_OU = "Sect.2"
DEBUG_HEADER_NAME = "X-CC-DEBUG"
DEBUG_HEADER_VALUE = "enabled"
重要: routes を特定パスに限定することで、Workersも該当パスのみで実行されます。
Step 8: シークレット設定
8-1. CA証明書をBase64 DER形式に変換
openssl x509 -in root-ca.crt -outform DER | base64 -w 0 > ca-base64.txt
8-2. 本番環境用シークレット設定
cat ca-base64.txt | npx wrangler secret put CA_CLIENT_ISSUER --env production
cat ca-base64.txt | npx wrangler secret put CA_OCSP_ROOT --env production
# 確認
npx wrangler secret list --env production
Step 9: Workersデプロイ
9-1. 本番環境へデプロイ
npx wrangler deploy --env production
Step 10: 動作確認
10-1. テストケース
# テスト1: /mtls/ に証明書なしでアクセス → ブロック
curl -v https://www.spt-lab.net/mtls/test
# 期待: 403 Forbidden (WAFでブロック)
# テスト2: / に証明書なしでアクセス → 許可
curl -v https://www.spt-lab.net/
# 期待: 200 OK
# テスト3: /mtls/ に有効な証明書でアクセス → 許可
curl -v --cert client.crt --key client.key \
https://www.spt-lab.net/mtls/test
# 期待: 200 OK + Certificate-Verification: ALLOWED
# テスト4: /mtls/ に無効なO/OUの証明書でアクセス → ブロック
curl -v --cert invalid-client.crt --key invalid-client.key \
https://www.spt-lab.net/mtls/test
# 期待: 403 + Certificate-Verification: DENIED
# テスト5: デバッグモードでアクセス
curl -v --cert client.crt --key client.key \
-H "X-CC-DEBUG: enabled" \
https://www.spt-lab.net/mtls/test
# 期待: デバッグヘッダーが含まれる
10-2. ログ確認
# リアルタイムログ
npx wrangler tail --env production
参考ドキュメント