FDサービス系 ALB + WAF 移行設計書
ドキュメント情報
| 項目 | 内容 |
|---|---|
| 作成日 | 2026-05-22 |
| 対象環境 | production (*.fstdr.jp, *.fastdoctor.jp) |
| 対象システム | FDサービス系ALB(CloudFlare脱却対象) |
| 関連Issue | mental-online-karte#11657, mental-online-karte#11666, mental-online-karte#11667 |
| 関連ドキュメント | CloudFront WAF アーキテクチャ設計書(LP向け), WAF 運用設計書(LP向け), FDサービス系WAF 運用設計書, 構成比較検討結果 |
本設計書の位置づけ
- 最優先はセキュリティリスクの軽減。現在CloudFlare Proxyを外した状態でWAF保護がなく、この状態の解消が急務
- ALB + WAF(REGIONAL)構成を採用する。当初は CloudFront + WAF で全サービスを統一する方針だったが、staging 検証で CloudFront 固有の制約がサービス間通信と競合する問題が判明したため、方針を変更した(詳細は構成比較検討結果を参照)
- CloudFront のキャッシュによるパフォーマンス向上・コスト削減は今後の検討課題として残す。WAF 導入が安定した後、必要なサービスから CloudFront の追加を検討する
- ルール構成・閾値・Phase計画等の具体値は 初期設定値 であり、Count運用中の観察結果やサービス固有の事情に応じて変更していく前提
- 設計書自体も運用の中で更新し、実態と乖離しないように維持する
要するに: まずCountで入れる → 観察する → 育てていく。この設計書はそのスタート地点を定義するもの。
背景
障害の経緯(2026-05-21)
- 21:40頃、CloudFlare → ALB 経路上でレイテンシが異常悪化し、FDサービス全体に影響
- 止血対応としてCloudFlareのProxy設定をOFF(DNS onlyに切替)→ 順次復旧
- 恒久対応方針: CloudFlare Proxyは戻さず、AWS WAF へ完全移行する
目的
- CloudFlareへの依存を排除し、AWS内で完結するセキュリティレイヤーを構築する
- WAFによる基本的な攻撃防御を実現する(導入を優先し、段階的に強化)
なぜ ALB + WAF(REGIONAL)にするのか
当初は CloudFront + WAF で全サービスを統一する方針だったが、staging 検証で CloudFront 固有の制約(GET + body → 403 等)がサービス間通信と競合する問題が判明。CloudFront Functions / Lambda@Edge での回避も不可能であることを確認し、ALB に直接 WAF をアタッチする構成に変更した(API Gateway(HTTP API)は WAF 非サポートのため別途検討)。
- セキュリティリスクの軽減が最優先: WAF 保護がない状態の解消が急務。ALB + WAF なら迅速に導入できる
- CloudFront 固有の制約を回避: 既存アプリとの互換性問題のリスクがない
- バイパス耐性が構造的に高い: WAF が ALB に直接統合されるため、バイパス不可能(検証済み・SA 確認済み)
- CloudFront のキャッシュは今後の検討課題: WAF 導入が安定した後、必要なサービスから CloudFront の追加を検討する
詳細な比較検討・経緯: CloudFront + WAF vs ALB + WAF 構成比較・検討結果を参照
参考ドキュメント
- AWS WAF マネージドルールグループ一覧
- AWS WAF テスト・チューニングガイド
- AWS WAF Rate-based rules ベストプラクティス(AWS公式ブログ)
- AWS WAF Geo制限 実装ガイド(AWS公式ブログ)
- WAF マネージドルールのカスタマイズ(AWS公式ブログ)
- CloudFront WAF アーキテクチャ設計書(LP向け)
- WAF 運用設計書
Cloudflare設定との対応
CloudFlareで有効化されていた保護機能と、AWS WAFでの対応方針を整理する。 LP向け設計書の対応表も参照。
| Cloudflare機能 | CloudFlareでの状態 | AWS WAF相当 | 本設計での対応 | 備考 |
|---|---|---|---|---|
| OWASP コア | ✅ 有効(ブロック) | Core Rule Set (CRS) | Phase 1 Count → Phase 2 Block | OWASP Top 10 対策 |
| Cloudflare Managed Ruleset | ✅ 有効 | IP Reputation + Known Bad Inputs | Phase 1 Count → Phase 2 Block | 2つのマネージドルールで同等カバー |
| IP Access Rules | 推定有効 | IP Set(共有 Blacklist) | Phase 1 から即Block | 既存 waf-shared-sets モジュール活用 |
| HTTP DDoS保護(自動) | デフォルト有効 | Shield Standard(自動)+ Rate-based rules | Shield Standard はデフォルト有効。Rate-based は Phase 2で検討 | Shield Standard は L3/L4 中心。アプリ層flood対策として Rate-based ルールをPhase 2で検討 |
| Exposed Credentials Check | ✅ 有効 | ❌ AWS WAF に直接相当なし | 対応なし | AWS WAF Account Takeover Prevention($10/月/ACL)で部分的に代替可能だが、現時点では未導入。Rate-based ルールでブルートフォースは緩和 |
| スーパー ボット ファイト モード | ✅ 有効 | Bot Control($10/月/ACL) | Phase 3以降で検討 | まずマネージドルール+Rate-based でBot対策し、不足なら導入判断 |
| スキーマ検証 | ✅ 有効 | ❌ AWS WAF に相当機能なし | 対応なし(アプリ層で対応) | APIスキーマ検証はアプリケーション側(API Gateway, アプリコード)で実施 |
CloudFlareになかった追加保護(本設計で新規導入):
| 機能 | AWS WAF ルール | 導入Phase | 追加理由 |
|---|---|---|---|
| Geo制限(日本以外ブロック) | Geo Match (Custom) | 導入しない | 海外勤務の医師・渡航中の患者向けサービスがあるため |
| Anonymous IP List | AWSManagedRulesAnonymousIpList | Phase 1 Count → Phase 2 Block | TOR/VPN/Hosting Provider 経由の攻撃を遮断 |
| SQLi専用ルール | AWSManagedRulesSQLiRuleSet | Phase 1 Count → Phase 2 Block | RDS利用サービス向け。CRSのSQLi検出を補完 |
| Rate-based(脅威IP向け) | Rate-based + Label scope-down | Phase 2以降で検討 | IP Reputation の誤検知リスクを観察してから判断 |
方針: CloudFlareで有効だった保護は全てAWS WAFで再現する。 さらに、CloudFlareでは未設定だった Anonymous IP List・SQLi・Rate-based を追加し、 CloudFlare時代よりも強固な保護を実現する。 一方、AWS WAFに直接相当しない Exposed Credentials Check・スキーマ検証は、 意図的に対応しないものとして明記する。
対象ドメイン・ALB一覧
障害時に影響を受けたドメイン(CloudFlare Proxy OFF済み):
fd-prod アカウント(967691968827)— *.fstdr.jp
| ドメイン | サービス | Origin(ALB/API GW) | AWSアカウント | リージョン | 備考 |
|---|---|---|---|---|---|
| mental-reserve-patient.fstdr.jp | mental-reserve-patient | mental-reserve-patient-1989296911.us-east-1.elb.amazonaws.com | fd-prod | us-east-1 | メンタル予約画面 |
| p.fstdr.jp | online-patient-mypage | online-patient-mypage-1403221813.us-east-1.elb.amazonaws.com | fd-prod | us-east-1 | 患者マイページ |
| mental-reserve-admin.fstdr.jp | mental-reserve-admin | mental-reserve-admin-1313503852.us-east-1.elb.amazonaws.com | fd-prod | us-east-1 | メンタル管理画面 |
| fd-app-bff.fstdr.jp | fd-app-bff | fd-app-bff-1521485696.us-east-1.elb.amazonaws.com | fd-prod | us-east-1 | アプリBFF |
| fd-video.fstdr.jp | fd-video | online-video-2038185983.us-east-1.elb.amazonaws.com | fd-prod | us-east-1 | ビデオ通話 |
| verify-account.fstdr.jp | verify-account | verify-account-service-bff-1598402994.us-east-1.elb.amazonaws.com | fd-prod | us-east-1 | 認証 |
| backend-online-karte.fstdr.jp | backend-online-karte | online-karte-service-2073905677.ap-northeast-1.elb.amazonaws.com | fd-prod | ap-northeast-1 | カルテバックエンド |
| payment.fstdr.jp | payment | d-sslgqoucs8.execute-api.us-east-1.amazonaws.com | fd-prod | us-east-1 | 決済(API Gateway、SG影響なし) |
| dr-site.fstdr.jp | dr-site | d-1ef8u9oft5.execute-api.us-east-1.amazonaws.com | fd-prod | us-east-1 | 往診用(API Gateway、SG影響なし) |
| u.fstdr.jp | Heroku | encircled-cat-9nts27s1e0zpm2tywaynfes1.herokudns.com | - | Heroku | SMS短縮URL → PMP |
fd-sys アカウント(913831226605)— *.fastdoctor.jp / member.fstdr.jp
| ドメイン | サービス | Origin(ALB/API GW) | AWSアカウント | リージョン | 備考 |
|---|---|---|---|---|---|
| backend.fastdoctor.jp | fastdoctor-manager | fastdoctor-manager-prd-app-383969541.us-east-1.elb.amazonaws.com | fd-sys | us-east-1 | FDsysのAPI |
| cp.fastdoctor.jp | fastdoctor-manager | fastdoctor-manager-prd-app-383969541.us-east-1.elb.amazonaws.com | fd-sys | us-east-1 | クリニックポータル(業務システム)。backend.fastdoctor.jp と同一 ALB |
| u.fastdoctor.jp | fastdoctor-manager | fastdoctor-manager-prd-app-383969541.us-east-1.elb.amazonaws.com | fd-sys | us-east-1 | 短縮URL / リダイレクトサービス。backend.fastdoctor.jp と同一 ALB。トラフィック僅少 |
| cable.fastdoctor.jp | cable-fdm | cable-fdm-prd-app-1384687700.us-east-1.elb.amazonaws.com | fd-sys | us-east-1 | |
| member.fstdr.jp | member | d-u35c6t4ml1.execute-api.us-east-1.amazonaws.com | fd-sys | us-east-1 | 会員(API Gateway、SG影響なし) |
注:
- API Gateway 経由のサービス(
payment,dr-site,member)は全て HTTP API(API Gateway v2)で、HTTP API は AWS WAF をサポートしていない(公式ドキュメント)。WAF 導入方法は別途検討が必要。まず ALB のサービスから WAF 導入を優先する *.fastdoctor.jpドメインは fd-sys アカウントで管理。設計の考え方は同一だが、Terraform管理場所(fastdoctor-manager/)とACM証明書(*.fastdoctor.jp)が異なるmember.fstdr.jpはドメインが*.fstdr.jpだが、API GatewayはAWS fd-sysアカウント上に存在する- サービス間通信はVPC Peering(fd-prod Virginia ↔ fd-prod Tokyo、fd-prod Tokyo ↔ fd-sys)経由のPrivate通信であり、
*.fstdr.jpのinternet-facing ALBをインターネット経由で呼び合う構成にはなっていない(ネットワーク構成図およびルートテーブル確認済み)
アーキテクチャ設計
全体構成
設計方針
| 項目 | 方針 | 理由 |
|---|---|---|
| WAF Web ACL | サービスごとに個別 | サービスごとに要件が異なる可能性。コスト: $5/月/ACL |
| WAFアタッチ先 | ALB に直接アタッチ(REGIONAL) | CloudFront 固有の制約を回避。バイパス耐性が構造的に高い。API Gateway(HTTP API)は WAF 非サポートのため別途検討 |
| WAFスコープ | REGIONAL | ALB にアタッチするため |
| IP/UA Blacklist | 共有 | waf-shared-sets モジュールを REGIONAL スコープで活用 |
| WAF初期モード | Count | 導入を優先し、誤検知を確認してからBlockへ |
| Rate-based rule | Phase 2で検討 | 誤検知リスクを考慮し、Phase 1のトラフィックパターン観察後に導入判断 |
| Geo制限 | 導入しない | 海外勤務の医師・渡航中の患者向けサービスがあるため |
| DNS切替 | 不要 | CloudFront を使わないため。WAF を ALB にアタッチするだけ |
| ALB SG制限 | 不要 | WAF が ALB に直接統合されるため、バイパス不可能(検証済み・SA確認済み) |
API Gateway サービスの扱い(別途検討)
payment.fstdr.jp, dr-site.fstdr.jp, member.fstdr.jp は API Gateway(HTTP API)経由。
HTTP API は AWS WAF をサポートしていない(公式ドキュメントで確認済み。WAF をアタッチできるのは REST API のみ)。そのため、ALB と同じ方式での WAF 導入はできず、別途検討が必要。
別途検討とする理由:
- HTTP API で WAF 保護を実現するには CloudFront を前段に置く必要があるが、CloudFront 固有の制約(GET + body → 403 等)の影響調査が必要
- 認証なしの
member.fstdr.jpは攻撃リクエストがバックエンドまで到達するリスクがあり保護の優先度は高いが、構成の検討に時間がかかる - セキュリティリスク軽減を最優先するため、まず ALB のサービスから WAF 導入を進める
API Gateway サービスの現状構成:
| サービス | 認証方式 | バックエンド | WAF 保護 | 備考 |
|---|---|---|---|---|
| payment.fstdr.jp | Lambda Authorizer(API Key) | ECS via ALB (VPC Link) | 別途検討 | 認証ありのため攻撃リクエストは Authorizer で弾かれる |
| dr-site.fstdr.jp | Lambda Authorizer(JWT) | ECS via ALB (VPC Link) | 別途検討 | 同上 |
| member.fstdr.jp | 認証なし | ECS via ALB (VPC Link) | 別途検討(優先度高) | 認証なしのため攻撃リクエストがバックエンドまで到達する |
注: API Gateway の WAF 導入方法の詳細な比較検討は、ALB の WAF 導入が完了した後に
docs/tasks/に別途ドキュメントを作成して実施する。
コンポーネント詳細
WAFによるアクセス制御の考え方
WAF は ALB に直接アタッチされるため、WAF が検査するのは ALB に接続してきた IP アドレス。 ALB + WAF(REGIONAL)構成では、ALB への全リクエストが WAF を通過する(infra-dev で検証済み、AWS SA 確認済み)。バイパスは構造的に不可能。
エンドユーザー(患者・医師等のグローバルIP)
→ ALB + WAF ← WAFはALBに接続してきたIP(= エンドユーザーのIP)を検査
→ ECS Tasks(Private Subnet)バイパス耐性:
- ECS は全て Private Subnet に配置、
assign_public_ip = false(Terraform コードで確認済み) - ECS Security Group は ALB Security Group からの通信のみ許可
- インターネットに公開されている入口は ALB のみ → ALB にアタッチされた WAF を迂回する手段がない
サービスごとのアクセス元とIP制限の可否:
現時点で確実にわかっているアクセス元:
| ドメイン | アクセス元 | 根拠 |
|---|---|---|
| mental-reserve-patient.fstdr.jp | 患者(不特定多数) | インシデント記録「患者予約」 |
| p.fstdr.jp | 患者(不特定多数) | インシデント記録「患者マイページ」 |
| fd-video.fstdr.jp | 患者・医師(不特定多数) | インシデント記録「FDビデオが重くて入れない(患者も)」 |
| mental-reserve-admin.fstdr.jp | 社内スタッフ・ゆめみ | インシデント記録「ゆめみ管理画面」 |
| backend.fastdoctor.jp | 内部・外部不明 | インシデント記録「FDsysのAPI」 |
上記以外のサービスはアクセス元の調査が完了していない。 Phase 1のWAFログ(Countモード)でアクセスパターンを確認した上で、サービスごとのアクセス要件を整理し、IP制限の要否を改めて検討する。
不特定多数のエンドユーザーがアクセスするサービスが大半の見込みであり、IPホワイトリスト方式は取れない可能性が高い。 WAFの防御は複数のルールを組み合わせて攻撃トラフィックを段階的にフィルタするアプローチとなる。具体的なフィルタ方針はアクセス要件の整理後にサービスごとに検討する。
IP制限の方針:
- 現時点ではTerraform/実機の状態からアクセス元を完全に把握できない
- Phase 1のWAFログ(Countモード)でアクセスパターンを確認し、サービスごとのアクセス要件を整理した上でIP制限を検討する
- IP制限はいきなりかけず、徐々に制限する
IPセットの構成:
| IPセット | スコープ | 用途 | 例 |
|---|---|---|---|
共有IP Blacklist(waf-shared-sets) | 全サービス共通 | 全サービス共通の攻撃元IPブロック | 攻撃元IP、悪意あるASレンジ(M247等) |
共有IP Whitelist(force-allow-ip-list) | 全サービス共通 | 全サービス共通の誤ブロック防止 | 監視ツール、オフィスIP、CI/CD |
| サービス個別IP Whitelist | サービスごと | サービス固有のアクセス要件に基づくIP許可 | paymentの呼び出し元IP等 |
| サービス個別IP Blacklist | サービスごと | 特定IPのみ許可するサービスで、Whitelist以外を全拒否 | Whitelist方式のサービスで 0.0.0.0/0 をBlock |
- 共有IPセットは
waf-shared-setsモジュールで管理(LP向けWAFと共有) - サービス個別IPセットは各サービスのWAF Web ACL内で管理(他サービスに影響しない)
- Phase 1のアクセスパターン確認後に、サービス個別IPセットの要否を判断する
WAFの対象外となる通信:
| 通信 | 理由 |
|---|---|
| VPC内部通信(ECS → internal ALB等) | VPC内のローカルルーティング。CloudFrontを経由しない |
| VPC Peering経由の通信(fd-prod ↔ fd-sys等) | Private Subnet同士のPeering通信。CloudFrontを経由しない |
| API Gateway VPC Link経由の通信 | Private通信。CloudFrontを経由しない |
WAF Web ACL(サービスごと)
| 設定項目 | 値 |
|---|---|
| Scope | REGIONAL |
| Default Action | Allow |
| CloudWatch Metrics | 有効 |
| Sampled Requests | 有効 |
ルール優先度設計(Priority Order)
AWSのベストプラクティスに基づき、コストの低いルール(IP Set等)を先に評価し、WCUの高いマネージドルールに到達するリクエスト数を減らす構成とする。
初期ルール構成(Phase 1):
| Priority | ルール名 | タイプ | アクション | WCU | 備考 |
|---|---|---|---|---|---|
| 0 | service-allow-ip-list | Custom (IP Set) | Allow | 1 | サービス個別のIP許可リスト(初期は空) |
| 1 | force-allow-ip-list | Custom (IP Set) | Allow | 1 | 共有IP許可リスト(初期は空) |
| 2 | service-block-ip-list | Custom (IP Set) | Block | 1 | サービス個別のIP拒否リスト(Whitelist方式のサービスで全拒否用。初期は空) |
| 3 | ip-blacklist | Custom (IP Set) | Block | 2 | 共有IP Blacklist(IPv4/IPv6)参照 |
| 4 | ua-blacklist | Custom (String Match) | Block | 1 | 共有UA Blacklist参照(sqlmap, nikto, nmap, masscan) |
| 20 | amazon-ip-reputation | Managed | Count | 25 | AWS脅威インテリジェンス(MadPot) |
| 21 | anonymous-ip-list | Managed | Count | 50 | TOR, VPN, Hosting Provider IP |
| 30 | known-bad-inputs | Managed | Count | 200 | Log4j, Spring RCE等の既知脆弱性 |
| 40 | core-rule-set | Managed | Count | 700 | OWASP Top 10対策 |
| 50 | sqli-rule-set | Managed | Count | 200 | SQL Injection 専用ルール |
合計 WCU: 1,181 / 1,500(残り319 WCU)
Priority設計の原則(AWS公式ガイド):
- サービス個別の許可/拒否リスト(最優先)
- 共有の許可/拒否リスト(IP/UA)
- IP Reputation系(低WCU)
- マネージドルール(高WCU、コンテンツ検査)
Phase 1で導入を見送るルール
| ルール | 見送り理由 |
|---|---|
| Rate-based | 誤検知リスクを考慮し、Phase 1のトラフィックパターン観察後にPhase 2で導入判断 |
| Admin Protection | FDサービスはカスタムパスのため効果が限定的 |
| Linux/POSIX OS | LFI/RFIはCRSでカバー済み。WCUが高く、追加の恩恵が限定的 |
| Bot Control | コストが高い。Phase 3以降で検討 |
共有リソース(既存の waf-shared-sets モジュール):
aws_wafv2_ip_set.shared_ip_blacklist_ipv4— 共有IPv4ブロックリストaws_wafv2_ip_set.shared_ip_blacklist_ipv6— 共有IPv6ブロックリストua_blacklist— 共有UAブラックリスト(sqlmap, nikto, nmap, masscan)
WAF Body Inspection(リクエストボディ検査):
oversize_handling = "NO_MATCH"を指定し、ボディサイズ上限(デフォルト16KB、最大64KB)を超えるリクエストはルールにマッチしない扱いとして通過させる- FDサービスでは画像アップロードやカルテ入力等で大きなPOSTボディが発生するため、ボディサイズ超過時にBlockされないようにする
CRS SizeRestrictions_BODY / SizeRestrictions_QueryString ルールの除外:
- CRS(AWSManagedRulesCommonRuleSet)の
SizeRestrictions_BODYルールは、リクエストボディが 8,192 バイト を超えると検知する - fd-system production の WAF ログ(Phase 1 Count)を分析した結果、管理画面での PATCH 操作(患者データ更新等)が 13,000〜22,000 バイトの JSON ボディを送信しており、Count 検知の 82.6%(1,412/1,710 件) を占めていた
cp.fastdoctor.jp(クリニックポータル)でも同様に検知(3日間で579件)。患者情報更新(PATCH /clinic_portal/cp_patients/*.json)、ドキュメントアップロード(POST /clinic_portal/cp_patients/*/cp_documents.json)、一括更新(PATCH /clinic_portal/bulk/cp_patients.json)で 8KB〜24.9MB のボディが発生。cp.fastdoctor.jpはbackend.fastdoctor.jpと同一 ALB(fastdoctor-manager-prd-app)のため、WAF 側のSizeRestrictions_BODYrule_action_override(count)は既に適用済み。追加の WAF 設定変更は不要で、Datadog Index Exclusion Filter にcp.fastdoctor.jpを追加するのみ。分析結果p.fstdr.jp(online-patient-mypage)でも同様に検知(5日間で659件、全て COUNT/ALLOW)。トリガーしているAPIは2種類のみ:①PUT /api/ai-triage/session/online_interview/attachment(患者がオンライン問診の事前トリアージで画像を添付アップロード、362件/55%、ボディサイズ 42KB〜4.96MB)、②POST /api/medical_examinations/{id}/kartes/{karteId}/images(診療中に医師・スタッフがカルテに画像を添付、297件/45%、ボディサイズ 33KB〜700KB)。発生時間帯は午前11時〜午後3時(診療時間帯)に集中しており、100% 正規の医療業務パターン。online-patient-mypage は独自 ALB に個別 WAF Web ACL がアタッチされているため、managed_rule_core_rule_set_excluded_rulesにSizeRestrictions_BODYを追加する必要がある。分析結果- 保険証アップロード等のファイルアップロードでもボディサイズ超過が発生する
SizeRestrictions_QueryStringルールは、クエリ文字列が 2,048 バイト を超えると検知する。fd-system の WAF ログ分析(直近5日間)で、医師スケジュール管理(/admin/doctor_work_schedules/*)等の正規業務操作で検知されていた(133件 / 3,400件)- これらは正常な業務操作であり、CRS を Block に切り替えた場合に業務に影響する
- CRS を Block に切り替える際は、
SizeRestrictions_BODYとSizeRestrictions_QueryStringをrule_action_overrideでcountに維持する countにすることでログは記録されるが、後続ルール(XSS、SQLi 等)の評価はスキップされず正常に機能する(allowは terminating action のため後続ルールがスキップされ不可)- ログノイズは ログコスト最適化 の方針に沿い、Datadog Index Exclusion Filter で
SizeRestrictions_BODY/SizeRestrictions_QueryStringの Count ログをインデックスから除外。対象ホスト:backend.fastdoctor.jp,backend-stg.fstdr.jp,cp.fastdoctor.jp,p.fstdr.jp。調査が必要な場合は S3 + Athena で対応 u.fastdoctor.jpはトラフィック僅少(3日間で34件)のため Exclusion Filter 対象外
# CRS を Block に切り替える際の設定例
# SizeRestrictions_BODY / SizeRestrictions_QueryString は Count に維持し、他のルールは Block
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
version = "Version_1.21"
rule_action_override {
name = "SizeRestrictions_BODY"
action_to_use { count {} }
}
rule_action_override {
name = "SizeRestrictions_QueryString"
action_to_use { count {} }
}
}X-Forwarded-For の取り扱い
ALB + WAF(REGIONAL)構成では CloudFront を経由しないため、X-Forwarded-For の hop 数は現状と変わらない。アプリ側の IP 取得ロジックに影響しない。
現状(CloudFlare DNS only):
Client(203.0.113.5) → ALB
X-Forwarded-For: 203.0.113.5
ALB + WAF 導入後:
Client(203.0.113.5) → ALB + WAF
X-Forwarded-For: 203.0.113.5 ← 変更なしサービスごとのWAFルール調整方針
WAF ACLはサービスごとに個別だが、初期は全サービス同一のルール構成(上記Phase 1構成)で導入する。
Phase 1のCount運用でサービスごとのアクセスパターン・誤検知状況を確認した上で、Phase 2以降で以下を検討する:
- サービスごとのBlock化タイミングの調整
- サービス個別IP許可/拒否リストの設定(アクセス要件整理後)
- 誤検知が多いルールの除外設定(RuleActionOverride または Label-based パターン)
- CAPTCHA Challenge の導入(ブラウザアクセスのサービスのみ。FE側対応が必要)
- Rate-basedルールの導入
参考: 誤検知対策として、マネージドルールを Count にしてラベルを付与し、後続の自前ルールでパス単位の除外を行う Label-based パターンがある。 AWS公式ブログ: How to customize behavior of AWS Managed Rules
Rate-based ルール(Phase 2で検討)
Rate-basedルールは一定時間内に同じIPからのリクエスト数が閾値を超えたら自動BlockするAWS WAFの機能。 DDoS/ブルートフォース防御に有効だが、誤検知リスクを考慮しPhase 1では導入せず、トラフィックパターンを観察してからPhase 2で導入判断する。
背景: 認証なしのエンドポイントに対してスクリプトが無限にリクエストを送信し、staging環境に障害が発生したインシデントがあった。同じIPからの異常なリクエスト量を検知してBlockする仕組みが必要であり、アプリケーション層ではなくネットワーク層(ALB + WAF)での対策が望ましい。
Phase 2で検討するルール候補:
| ルール | 閾値(参考) | 目的 |
|---|---|---|
| 全体レートリミット(blanket) | 2,000 req / 5分 / IP | 一般的なDDoS/フラッド防御 |
| エンドポイント別(スコープダウン) | 予約/案件作成系: 10 req/1分 等 | 特定URIへのブルートフォース防御 |
参考: AWS公式ブログ: The three most important AWS WAF rate-based rules
スコープダウンステートメントによるエンドポイント別制限
Rate-basedルールにスコープダウンステートメントを組み合わせることで、特定のURIパスにのみ厳しいレート制限をかけられる。全体のblanketルールより厳しい閾値を設定できるため、誤検知リスクを抑えつつ重要なエンドポイントを保護できる。
適用候補:
- 予約作成API(秒間1回も叩かれることがない → 10 req/1分 程度で十分)
- 案件作成API(同上)
- ログイン/認証系API
- 認証なしのエンドポイント(スクリプトで無限に叩けるリスクがあるため、特に厳しい制限が必要)
設定例:
Rate-based ルール(例: backend.fastdoctor.jp の WAF Web ACL):
閾値: 10 req / 1分
スコープダウン: 案件作成等の特定エンドポイントにマッチするリクエストのみ対象
→ 対象パス以外にはこのルールは適用されない実例: staging環境で案件作成APIがスクリプトにより無限ループで叩かれ、障害が発生した。 このようなエンドポイントは秒間1回も叩かれることがない正常系のため、10 req/1分 程度の閾値で十分。
注: スコープダウンの対象パスは、レートリミット導入時に各サービスのエンドポイントを精査して決定する。
別案: WAF Rate-basedルールの誤検知リスクが許容できない場合、Lambdaで一定期間に閾値以上のリクエストがあったIPを検知し、WAFのIP Blacklistに自動追加する方式も検討する。この場合、閾値やブロック期間を柔軟に制御でき、誤検知時の解除も容易になる。
Geo制限
Geo制限は導入しない。 海外勤務の医師や渡航中の患者向けサービスがあるため、国外アクセスを一律にブロックできない。
Rate Limit の設計
例外 IP
Rate-based ルール導入時に、以下の IP は Rate Limit の対象外(force-allow-ip-list)とする:
| IP | 理由 |
|---|---|
| NAT IP(各 VPC) | サービス間通信が同一 IP で集約されるため、Rate Limit に引っかかるリスクがある |
| オフィス IP | 社内スタッフのアクセスが集約される |
| Datadog Synthetics | 外形監視のリクエストがブロックされないようにする |
Rate Limit 導入時の注意
Rate-based ルールは Count モードで様子を見ながら 段階的に適用する。例外 IP を事前に force-allow-ip-list に登録してから導入すること。
Phase移行計画
Phase 1: WAF導入(Count)
WAF(REGIONAL)を Count モードで ALB にアタッチする。DNS 切替は不要で、既存の通信に影響を与えない。API Gateway サービスは別途検討。
WAFルール(全てCountモード、IP/UA BlacklistのみBlock):
- IP/UA Blacklist — Block(共有リスト)
- Amazon IP Reputation List — Count
- Anonymous IP List — Count
- Known Bad Inputs — Count
- Core Rule Set (CRS) — Count
- SQLi Rule Set — Count
この期間にやること:
- Datadogで各ルールのマッチ数・パターンを監視
- 誤検知パターンの収集(特にCRS、SQLi)
SizeRestrictions_BODY(ボディサイズ超過)、CrossSiteScripting_BODY(XSS誤検知)等のマッチを確認- サービスごとのアクセス元パターンを確認し、アクセス要件を整理
注: ALB + WAF(REGIONAL)構成では、Phase 1.5(ALB SG 制限)は不要。WAF が ALB に直接統合されるため、バイパスが構造的に不可能(検証済み・SA 確認済み)。
Phase 2: Count → Block化(段階的)
AWS推奨の「誤検知リスクが低いルールから順にBlock化」に従い、以下の順序で切り替える。
| 順序 | ルール | Block化の条件 | 状況 |
|---|---|---|---|
| 1 | Amazon IP Reputation List | Count運用で誤検知ゼロを確認 | 継続観察中 |
| 2 | Anonymous IP List | 同上。VPN経由の正規アクセスがないことを確認 | 継続観察中(VPN 経由スタッフの可能性あり) |
| 3 | Known Bad Inputs | 同上。誤検知率は極めて低い | ✅ Block 昇格済み(fd-system staging、2026-06-23)。直近5日間の分析で14件すべてスキャナー(.env ファイルスキャン等)、正規通信への影響なし |
| 4 | SQLi Rule Set | 検索API等でのマッチパターンを確認し、必要なら除外設定 | 継続観察中 |
| 5 | Core Rule Set (CRS) | 最も誤検知が多い。RuleActionOverride/Label-based除外を設定してから | SizeRestrictions_BODY / SizeRestrictions_QueryString を除外済み。他ルールは継続観察中 |
重要: 各ルールのBlock切り替え後、最低2-3日は様子を見てから次に進む。 切り替えは1ルールグループずつ行い、問題があればすぐにCountに戻せるようにする。
Block数のモニタリングアラート: Block化後の誤検知に早期に気付くため、Block数の急増を検知するDatadogアラートを設定する。LP向けWAFの運用設計書(WAF 運用設計書)で定義済みのアラート設定を参考にする。
移行条件: Phase 1でトラフィックパターンが把握できていること
Phase 3: 追加ルール・最適化
Phase 2でBlock化が安定した後、必要に応じて以下を検討・導入する。
- Rate-basedルールの導入検討(Phase 1-2のトラフィックパターン観察結果に基づき判断)
- BE系はLabel-based除外パターンの適用
- 脅威IP向け厳格Rate-based(IP Reputation ラベル + scope-down)の導入検討
- CloudFront のキャッシュ導入検討 — パフォーマンス向上やコスト削減が必要な場合に検討する。ただし CloudFront 固有の制約(GET + body → 403 等)があるため、導入前に staging で十分に検証すること
- 移行条件: Phase 2の全ルールがBlockで安定運用されていること
WAF導入手順(概要)
詳細な作業手順・タイミング・サービスごとの導入順序は、別途「切り替え計画書」を策定しそちらに記載する。 本セクションは概要のみ記載。
ALB + WAF(REGIONAL)構成では DNS 切替は不要。WAF を ALB にアタッチするだけで導入完了。
導入フロー(概要)
- WAF Web ACL を作成(Terraform apply)
- ALB にアタッチ
- 動作確認(ヘルスチェック、基本的なページ表示)
- Datadogでモニタリング開始
切り戻し手順(概要)
問題発生時:
- WAF Web ACL を ALB から detach(Terraform で
waf_web_acl_arnを削除して apply) - 即座に WAF が無効化され、元の ALB 直接アクセスに戻る
モニタリング設計
WAFログの利用目的
- 誤検知(False Positive)の調査: 正規ユーザーがブロックされていないか確認
- ブロック漏れの検出: 攻撃パターンがすり抜けていないか検証
- 緊急時の攻撃元特定: DDoS攻撃等のインシデント対応時にIPアドレス・パターンを即座に特定
- 主に直近(数時間〜数日)のログ分析で運用。過去に大きく遡る分析は基本的に不要
監視プラットフォーム
- Datadog で一元管理(メトリクス + ログの統合監視)
- アラート発火 → 同一画面でログ確認 → 原因特定のワークフロー
疎通検証期間のDatadogアカウント分離:
アクセス数が多いサービスのみ、疎通検証期間(約1週間)にインフラDatadogアカウントでログ量を計測する。 それ以外のサービスは最初から本番Datadogアカウントに出力する。
| サービス | 24h アクセス数(参考値) | Datadogアカウント |
|---|---|---|
| backend.fastdoctor.jp | 3.07k(訪問者)/ 113k(APIリクエスト) | インフラで検証後に本番切替 |
| p.fstdr.jp | 2.08k | インフラで検証後に本番切替 |
| mental-reserve-patient.fstdr.jp | 1.58k | 最初から本番 |
| mental-reserve-admin.fstdr.jp | 848 | 最初から本番 |
| fd-video.fstdr.jp | 387 | 最初から本番 |
| u.fstdr.jp | 216 | 最初から本番 |
| cp.fastdoctor.jp | 327 | 最初から本番 |
| その他 | 少量 | 最初から本番 |
インフラアカウントは現状ログが無いため、WAFログの影響を正確に計測できる- 検証期間中も
インフラDatadogアカウントでダッシュボードでモニタリングする - 疎通検証完了後、本番Datadogアカウントに切り替え。ダッシュボードもexport/importする
注: 上記アクセス数はCloudFlare Proxy OFF後(2026-05-21障害後)の24時間データのため、 通常時より60-70%減少している。通常時はさらに多い前提でログ量を見積もること。
ログ基盤
WAF ログ基盤は既存の LP 向け WAF と同じ構成。WAF スコープが REGIONAL に変わるが、ログの転送先・形式は同一。
ログの出力先:
- ALLOW: WAFログには出力しない(WAF Logging Filterで DROP)
- BLOCK/COUNT: WAF Logging Filter → Firehose → Datadog + S3バックアップ
| ログタイプ | 保存先 | 保存期間 | 用途 |
|---|---|---|---|
| WAF BLOCK/COUNT | Kinesis Firehose → Datadog | 15日 | リアルタイム監視、誤検知分析 |
| WAF BLOCK/COUNT | Kinesis Firehose → S3 Backup | 1年(365日) | 監査要件、過去データ分析 |
| CloudFront 4xx/5xx | CloudWatch デフォルトメトリクス → Datadog | - | エラー率監視 |
ログ基盤の設定詳細:
| 項目 | 設定 |
|---|---|
| WAF Logging Filter | Block/Count/Excluded_as_Count のみ記録(Allow はDROP) |
| リダクション | Authorization, Cookie ヘッダーをマスク |
| Firehose バッファ | 4 MB / 60秒 |
| S3 | 暗号化(AES256)・バージョニング有効・パブリックアクセス全ブロック・365日保持 |
| Datadog送信先 | aws-kinesis-http-intake.logs.ap1.datadoghq.com |
| Datadog保持期間 | 15日 |
Datadog Logs Pipeline(LP設計書で構築済みをそのまま利用)
LP向け設計書で構築済みの Datadog Logs Pipeline をそのまま利用する。 FDサービス系WAFも同じ source:waf でログが送信されるため、追加のPipeline構築は不要。
既存Pipeline構成:
親パイプライン: AWS Web Application Firewall(Datadog標準)
├─ 標準の正規化処理(action → @system.action、clientIp → @network.client.ip 等)
│
├─ ネスト1: Add waf count tag(COUNT用)
│ └─ nonTerminatingMatchingRules からルールIDを抽出 → waf_rule タグ付与
│
└─ ネスト2: Add waf block tag(BLOCK用)
└─ terminatingRuleId からルールIDを抽出 → waf_rule タグ付与Pipelineで付与されるタグ:
| タグ | 内容 | 用途 |
|---|---|---|
waf_action | WAFのアクション(BLOCK, COUNT) | アクション別集計・アラート |
waf_rule | マッチしたルールID(例: core-rule-set, ip-reputation) | ルール別集計・フィルタリング |
waf_host | リクエスト先ドメイン(例: p.fstdr.jp, backend.fastdoctor.jp) | サービス別集計 |
waf_useragent | リクエストのUser-Agent | Bot分析・不審UA検知 |
- COUNT/BLOCK分離: ログ構造が異なるため(COUNTは
nonTerminatingMatchingRules配列、BLOCKはterminatingRuleId)、ネストパイプラインで分離処理 - FDサービス系WAFのログも同じ
source:wafで送信されるため、waf_hostタグでLP向けWAFとFDサービス系WAFのログを区別できる
共有IPセット
共有 IP セットは REGIONAL スコープ で新規作成する(LP 向け WAF の CLOUDFRONT スコープとは別リソース)。IP リストの内容は同一のものを管理する。
| 共有リソース | スコープ | 用途 | 共有範囲 |
|---|---|---|---|
shared_ip_blacklist_ipv4 | REGIONAL | 攻撃元IP・悪意あるASレンジのブロック | FDサービス WAF 全ACL |
shared_ip_blacklist_ipv6 | REGIONAL | 同上(IPv6) | 同上 |
ua_blacklist(リスト) | - | sqlmap, nikto, nmap, masscan のブロック | 同上 |
- IP Blacklist に追加する際は、Terraform変数定義にコメントで追加理由・追加日・対象の説明を記載する(LP設計書の運用ルールに準拠)
- LP 向け WAF(CLOUDFRONT スコープ)とは別リソースだが、IP リストの内容は Terraform 変数で共通化する
ログコスト最適化(#11672)
Geo制限は導入しないため、当初想定していた国外Blockログによるコスト圧迫リスクは低下した。ただし Block/Count ログ量が想定を超える場合は以下を検討する。
- Datadog Index Exclusion Filter で不要なログをインデックスから除外(インデックスコスト削減)
- 除外されたログの調査が必要な場合は S3 + Athena で全フィールドをSQL検索して対応
詳細は FDサービス系WAF 運用設計書 > ログコスト最適化 を参照。
Datadogアラート(既存 datadog/service_modules/waf/monitoring 活用)
既存のDatadog WAFモニタリングモジュールを活用し、以下のアラートを設定する:
| モニター | 閾値 | 目的 |
|---|---|---|
| WAF Count by Rule | 300(10分間) | マネージドルールのマッチ急増検知(Count中の攻撃検出) |
| WAF Count by IP | 300(10分間) | 特定IPからの集中アクセス検知 |
| WAF Count by UA | 300(10分間) | 不審なUser-Agent検知 |
Phase 1 で重点監視すべき項目
| 監視対象 | 確認ポイント | 対応 |
|---|---|---|
| CRS マッチ率 | 正規リクエストがマッチしていないか | 誤検知なら RuleActionOverride で個別ルールを除外 |
| SQLi マッチ率 | 検索API等で誤検知が出ていないか | 同上 |
| Datadogログ量 | インジェスト量が想定内か | 想定以上なら WAF Logging Filter の調整を検討 |
マネージドルール更新の監視
AWSはマネージドルールのデフォルトバージョンを随時更新する。更新により新たな誤検知が発生する可能性がある。
- 対策: AWS SNS通知を購読し、ルール更新時にアラートを受け取る
- 代替案: 特定のスタティックバージョンにピン留めし、手動でアップグレード+テスト
- 推奨: 初期はデフォルトバージョン(自動更新)で運用し、誤検知が多発するようなら固定に切り替え
ALB + WAF 構成の注意点
| # | 注意点 | 詳細 | 対応 |
|---|---|---|---|
| 1 | WAF変更の伝播遅延 | WAFルール変更は反映まで数秒〜数分かかる | ルール変更はトラフィックの少ない時間帯に実施 |
| 2 | WCU上限 | REGIONAL Web ACLの上限は1,500 WCU。現設計で1,182 WCU使用、残り318 WCU | ルール追加時はWCU予算を確認 |
| 3 | マネージドルールの自動更新 | AWSがデフォルトバージョンを更新すると新たな誤検知が発生する可能性 | SNS通知を購読 or バージョン固定 |
| 4 | 共有IPセットのスコープ | LP向けWAF(CLOUDFRONT)とはスコープが異なるため、REGIONAL スコープで別途作成が必要 | Terraform変数でIPリストを共通化し、両スコープに適用 |
コスト見積もり
サービスあたりの月額コスト
| リソース | 費用 | 備考 |
|---|---|---|
| WAF Web ACL | $5/月 | 1 ACLあたり |
| WAF ルール | $10/月 | 10ルール × $1(Anonymous IP List, SQLi, サービス個別IPセット追加分含む) |
| WAF リクエスト | $0.60/100万リクエスト | |
| Kinesis Firehose | トラフィック依存 | WAFログ転送 |
| S3 (WAFログバックアップ) | 微量 | Block/Countログのみ保存 |
| 合計(固定費) | 約$16/月/サービス | トラフィック費用除く。CloudFront 料金なし |
全体コスト概算(10サービス想定)
| 項目 | 月額 |
|---|---|
| WAF固定費(ACL + ルール) | $160 |
| WAFリクエスト費 | トラフィック依存 |
| Kinesis Firehose + S3 | トラフィック依存 |
| Datadog WAFログ取り込み | 要確認(ログ量次第) |
| CloudFlare | $0(DNS onlyは無料) |
Datadogログコストについて: Block/Countログのみ転送するフィルタが設定済み。 Geo制限は導入しないため、国外Blockログによるコスト圧迫リスクは低い。
注: CloudFront 料金が不要になるため、CloudFront + WAF 構成と比較して月 $10〜20 程度の削減が見込める
実装計画
優先順位
パイロットサービスで導入検証し、順次展開する。
fd-prod アカウント(*.fstdr.jp):
- パイロット(1サービス): トラフィックが少なく影響範囲の小さいサービスから開始(選定は別途)
- 残りの
*.fstdr.jpサービス: パイロットの結果を踏まえて順次導入
fd-sys アカウント(*.fastdoctor.jp + member.fstdr.jp):
*.fastdoctor.jpサービス: fd-prodでの導入が安定してから。Terraform管理場所(fastdoctor-manager/)とACM証明書が異なる
Terraform実装方針
- 既存の
template_modules/options/cloudfront-waf/をベースに、REGIONAL スコープ版の WAF モジュールを作成- 追加が必要なルール: Anonymous IP List、SQLi Rule Set、サービス個別IPセット
- 既存のルール構成(
rules.tf)に追加ルールを動的に組み込む - REGIONAL スコープの共有 IP セット(
waf-shared-sets)を新規作成
waf-shared-setsモジュールは既存のまま共有リソースとして活用- Datadog WAFモニタリング(
datadog/service_modules/waf/monitoring)も既存モジュールを活用
考慮事項
| 項目 | 対応方針 |
|---|---|
| fd-video(WebSocket/WebRTC) | WebRTCはUDP直接通信。HTTPS signaling部分のALBにWAFをアタッチ |
| API Gateway サービス(payment, dr-site, member) | 別途検討。HTTP API は WAF 非サポート。CloudFront を前段に置く構成等を検討する。詳細は「API Gateway サービスの扱い」を参照 |
| u.fstdr.jp(Heroku リダイレクト) | Heroku側にIP制限がなければ問題なし |
| Geo制限 | 導入しない。海外勤務の医師・渡航中の患者向けサービスがあるため |
| Rate Limit 例外IP | NAT IP(各VPC)、オフィスIP、Datadog Synthetics を force-allow-ip-list に登録 |
| マネージドルール更新通知 | AWS SNS トピックの購読設定を検討 |
変更履歴
| 日付 | バージョン | 変更内容 | 担当 |
|---|---|---|---|
| 2026-05-25 | 1.0 | 初版作成 | 大賀 |
| 2026-06-08 | 2.0 | CloudFront + WAF → ALB + WAF(REGIONAL)に方針変更。staging 検証で CloudFront 固有の制約(GET + body → 403 等)が判明したため | 大賀 |
| 2026-06-18 | 2.1 | CRS SizeRestrictions_BODY 除外方針を追記。fd-system production の WAF ログ分析で正常な業務操作(PATCH 13K〜22K バイト)が Count 検知の 82.6% を占めていたため | 大賀 |
| 2026-06-23 | 2.2 | Phase 2 実績を追記。known-bad-inputs を Block 昇格(fd-system staging)。CRS SizeRestrictions_QueryString 除外を追加(医師スケジュール管理等で検知) | 大賀 |
| 2026-06-23 | 2.3 | cp.fastdoctor.jp / u.fastdoctor.jp の WAF 分析結果を追記。SizeRestrictions_BODY の Exclusion Filter 対象に cp.fastdoctor.jp を追加。サービス一覧の備考を充実 | 大賀 |
| 2026-07-01 | 2.4 | p.fstdr.jp(online-patient-mypage)の SizeRestrictions_BODY 除外方針を追記。画像アップロード(AI triage添付 + カルテ画像)で5日間659件の誤検知、全て正規医療業務 | 大賀 |