株式会社ネットワールドのエンジニアがお届けする技術情報ブログです。
各製品のエキスパートたちが旬なトピックをご紹介します。

Cloudflare Workersを利用してmTLS(クライアント証明書認証) + OCSP失効確認

 

 

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でトークン作成

  1. My ProfileAPI TokensCreate Token
  2. Create Custom Token を選択
  3. 以下の権限を設定:
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
  1. Account Resources: Include > 対象アカウント
  2. Zone Resources: Include > Specific zone > 対象ドメイン
  3. 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 接続の最初のリクエストでのみ転送されます。

https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/mutual-tls-authentication/#forward-a-client-certificate

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. ダッシュボードで設定する場合

  1. Cloudflare Dashboard → 対象ドメイン
  2. SecurityWAFCustom rules
  3. Create rule をクリック
  4. 以下を設定:
項目
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)
  1. 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

参考ドキュメント