SOC2審査で月30万円の請求から学んだCloudTrail・Config設計|実装コード付き
SOC2審査でCloudTrail・Configの設定に失敗して月30万円の請求を食らった話。審査官に指摘された失敗パターンと正解設計を実装コード付きで共有します。
最初は「ログ取ればいいでしょ」だった
先日、チームでSOC2 Type II審査に向けて監査ログ周りを設計し直したんですよ。最初は「CloudTrailで全イベント取ってConfig入れれば大丈夫」くらいの気持ーで始めたんですが、実際に審査官が来て見学すると、こちらが想定してなかった質問がバンバン飛んでくるんです。
「このイベントなぜ記録されてるのか」「いつからこの設定なのか」「実際に検証したことあるのか」みたいな。そこで初めて気づいたんですが、ログ取ってるだけじゃダメ。ログを取ってから、いかに効率よく追跡・検証・自動化するか が本当の勝負だったんですよね。
CloudTrail・Config両方入れたのに月30万円の請求が来た話
まず最初にハマったのが、CloudTrailの請求の読み方。API呼び出しごとに課金されるんですが、うちの場合は本番・ステージング・開発で各リージョンに展開してるので、同じオペレーションが複数リージョンで発火してた。加えてConfigのルール評価がCloudTrailをトリガーにしてるから、ダブルで費用が跳ね上がってたわけです。
やったことは2つ:
- CloudTrail Data Events の制御 — S3・Lambda・DynamoDBの詳細なデータイベントは本当に全部必要か?審査要件を確認して、必要なリソースだけに限定。
- Config Rules の評価頻度見直し — 「12時間ごと評価」から「変更時のみ評価」に切り替え。静的リソースの監査コストが大幅に下がりました。
# CloudTrail設定例(必要なイベントのみ)
CloudTrail:
EnableLogFileValidation: true
IncludeGlobalServiceEvents: true
IsLogging: true
S3BucketName: audit-logs-bucket
# Data Events は本当に必要なリソースのみ
EventSelectors:
- IncludeManagementEvents: true
ReadWriteType: All
DataResources:
- Type: AWS::S3::Object
Values:
- "arn:aws:s3:::sensitive-bucket/*" # 重要なバケットのみ
- Type: AWS::Lambda::Function
Values:
- "arn:aws:lambda:*:*:function:production-*"
これだけで月15万円くらい削減できました。審査官にも「設計が細かい」って褒められたし。
Config Rule の自動修復設定が本番で問題を起こした件
Configの自動修復機能、便利だからって使ってたんですよ。例えば「S3が公開アクセス許可になったら自動で閉じる」みたいな。でも本番運用で気づいたのが、自動修復が急に入ると、デプロイパイプラインが壊れる ってこと。
シナリオとしてはこう:
- IaCで一時的に広めのセキュリティグループ設定をデプロイ
- Config が「ルール違反だ」って検出
- 自動修復が勝手に設定を戻す
- パイプラインのテストが失敗
- 朝方に起きてチームが困惑…
なので、自動修復は本当に安全な設定のみ に限定しました。むしろ、検出したら即座に Slack通知 → 手動確認 の方がいい場面も多いです。
{
"ConfigRules": [
{
"ConfigRuleName": "s3-bucket-public-read-prohibited",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "S3_BUCKET_PUBLIC_READ_PROHIBITED"
},
"Scope": {
"ComplianceResourceTypes": ["AWS::S3::Bucket"]
},
"AutomaticRemediation": {
"Enabled": true,
"Automatic": true
}
},
{
"ConfigRuleName": "ec2-security-group-no-unrestricted-ingress-22",
"AutomaticRemediation": {
"Enabled": false
},
"Source": {
"Owner": "AWS",
"SourceIdentifier": "RESTRICTED_SSH"
}
}
]
}
自動修復を有効化するなら、本当に冪等で安全な設定(タグの追加、ログの有効化など)だけにしてます。危ない設定は検出だけにして、人間が確認してから修復する方が、トラブルは減りますね。
マルチアカウント監査が地獄になるパターン
うちは10アカウント運用してるんですが、CloudTrailとConfigを各アカウントで独立させてたんです。結果、審査官が「全アカウントの監査ログを一元的に見たい」って言ったときに、10個のアカウントを行ったり来たり する羽目に。これは見た目も悪いし、何かあったときの追跡も大変。
やったのは 集約アカウント の構築です。すべてのログとコンプライアンスデータを1つのアカウントに集めることで、監査官の確認もスムーズ、調査も格段に早くなりました。
graph TB
subgraph "Production Account"
P_CT["CloudTrail<br/>Events"]
P_CW["EventBridge<br/>Event Bus"]
end
subgraph "Development Account"
D_CT["CloudTrail<br/>Events"]
D_CW["EventBridge<br/>Event Bus"]
end
subgraph "Staging Account"
S_CT["CloudTrail<br/>Events"]
S_CW["EventBridge<br/>Event Bus"]
end
subgraph "Audit Account"
AUDIT_S3["S3 Bucket<br/>audit-logs"]
AUDIT_ATHENA["Athena<br/>Log Query"]
AUDIT_CONFIG["Config<br/>Aggregator"]
AUDIT_DASH["Dashboard<br/>CloudWatch"]
end
P_CT -->|CloudTrail Logs| AUDIT_S3
D_CT -->|CloudTrail Logs| AUDIT_S3
S_CT -->|CloudTrail Logs| AUDIT_S3
P_CW -->|Cross-Account<br/>Event Bus| AUDIT_DASH
D_CW -->|Cross-Account<br/>Event Bus| AUDIT_DASH
S_CW -->|Cross-Account<br/>Event Bus| AUDIT_DASH
AUDIT_S3 --> AUDIT_ATHENA
AUDIT_CONFIG -->|Compliance Data| AUDIT_DASH
こうすると、監査官が見たいときは 集約アカウント1つ にアクセスすればすべてが見える。設定が複雑に見えるかもしれませんが、実装はCloudFormationのStackSetsで自動化できます。
# CloudFormation(各アカウントで実行)
AWSTemplateFormatVersion: '2010-09-09'
Resources:
CloudTrailBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: audit-logs-bucket # 集約アカウント側
PolicyText:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:PutObject
Resource: arn:aws:s3:::audit-logs-bucket/*
Condition:
StringEquals:
s3:x-amz-acl: bucket-owner-full-control
CloudTrail:
Type: AWS::CloudTrail::Trail
DependsOn: CloudTrailBucketPolicy
Properties:
S3BucketName: audit-logs-bucket
IsLogging: true
IncludeGlobalServiceEvents: true
IsMultiRegionTrail: true
EnableLogFileValidation: true
StackSets使えば、新しいアカウント追加するときも一発で自動デプロイできますね。
アラート戦略:ノイズとの付き合い方
最初は「怪しいイベント全部Slack通知」ってやってたんですよ。でも実際には 1時間で500通 のアラートが来てて、誰も見てない状況が続いた。重要なアラートまで埋もれるから、もう本当に困ったんです。
改善したのは 3段階アラート です。セキュリティレベルに応じて通知を分けることで、本当に見るべき異常をちゃんと浮かび上がらせられるようになりました。
Level 1: Critical(即座に対応が必要)
IAM権限の追加・削除、ルートアカウント活動、CloudTrail・Config設定の変更、S3バケットポリシー変更など。これらは見つけたら即座にアクション。
# EventBridge Rule (Critical)
import boto3
import json
rule_pattern = {
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["iam.amazonaws.com"],
"eventName": [
"AttachUserPolicy",
"PutUserPolicy",
"CreateAccessKey",
"CreateUser"
]
}
}
slack_notification = {
"text": "🚨 Critical: IAM変更検出",
"fields": [
{"title": "Event", "value": "$.detail.eventName"},
{"title": "User", "value": "$.detail.userIdentity.arn"},
{"title": "Time", "value": "$.time"}
]
}
Level 2: Warning(翌営業日までに確認)
EC2インスタンス起動・終了、ストレージ設定変更、ネットワーク設定変更。これらは重要だけど、緊急レベルではないので朝の定期確認で大丈夫。
Level 3: Info(集約レポート化)
API呼び出し(正常系)、リソースタグ変更、その他マイナー変更。こういったのは毎日のレポートにまとめて、週単位で眺めるくらいでいい。
これで、Slack通知は 1日あたり10件程度 に削減。本当に見るべき通知が埋もれなくなりました。
コスト最適化×監査のバランス設計
CloudTrailとConfigのコストは、正しく設計しないと本当に痛い。でも「費用削減のために監査を甘くする」はダメ。そのバランスを取るために、いくつか工夫してます。
1. CloudTrail ログの自動アーカイブ
CloudTrailログは初日は頻繁にアクセスするけど、1ヶ月後なんてほぼ見ない。だから S3 Lifecycle Policy で自動的に Glacier に移す。こうするだけでストレージコストが大幅に下がります。
{
"Rules": [
{
"Id": "ArchiveCloudTrailLogs",
"Status": "Enabled",
"Transitions": [
{
"Days": 30,
"StorageClass": "GLACIER"
},
{
"Days": 365,
"StorageClass": "DEEP_ARCHIVE"
}
],
"Expiration": {
"Days": 2555
}
}
]
}
2. Config Snapshot の定期実行
Configは「毎時間ルール評価」とか「リソース変更時ごと」にしてると費用が跳ね上がる。代わりに 1日1回のスナップショット で十分な場合も多いです。この方が費用効率がいいし、運用も楽になります。
3. CloudWatch Logs Insights で日次クエリ自動化
審査官が「昨日のS3アクセスログ見たい」って言ったときに、毎回 Athena でクエリ書いてたんですが、地味に時間がかかるんですよ。今は EventBridge + Lambda + CloudWatch Logs で自動集計するようにしました。
import json
import boto3
from datetime import datetime, timedelta
logs_client = boto3.client('logs')
def lambda_handler(event, context):
# 過去24時間のCloudTrailログをクエリ
query = """
fields @timestamp, eventName, userIdentity.arn, sourceIPAddress
| filter eventSource = "s3.amazonaws.com"
| stats count() as access_count by eventName
"""
start_time = int((datetime.now() - timedelta(hours=24)).timestamp() * 1000)
end_time = int(datetime.now().timestamp() * 1000)
response = logs_client.start_query(
logGroupName='/aws/cloudtrail',
startTime=start_time,
endTime=end_time,
queryString=query
)
return {
'statusCode': 200,
'queryId': response['queryId']
}
実装の正解形(2026年版)
最後に、「今だったらこう設計する」っていう正解形を示しときます。監査要件と費用のバランスがちょうどいい設計ですね。
flowchart TB
subgraph PROD["Production Account"]
PROD_CT["CloudTrail<br/>(API Events)"]
PROD_CONFIG["AWS Config<br/>(State)"]
end
subgraph DEV["Development Account"]
DEV_CT["CloudTrail"]
DEV_CONFIG["AWS Config"]
end
subgraph AUDIT["Audit Account (Centralized)"]
AUDIT_S3["S3 Bucket<br/>(audit-logs)"]
AUDIT_ATHENA["Athena<br/>(Query)"]
AUDIT_CONFIG["Config Aggregator<br/>(Multi-Account)"]
AUDIT_DASHBOARD["CloudWatch Dashboard<br/>(Unified View)"]
AUDIT_EVENTBRIDGE["EventBridge<br/>(Alert Rules)"]
AUDIT_SNS["SNS<br/>(Slack)"]
end
PROD_CT -->|Cross-Account<br/>Logs| AUDIT_S3
DEV_CT -->|Cross-Account<br/>Logs| AUDIT_S3
PROD_CONFIG -->|Config<br/>Aggregation| AUDIT_CONFIG
DEV_CONFIG -->|Config<br/>Aggregation| AUDIT_CONFIG
AUDIT_S3 -->|Query| AUDIT_ATHENA
AUDIT_CONFIG -->|Compliance| AUDIT_DASHBOARD
AUDIT_DASHBOARD -->|Metric| AUDIT_EVENTBRIDGE
AUDIT_EVENTBRIDGE -->|Alert| AUDIT_SNS
実装チェックリスト:
| 項目 | 実装内容 | 費用削減効果 |
|---|---|---|
| CloudTrail | Data Events は必要リソースのみ | 月-10万円 |
| Config Rules | 変更検出ベース(定期評価減) | 月-5万円 |
| ログ保存 | 30日後 Glacier、1年後 Deep Archive | 月-8万円 |
| アラート | 3段階フィルタリング | ノイズ 95%削減 |
| 集約 | 複数アカウント → 1つのダッシュボード | 運用時間 80%削減 |
まとめ
正直なところ、CloudTrail・Config監査設計は「ログ取れば大丈夫」では全くダメ。SOC2審査を経験して気づいたのは、以下の3つがセットで回って初めて意味がある、ってことです:
- 効率的なログ収集 — 必要なイベントのみ、マルチアカウント集約で無駄を削減
- 実用的なアラート — ノイズを減らして本当に見るべき異常を浮かび上がらせる
- 自動化による継続性 — EventBridge・Lambda で日々の監査を半自動化
「月30万円かかってた監査コストが、設計見直しで月12万円に削減」「チーム4人で50時間かかってた月次レポートが、自動集計で2時間に」みたいな実績が出ると、監査って結構ビジネス価値あるんだなって実感します。
次のステップとしては、CloudTrail・Configの設計がしっかりしてないと、その先のZTA実装やAI検証も絶対に上手くいきません。基盤を固めることが、セキュリティコンプライアンスの第一歩だと思いますよ。