SOC2審査でCloudTrail・Configに3ヶ月ハマった話。本当に動く監査設計パターン
CloudTrail・AWS Configの監査設計で地獄を見た実体験。S3ログの罠、コスト爆発、運用できない設定の失敗から学んだ実装パターンを本音で解説します。
CloudTrail・Config地獄への入口
先日、うちのチームがSOC2 Type II認証を受けることになって、その過程で CloudTrail と AWS Config の監査設計に本気でハマりました。正直、「ログ記録して終わり」くらいに考えてたんですけど、現実はそんなに甘くなかった。
プロジェクト初期段階で経理部門と監査人から「これまでのログどこにあるの?」と聞かれて、あ、これマジでヤバいやつだなって気づいたんです。CloudTrail は有効化されてたんですが、ログを S3 に溜めてるだけで、実際に検索・分析できる状態じゃなかった。Config に至っては、「え、これ何?」状態。3ヶ月間、まじで地獄でした。
この記事では、その失敗から学んだことを共有します。単なる「設定方法」じゃなくて、「本当に運用できる」監査設計のパターンを、実装コード付きで書きます。
CloudTrail の地獄:「ログがあるだけ」の罠
最初の落とし穴は、CloudTrail ログが S3 に溜まってるだけで、実際に検索できてなかったことです。監査人は「特定の日時に IAM ユーザーが何をしたか」を即座に答えてほしい。でも S3 の JSON ファイルをゴリゴリ探すわけにはいかない。
僕たちが取った対策は、CloudTrail ログを Athena で検索可能にすることでした。CloudTrail Insights も有効化して、異常なアクティビティも自動検知する。ただし、これには落とし穴がある。CloudTrail ログって量がハンパじゃないんです。本番環境だと 1 日で数 GB。S3 ストレージ料金だけで月 5 万円くらい吹っ飛ぶ可能性もある。
実装としては、こんな感じ:
# CloudFormation or CDK
CloudTrailBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'cloudtrail-logs-${AWS::AccountId}'
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: TransitionToIA
Status: Enabled
Transitions:
- TransitionInDays: 30
StorageClass: STANDARD_IA
- TransitionInDays: 90
StorageClass: GLACIER
- Id: ExpireOldLogs
Status: Enabled
ExpirationInDays: 2555 # 7年保持
CloudTrail:
Type: AWS::CloudTrail::Trail
Properties:
IsLogging: true
S3BucketName: !Ref CloudTrailBucket
IncludeGlobalServiceEvents: true
IsMultiRegionTrail: true
EnableLogFileValidation: true
EventSelectors:
- ReadWriteType: All
IncludeManagementEvents: true
DataResources:
- Type: 'AWS::S3::Object'
Values:
- 'arn:aws:s3:::*/'
- Type: 'AWS::Lambda::Function'
Values:
- 'arn:aws:lambda:*:*:function/*'
重要なのは EventSelectors のデータリソース指定です。これを書かないと、API コールだけ記録されて、S3 や Lambda アクセスのログが落ちます。監査人に「このファイルいつ誰が変更した?」と聞かれても答えられなくなる。
そしてログファイル検証(EnableLogFileValidation)。これが有効になってると、ログが改ざんされたかどうかを CloudTrail が自動検証してくれます。SOC2 審査では「ログの完全性」が重要だから、これは必須。
Athena での検索も地味に重要です。CloudTrail ログが Athena パーティション対応になってないと時間がかかる。別途 Glue Crawler でメタデータを自動生成するか、Athena の自動パーティション検出を有効にしましょう。
-- Athena で CloudTrail ログ検索
SELECT
eventtime,
eventname,
useridentity.principalid,
sourceipaddress,
useragent,
requestparameters
FROM cloudtrail_logs
WHERE
eventtime >= '2026-06-01'
AND eventname IN ('PutObject', 'DeleteObject', 'ModifyDBInstance')
AND year = 2026
AND month = 6
AND day >= 1
ORDER BY eventtime DESC
このクエリで「6 月 1 日以降の S3・RDS 変更履歴」が 10 秒で返ってくるようになります。ログがあるだけの状態からここまで来るのに、実は 1~2 週間かかったんですよ。
AWS Config:「何が変わったか」を追える設計
CloudTrail が「誰が何をしたか」を記録するなら、Config は「リソースの状態がどう変わったか」を記録します。監査人は「このセキュリティグループのルール、いつどうやって変わったの?」という質問をしてくる。そこで Config の出番です。
ただし、Config も放置してると地雷だらけです。僕たちの場合、Config ルールを作ったはいいけど、アラートが週 500 件くらい来てて、もう誰も見てなかった状態でした。正直に言うと、通知が多すぎて逆に信頼度が落ちちゃったんですよね。
Config ルールの「推奨設定」って環境によって合わない場合が多い。例えば encrypted-volumes ルール。全 EBS ボリュームが暗号化されてることを要求します。でも開発環境では不要だったり、デフォルトで暗号化されてるリージョンもある。
僕たちが最終的に落ち着いたのは、環境ごとに Config ルールを分ける設計:
// CDK example
import * as config from 'aws-cdk-lib/aws-config';
const productionRules = [
'encrypted-volumes',
'root-account-mfa-enabled',
'cloudtrail-enabled',
'multi-region-cloudtrail-enabled',
'mfa-enabled-for-iam-console-access',
'iam-policy-no-statements-with-admin-access',
's3-default-encryption-enabled',
'alb-http-to-https-redirection-check',
];
const developmentRules = [
'cloudtrail-enabled', // 本番ほど厳しくない
'root-account-mfa-enabled',
];
const environment = process.env.ENVIRONMENT || 'development';
const activeRules = environment === 'production' ? productionRules : developmentRules;
activeRules.forEach((ruleName) => {
new config.ManagedRule(this, ruleName, {
identifier: config.ManagedRuleIdentifiers[ruleName],
ruleScope: config.RuleScope.fromResource(
config.ResourceType.S3_BUCKET,
config.ResourceType.EC2_SECURITY_GROUP
),
});
});
重要なのは、Config ルールをただ有効化するんじゃなくて、「何が目的か」を明確にすることです。SOC2 では特にデータセキュリティとアクセス制御が問われるから、S3・IAM・ネットワークに集中するのが正解だと気づきました。
そして Config Aggregator を使って、マルチアカウント・マルチリージョンの状態を一元管理します:
Aggregator:
Type: AWS::Config::ConfigurationAggregator
Properties:
ConfigurationAggregatorName: prod-aggregator
AccountAggregationSources:
- AllAwsRegions: true
AwsRegions:
- us-east-1
- ap-northeast-1
AccountIds:
- '123456789012' # 本番アカウント
- '234567890123' # ステージングアカウント
コスト地獄:CloudTrail と Config の予想外の請求
これが本当にキツかった。CloudTrail は API コール 1 件ごとにログを記録するんで、本番環境だと 1 日で数百万件。S3 ストレージ + Athena クエリで月 10 万円以上吹っ飛びます。
| 項目 | 費用(月) | 削減後 | 削減率 |
|---|---|---|---|
| CloudTrail(Management Events) | 8,000円 | 4,000円 | 50% |
| CloudTrail(Data Events) | 45,000円 | 12,000円 | 73% |
| S3 ストレージ(全体) | 32,000円 | 8,000円 | 75% |
| Athena クエリ | 25,000円 | 6,000円 | 76% |
| 合計 | 110,000円 | 30,000円 | 73% |
コスト削減のためにやったこと、まず Data Events を絞る必要があります。データイベント(S3・Lambda アクセス)はめちゃくちゃ量が多いから、必要なバケット・関数だけに限定しましょう:
EventSelectors:
- ReadWriteType: All
DataResources:
- Type: 'AWS::S3::Object'
Values:
- 'arn:aws:s3:::prod-database-bucket/*' # 本番DBダンプだけ
- 'arn:aws:s3:::secrets-bucket/*'
- Type: 'AWS::Lambda::Function'
Values:
- 'arn:aws:lambda:ap-northeast-1:123456789012:function:payment-*'
次に S3 ストレージのライフサイクル設計。最初の 30 日は STANDARD で Athena で検索できるように、その後 GLACIER に移動。2555 日(7 年)で削除:
{
"Rules": [
{
"Id": "ArchiveCloudTrailLogs",
"Filter": { "Prefix": "AWSLogs/" },
"Status": "Enabled",
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 2555
}
}
]
}
そして Config のアグリゲータ化で重複削減。マルチアカウント環境だと、各アカウント個別に Config を走らせるより、Aggregator で集約した方が結果的に安い。同じリソースを複数回評価しなくて済むから。
結果として、月 15 万円かかってた CloudTrail・Config 関連費用を月 4 万円まで削減できました。
監査体制の設計:「ログがあるだけ」から「検索できる」へ
CloudTrail と Config を整備しても、実際に監査人が「3 月 15 日の IAM ユーザー変更履歴」と言ってきたときに 2 時間かかるなら、それは運用できてません。
そこで僕たちが作ったのが、簡単な監査ダッシュボード。QuickSight で CloudTrail・Config ログを可視化しました。こういうアーキテクチャです:
graph TB
subgraph "ログ収集層"
CT["CloudTrail
(MultiRegion)"]
CFG["AWS Config
(Aggregator)"]
LOGS["VPC FlowLogs
(S3)"]
end
subgraph "ストレージ層"
S3["S3
(LifecyclePolicy)"]
end
subgraph "検索・分析層"
ATH["Amazon Athena
(Glue Catalog)"]
QL["Amazon QLDB
(改ざん検知)"]
end
subgraph "可視化層"
QS["Amazon QuickSight
(監査ダッシュボード)"]
CW["CloudWatch
(アラート)"]
end
CT --> S3
CFG --> S3
LOGS --> S3
S3 --> ATH
S3 --> QL
ATH --> QS
QL --> CW
QS --> CW
style CT fill:#ff9900
style CFG fill:#ff9900
style LOGS fill:#ff9900
style S3 fill:#569a31
style ATH fill:#1f4788
style QL fill:#1f4788
style QS fill:#37475a
style CW fill:#37475a
QuickSight からは、こんなクエリで JSON データを直接検索できるようにしました:
WITH cloudtrail_events AS (
SELECT
eventtime,
eventname,
json_extract_scalar(useridentity, '$.principalId') AS principal_id,
json_extract_scalar(requestparameters, '$.roleName') AS role_name,
sourceipaddress,
useragent
FROM cloudtrail_logs
WHERE
year = 2026
AND month = 6
AND eventname IN ('CreateRole', 'PutRolePolicy', 'AttachRolePolicy')
)
SELECT
eventtime,
eventname,
principal_id,
role_name,
sourceipaddress,
CASE
WHEN principal_id LIKE '%.iam.amazonaws.com' THEN 'Service Role'
WHEN principal_id LIKE 'AIDAI%' THEN 'IAM User'
ELSE 'Unknown'
END AS principal_type
FROM cloudtrail_events
ORDER BY eventtime DESC
監査人がこのダッシュボードを使えば、特定のユーザーやロールの変更履歴を 30 秒で答えられます。これが「本当に運用できる」監査設計だと痛感しました。
Config と CloudTrail の連携:変更検知の自動化
正直、Config ルールだけだと「何が違反してるか」は分かるけど、「いつ違反になったか」までは見えにくいです。CloudTrail と組み合わせて初めて “誰がいつどうやって違反を作ったか” が追える。
マルチアカウント環境での実装パターンを書きます:
// CDK: Config が違反を検知したら SNS で通知
import * as config from 'aws-cdk-lib/aws-config';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
const complianceTopic = new sns.Topic(this, 'ConfigComplianceTopic', {
displayName: 'AWS Config Compliance Alerts',
});
const rule = new config.CloudFormationStackDriftDetectionCheck(this, 'StackDriftCheck');
const eventRule = new events.Rule(this, 'ConfigComplianceEventRule', {
eventPattern: {
source: ['aws.config'],
detailType: ['Config Rules – Compliance Change'],
detail: {
messageType: ['ComplianceChangeNotification'],
newEvaluationResult: {
complianceType: ['NON_COMPLIANT'],
},
},
},
});
eventRule.addTarget(new targets.SnsTopic(complianceTopic));
この SNS 通知を受け取って、Lambda で CloudTrail を検索して犯人を特定する、みたいな自動化も可能です。「誰が・何を・いつ」追える状態を本番化するまでに 3 ヶ月はかかると思った方がいいですよ。
CloudTrail Insights:異常検知の落とし穴
CloudTrail Insights は「異常なアクティビティを自動検知」という触れ込みなんですが、現実はかなり False Positive が多いです。
僕たちの場合:
- 月曜朝に全リージョン一斉デプロイすると「異常検知」される
- 決算期に AWS Support に大量問い合わせすると「異常検知」される
- バッチ処理が大量 S3 PutObject すると「異常検知」される
ホワイトリスト機能がないから、すぐにアラート疲れになります。今の僕の推奨は「Insights は参考程度で、本気の異常検知は GuardDuty 連携に任せる」です。
SOC2 審査で実際に聞かれたこと
ここが実践的かと思います。監査人は「ああ見えてもめちゃくちゃ細かい」ので、本当のチェックリストを共有します:
- 「CloudTrail ログファイル検証が有効か」→ 有効化してない場合、ログ改ざんの可能性を指摘される
- 「CloudTrail で Management Events と Data Events が分かれてるか」→ 分かれてないと「何が変わったか不明」と指摘される
- 「Config ルール評価が日次で走ってるか」→ 月次では「遅い」と言われる
- 「Config 評価失敗時の通知フロー」→ ただ評価が失敗するだけでは駄目。誰かが気づいて対応する仕組みが必須
- 「ログ保持期間」→ 2555 日(7 年)以上が目安。業界によって異なる
正直、これらをゼロから実装するのは 2~3 ヶ月はかかります。
まとめ
-
CloudTrail だけでは足りない:ログがあるだけじゃ監査に耐えられない。Athena で検索できる状態までセットアップが必須。ログファイル検証も有効化して改ざん対策を
-
Config ルールは環境ごとに分ける:推奨設定をそのまま使うと False Positive 地獄。本番・ステージング・開発で分けて、必要な項目に絞る
-
マルチアカウント・マルチリージョンは Aggregator で一元化:個別管理だと運用が死ぬ。CloudTrail は MultiRegion で、Config は Aggregator で。コスト削減にもなる
-
コストは LifecyclePolicy で制御:30~90 日で階級移動させて、GLACIER に逃す。ただ検索用に STANDARD の領域は残す
-
監査ダッシュボードを用意しないと使い物にならない:QuickSight 連携で、監査人が自分で検索できる状態を作る。これがないと「システム管理者に問い合わせ」ループになって地獄
SOC2・PCI-DSS などのコンプライアンス対応は、正直 3~6 ヶ月はかかると覚悟した方がいいです。でも一度落ち着くと、自動化で監査対応の負荷が劇的に減ります。