Skip to content

GuardDuty S3 Malware Protection 導入に向けた S3 バケット棚卸し調査

関連 Issue: https://github.com/fastdoctor-jp/mental-online-karte/issues/17287Notion: S3 バケット棚卸し・データ分類(本番環境)関連設計書: GuardDuty 組織統合設計書調査期間: 2026-08-12 〜 2026-08-14


1. 調査背景

GuardDuty 組織統合設計書にて「Malware Protection for S3 は別途設計後に有効化。対象バケットの精査が必要」と記載されていた(タスク #6)。 本調査は、対象バケットの選定に必要な以下を実施した。

  • 本番環境の全 S3 バケット(367件)の棚卸し・用途分類
  • 外部ユーザー(患者)が直接アップロードするバケットの特定
  • アップロード頻度の実測とコスト試算
  • アクセスログ・ライフサイクルポリシーの検討
  • Macie との比較・導入優先度の判断

2. 調査方法

2.1 バケット一覧の取得

bash
aws s3api list-buckets --profile fd-prod-read
aws s3api list-buckets --profile fd-sys-read
  • production アカウント: 162 バケット
  • fd-sys アカウント: 205 バケット
  • 合計: 367 バケット

2.2 バケット用途の分類

以下の方法で全367バケットを分類した。

  1. 命名パターンによる自動分類: *-alb-logs-*(ALBログ)、*-codepipeline(CI/CD)、*-serverlessdeploymentbuck-*(Serverless Framework)等
  2. Terraform コード調査: aws_s3_bucket リソース定義、IAM ポリシー、CORS 設定を確認
  3. AWS CLI による実態確認: aws s3 lsaws s3api get-bucket-tagging でバケット内容・タグを確認
  4. ソースコード調査: GitHub API でアプリケーションリポジトリのアップロード/参照コードを確認

2.3 外部アップロードの特定

以下の2つのアプローチで網羅的に確認した。

  1. Terraform CORS PUT 検索: リポジトリ全体で allowed_methods = ["PUT"] を検索 → 2件のみヒット
  2. アプリケーションコード検索: presigned URL 生成、STS AssumeRole、Shrine アップロード等のパターンを GitHub コード検索で確認

2.4 アップロード頻度の計測

CloudWatch NumberOfObjects メトリクスの日次差分で算出した。

bash
aws cloudwatch get-metric-statistics \
  --namespace AWS/S3 \
  --metric-name NumberOfObjects \
  --dimensions Name=BucketName,Value={バケット名} Name=StorageType,Value=AllStorageTypes \
  --start-time 2026-08-07T00:00:00Z \
  --end-time 2026-08-14T00:00:00Z \
  --period 86400 \
  --statistics Average \
  --region {us-east-1 or ap-northeast-1}
  • production バケット: us-east-1 から取得(S3 ストレージメトリクスの仕様)
  • fd-sys バケット: ap-northeast-1 から取得

3. 調査結果

3.1 全バケット分類

分類件数GuardDuty 対象
外部ユーザー直接アップロード4○(確定)
システム録画・自動記録5×
内部バッチ・運用生成8×
CI/CD・デプロイ約170×
ML/データパイプライン6×
ログ(ALB/WAF/CDN/VPC/セキュリティ)約120×
静的コンテンツ配信8×
Amazon Connect 関連9×
Terraform/インフラ管理約10×
テスト・デモ・空・廃止約10×
要オーナー確認1可能性低

全367バケットの詳細は以下を参照。

3.2 GuardDuty S3 Malware Protection 対象バケット(確定)

バケットアカウントアップロード契機アップロード方式リージョン日次アップロード月次スキャン量月額コスト
fastdoctor-images-productionfd-sys患者が保険証・医療証、医師がカルテ画像追加Shrine presigned POSTap-northeast-1~2,490件/日~54.2 GB~$27.2
mental-reserve-patient-medical-image-productionproductionメンタルヘルス患者が保険証等をアップロード(初診時のみ)presigned URL PUT(ブラウザ直接)us-east-1~1,203件/日~87.4 GB~$15.4
ai-triage-prd-file-uploadproduction事前問診で患者が症状の写真アップロードpresigned URL PUT(STS AssumeRole → 5分期限)us-east-1~1,877件/日~41.9 GB~$15.7
online-karte-static-front-productionproduction医師が検査結果・お薬手帳・保険証画像を添付、処方箋PDF生成BFF server-side PutObject + presigned URL PUT(KB用)ap-northeast-1~1,123件/日~15.8 GB~$11.2
合計~6,693件/日~199.3 GB/月~$69.5/月(~$834/年)

コスト算出根拠:

  • 計測期間: 2026-07-14〜08-13(30日間)
  • 計測方法: CloudWatch NumberOfObjects の日次スナップショットから前日との差分を日次アップロード数として算出
  • 平均ファイルサイズ: CloudWatch BucketSizeBytes ÷ NumberOfObjects で算出(全オブジェクトの平均)
  • 月次スキャン量: 日次アップロード数 × 平均ファイルサイズ × 30日
  • 月額コスト: データスキャン料金 + リクエスト料金(リージョンにより異なる)
  • リージョン別単価(AWS Pricing API から取得):
課金項目us-east-1(バージニア)ap-northeast-1(東京)
データスキャン$0.09/GB$0.1185/GB
リクエスト$0.000215/件$0.000282/件
  • 例: fastdoctor-images-production(東京)= 54.2 GB × $0.1185 + 74,700件 × $0.000282 = $6.42 + $21.07 = ~$27.5/月
  • mental-reserve-patient-medical-image-productionai-triage-prd-file-upload は Terraform の var.regionus-east-1 であるため、バージニアリージョンの単価を適用
  • 30日計測により平日/休日・月内変動を反映済み
  • 年末年始等の繁忙期は診察件数増加に伴いアップロード数が上振れる可能性があるが、大幅な増加は想定しにくい

3.3 対象外と判断したバケット(当初候補)

バケット当初の想定調査結果
ai-triage-assets-prdユーザーアップロード先の可能性⛔ 問診定義 JSON の配信専用(ソースコード確認済み)
retool-storage-prodRetool 添付ファイルの可能性⛔ 6オブジェクト・変化なし・orca/ のみ
online-examination-images-productionオンライン健診画像⛔ サービス廃止済み(リポジトリ削除済み・30日間ゼロ)
health-center-automation外部からファイル入力の可能性⛔ 11オブジェクト・サイズほぼゼロ(影響極小)

3.4 アップロード・参照パターン

全4バケットについて、ソースコード・設計書レベルで参照パターンを確認した。

バケット毎回アップロードか過去分の参照根拠
fastdoctor-images-production診察ごとに新規追加あり(確定)@karte.images.order(id: :desc) で全件返す
mental-reserve-patient-medical-image初診時のみ(再診では不要)あり(確定)設計書「原則ファイル削除しない」。DB に objectKey 永続保存
ai-triage-prd-file-upload毎回新規あり(確定)attachments テーブルにキー永続保存
online-karte-static-front-production診察時に新規追加あり(確定)設計書「カルテ改ざん防止のため削除しない」。論理削除のみ

全バケット共通: 設計原則として「医療記録の改ざん防止のためファイルは原則削除しない」。S3 オブジェクトの物理削除は行わない。

3.5 GuardDuty S3 Malware Protection の課金形態

課金項目単価FastDoctor 推定コスト
データスキャンus-east-1: $0.09/GB / ap-northeast-1: $0.1185/GB~$18.1/月
リクエストus-east-1: $0.000215/件 / ap-northeast-1: $0.000282/件~$49.7/月
EventBridge Managed Ruleアップロード検知用イベント取り込み~$0.21/月
S3 API (GET/PUT)GuardDuty が IAM ロール経由で S3 API 実行数円/月
S3 Object Tagging(任意)スキャン結果を S3 タグで付与~$2.1/月
合計~$69.5/月

重要: スキャン対象は新規アップロードのみ。既存オブジェクト(合計 7TB+)はスキャンされない。

根拠: AWS 公式ドキュメント — "Malware Protection for S3 listens to the Amazon EventBridge notifications. When an object gets uploaded to the selected bucket or one of the prefixes, GuardDuty downloads that object..."

Free Tier はアカウントあたり月 1,000 リクエスト + 1GB。FastDoctor は日次 ~7,000 件のため初日で超過。30日間無料トライアルはなし。


4. Macie との比較・方針

Macie と GuardDuty S3 Malware の違い

項目GuardDuty S3 MalwareMacie
目的マルウェア検出機密データ発見
タイミングアップロード時リアルタイム日次サンプリング
対象選定バケット単位で明示的に有効化デフォルト全バケット → 除外リスト

Macie 見送りの理由

  1. 費用対効果が薄い: 最大容量の3バケット(合計 7TB+)は JPEG 画像で Macie は画像の中身を読めない
  2. 漏洩リスクが低い: 全バケットでパブリックアクセスブロック済み + アカウントレベルでも無効化
  3. ガバナンス未整備: 機密データの分類・管理ポリシーが未整備。Macie の検出結果を受けて是正するプロセスがない
  4. コスト: $130〜200+/月の追加費用に対して ROI が低い

ロードマップ

Phase施策状態
Phase 1GuardDuty S3 Malware Protection 導入(4バケット)対象確定・コスト試算済み → 実装へ
Phase 2S3 Server Access Logs + Storage Lens 有効化調査完了 → 実装へ
Phase 3データ分類ポリシー・ガバナンス整備未着手
Phase 4Macie 導入(ガバナンス整備後)見送り中

5. S3 Server Access Logs

導入方針

監査の観点から、医療・個人情報を含むバケット全てにアクセスログを有効化する。

  • 得られる情報: オブジェクト単位のアクセス日時・誰が・どこから・何をしたか
  • 用途: 監査対応 + ライフサイクルポリシー検討の実測データ

コスト試算

S3 Server Access Logs の課金はログファイルの S3 保存料金のみ。ロギング機能自体は無料。

試算根拠:

バケット別試算(GET リクエストを PUT の10倍と仮定):

バケットPUT/月GET/月(推定)合計リクエスト/月ログサイズ/月保存コスト/月
fastdoctor-images-production~84K~840K~924K~277 MB~$0.007
mental-reserve-patient-medical-image~34K~340K~374K~112 MB~$0.003
ai-triage-prd-file-upload~57K~570K~627K~188 MB~$0.005
online-karte-static-front-production~39K~390K~429K~129 MB~$0.003
合計~2.35M~706 MB~$0.018/月

悲観ケース(GET が PUT の100倍):

バケット合計リクエスト/月ログサイズ/月保存コスト/月
fastdoctor-images-production~8.5M~2.5 GB~$0.06
4バケット合計~23.5M~7 GB~$0.18/月

結論: 悲観ケースでも月額 $0.18(約27円)。コストは事実上ゼロ。

注: S3 Server Access Logs は PUT リクエストに対する課金もない(ログ配信自体が無料)。課金されるのは保存先バケットのストレージ料金のみ。 参考: AWS公式 - S3 Server Access Logging

現状

組織 CloudTrail(fd-organization-trail)は S3 オブジェクトレベルのログを記録していない(data_resource ブロックなし)。 CloudTrail Data Events は高コスト($0.10/10万イベント)のため、S3 Server Access Logs を採用する。

S3 Server Access Logs の設定方針

対象バケット: 医療・個人情報を含む全バケット

バケットアカウントTF管理設定方法ログ出力先
mental-reserve-patient-medical-image-productionproductionTF コードに追加fd-s3-logs-prd(既存) or 新規作成
ai-triage-prd-file-uploadproductionTF コードに追加同上
online-karte-static-front-productionproductionTF コードに追加同上
fastdoctor-images-productionfd-sys×common/production/globals/s3/ に import → TF コードに追加。または手動(AWS CLI)fd-s3-logs-prd(fd-sys 側に既存)
call-voice-recordfd-sys×手動同上
call-voice-record-transcribedfd-sys×手動同上
fastdoctor-pgbackup / -virginiafd-sys×手動同上
online-examination-images-productionfd-sys×手動(ただし廃止済みのため優先度低)同上

fd-s3-logs-prd は fd-sys アカウントに既存の S3 アクセスログ保存先バケット。production アカウント側にも同様のログ保存先バケットがあるか確認が必要。

S3 Storage Lens

Terraform での追加設定は不要。 デフォルトダッシュボード(default-account-dashboard)が AWS により全アカウントに自動作成済みであることを確認した(production / fd-sys 両方)。

  • S3 コンソール → Storage Lens → default-account-dashboard でバケット単位のメトリクスを確認可能
  • バケットごとの容量・オブジェクト数・暗号化状態・ライフサイクルルール有無が確認できる
  • Free tier のメトリクスは14日間保持
  • リクエスト数(Activity metrics)は Advanced tier(有料)が必要だが、Access Logs で代替するため不要

TF 管理外バケットの import 方針

以下の TF 管理外バケットは common/amazon-connect/globals/s3/ に import し、Access Logs を Terraform で設定する。

バケットアカウントimport 先
fastdoctor-images-productionfd-syscommon/amazon-connect/globals/s3/
call-voice-recordfd-sys同上
call-voice-record-transcribedfd-sys同上
fastdoctor-pgbackup / -virginiafd-sys同上
online-examination-images-productionfd-sys同上(優先度低:サービス廃止済み)

6. 新規バケット追加時の漏れ防止策

今後新しいサービスが S3 にユーザーアップロード機能を追加した際に GuardDuty を有効化し忘れるリスクへの対策。

  1. 開発プロセスで防ぐ: 新規バケット作成時の PR レビューチェックリストに「ユーザーアップロードを受けるか?→ GuardDuty 必須」を追加
  2. Terraform モジュールで強制する: ファイルアップロード用バケットの共通モジュールを作成し、GuardDuty + Access Logs + 暗号化 + パブリックアクセスブロックをバケット作成と同時に自動適用

7. 今後の実装タスク

#タスク備考
1GuardDuty S3 Malware Protection の Terraform 実装✅ 完了(PR #2639)。4バケットに aws_guardduty_malware_protection_plan を適用。infra-dev で検証済み
2S3 Server Access Logs の有効化✅ 完了(PR #2642)。production に fd-s3-access-logs-production を新規作成。fastdoctor-images-production を import
3S3 Storage Lens の確認✅ 対応不要。デフォルトダッシュボード(default-account-dashboard)が全アカウントに自動作成済み
4ファイルアップロード用共通モジュール設計将来の漏れ防止策

別タスク(今回のスコープ外)

タスク備考
fd-private-key の Secrets Manager 移行SSH 秘密鍵を S3 に保管しているセキュリティリスク
fastdoctor-pgbackup のバックアップ運用確認世代管理されているか等
ストレージクラス移行(Glacier 等)Access Logs 30日実測後に検討
非本番環境(staging, develop)の調査本番の結果をテンプレートに展開

8. 参考資料