はじめに
ある日突然、API Gatewayの4xxエラーが急増し、CloudWatchアラートが鳴り響きました。しかし、実行ログを確認してもエラーの痕跡が見つからない...。
本記事では、実際に発生したこの問題について、原因調査からAWS WAFを使った恒久対策まで、実践的な解決方法を解説します。
問題の詳細
発生した症状
- 現象: API Gatewayの4xxエラーメトリクスが急増
- 環境: 試験用アカウント(通常はアクセス頻度が低い)
- 資材変更: 前回デプロイ以降、変更なし
不可解な点
CloudWatchメトリクスでは4xxエラーが大量に記録されているのに、以下のログには何も記録されていませんでした:
- API Gatewayの実行ログ
- Lambda関数のCloudWatchログ
メトリクスとログの矛盾が、調査を困難にしていました。
問題の調査結果
ステップ1: 実行ログの確認
API Gatewayのロググループ(API-Gateway-Execution-Logs_{api-id}/stage)を確認したところ、かなり前のシステム試験以降のログが存在しませんでした。
統合されているLambda関数のCloudWatchログも同様に、長期間実行されていない状態でした。
ステップ2: 手動テストの実施
Postmanで実際にAPIを呼び出してみると、正常に動作しました:
// レスポンス
[]
// ステータスコード
200 OK
Lambda関数も正常に実行され、ログも記録されています。つまり、APIそのものは正常に動作していることが確認できました。
ステップ3: AWS公式ドキュメントで判明した事実
AWSサポートから提示された参考資料を確認したところ、重要な事実が判明しました:
API Gatewayは、次のエラーに関するログを生成しない場合があります:
- "413 Request Entity Too Large" エラー
- "431 Request Header Fields Too Large" エラー
- "Excessive 429 Too Many Requests" エラー
- APIマッピングを設定せず、クライアントがカスタムドメインにリクエストを行った場合の4xxエラー
- "403 Missing Authentication Token" エラー(誤ったリソースパスへのアクセス)
- 内部の障害が原因で発生する5xxエラー
出典: API Gateway REST API の CloudWatch ログが見つからない場合のトラブルシューティング
これが、メトリクスには記録されているのにログが残っていない理由でした。
ステップ4: アクセスログの有効化
実行ログに記録されないエラーを調査するため、アクセスログを有効化しました。
設定手順
-
CloudWatchでロググループを作成
/aws/apigateway/{YourAPIName}-access-logs -
API Gatewayコンソールで設定
- ステージ → ログ/トレース → カスタムアクセスログを有効化
- ログ形式(JSON):
{
"requestId":"$context.requestId",
"extendedRequestId":"$context.extendedRequestId",
"ip":"$context.identity.sourceIp",
"requestTime":"$context.requestTime",
"httpMethod":"$context.httpMethod",
"resourcePath":"$context.resourcePath",
"status":"$context.status",
"error":"$context.error.message"
}
今回のような攻撃の内容のように通常のAPI Gatewayログだけでは検知できないものもあるため、API Gatewayの詳細ログやカスタムログも残しておくと安心です。
ステップ5: 真相が明らかに
アクセスログを確認すると、以下のようなログが大量に記録されていました:
{
"requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"extendedRequestId": "XxXxXxXxXxXxXxX=",
"ip": "20.xxx.xxx.xx",
"requestTime": "22/Jan/2026:03:55:54 +0000",
"httpMethod": "GET",
"resourcePath": "/aa.php",
"status": "403",
"error": "Missing Authentication Token"
}
犯人はBot攻撃
攻撃の特徴
- 送信元IP: 20.xxx.xxx.xx(すべて同一IPアドレス)
-
リクエストパス:
/aa.php、その他の存在しないPHPファイル - エラー: 403 Forbidden - Missing Authentication Token
これは典型的な脆弱性スキャンまたは自動化されたBot攻撃でした。
解決案の検討
Bot攻撃の影響分析
1. コスト面の影響 ⚠️
API Gatewayはリクエスト数に応じて課金されます。403エラーでもリクエストとしてカウントされるため、大量のBot攻撃があると不要なコストが発生します。
- 月間100万リクエストの攻撃 → 約$3.50の追加コスト(ap-northeast-1リージョン)
2. パフォーマンス面の影響 ⚠️
現在の設定ではスロットリング(Burst Limit 1000, Rate Limit 1000)を設定していますが、Bot攻撃がこの上限を消費すると、正規ユーザーのリクエストが429エラーになる可能性があります。
3. Lambda実行への影響 ✅
403エラー(Missing Authentication Token)はAPI Gatewayレベルで拒否されるため、Lambda関数は実行されず、Lambda側のコストや負荷は発生しません。
なぜWAFが必要か?
CloudWatchアラートだけでは不十分な理由:
- 事後検知: アラートは影響が出た後に通知される
- コスト削減不可: Bot攻撃のリクエストも課金対象
- スロットリング消費: 正規ユーザーへの影響リスク
AWS WAFを導入することで:
- API Gatewayに到達する前にブロック → コスト削減
- リアルタイムで防御
- スロットリング上限を正規ユーザーのために確保
WAFとは?
AWS WAF(Web Application Firewall)は、Webアプリケーションを一般的なWeb攻撃から保護するサービスです。
WAFの主な機能
- レート制限: 同一IPからの過剰なリクエストをブロック
- 地理的ブロック: 特定の国からのアクセスを制限
- IPレピュテーション: 既知の悪意あるIPをブロック
- カスタムルール: 独自の条件でリクエストをフィルタリング
WAFの料金
| 項目 | 料金 |
|---|---|
| Web ACL | $5/月 |
| ルール | $1/月/ルール |
| リクエスト | $0.60/100万リクエスト |
| AWS Managed Rules | 無料 |
例: 5つのルールを設定した場合
- 基本料金: $10/月
- リクエスト料金: リクエスト数に応じて変動
WAF設定内容
今回実装した5つのルールを紹介します。
ルール1: レート制限
同一IPアドレスから5分間に100リクエスト以上のアクセスをブロック(1分間あたり20リクエスト相当)。
_wafv2.CfnWebACL.RuleProperty(
name="RateLimitRule",
priority=1,
statement=_wafv2.CfnWebACL.StatementProperty(
rate_based_statement=_wafv2.CfnWebACL.RateBasedStatementProperty(
limit=100, # 5分間に100リクエスト
aggregate_key_type="IP"
)
),
action=_wafv2.CfnWebACL.RuleActionProperty(
block=_wafv2.CfnWebACL.BlockActionProperty()
),
visibility_config=_wafv2.CfnWebACL.VisibilityConfigProperty(
sampled_requests_enabled=True,
cloud_watch_metrics_enabled=True,
metric_name="RateLimitRule"
)
)
AWS WAFのレート制限は5分間の評価ウィンドウを使用します。1分間に20リクエストに制限したい場合は、limit=100と設定します。
ルール2: 地理的ブロック
日本以外からのアクセスをブロックします。
_wafv2.CfnWebACL.RuleProperty(
name="GeoBlockRule",
priority=2,
statement=_wafv2.CfnWebACL.StatementProperty(
not_statement=_wafv2.CfnWebACL.NotStatementProperty(
statement=_wafv2.CfnWebACL.StatementProperty(
geo_match_statement=_wafv2.CfnWebACL.GeoMatchStatementProperty(
country_codes=["JP"] # 日本のみ許可
)
)
)
),
action=_wafv2.CfnWebACL.RuleActionProperty(
block=_wafv2.CfnWebACL.BlockActionProperty()
),
visibility_config=_wafv2.CfnWebACL.VisibilityConfigProperty(
sampled_requests_enabled=True,
cloud_watch_metrics_enabled=True,
metric_name="GeoBlockRule"
)
)
日本以外からのアクセスをブロックする場合は、SaaSアプリ含めて国内からのアクセスに限定される場合のみとしてください。
例えば、試験ツールが海外クラウドのSaaSを利用している場合、API試験がWAFによりブロックされる可能性が高いです。
ルール3: AWS Managed Rules - IPレピュテーションリスト
AWSが管理する既知の悪意あるIPアドレスからのアクセスを自動的にブロックします。
_wafv2.CfnWebACL.RuleProperty(
name="AWSManagedRulesIPReputationList",
priority=3,
override_action=_wafv2.CfnWebACL.OverrideActionProperty(
none={}
),
statement=_wafv2.CfnWebACL.StatementProperty(
managed_rule_group_statement=_wafv2.CfnWebACL.ManagedRuleGroupStatementProperty(
vendor_name="AWS",
name="AWSManagedRulesAmazonIpReputationList"
)
),
visibility_config=_wafv2.CfnWebACL.VisibilityConfigProperty(
sampled_requests_enabled=True,
cloud_watch_metrics_enabled=True,
metric_name="AWSManagedRulesIPReputationList"
)
)
ルール4: AWS Managed Rules - 既知の不正入力
SQLインジェクション、XSSなどの一般的な攻撃をブロックします。
_wafv2.CfnWebACL.RuleProperty(
name="AWSManagedRulesKnownBadInputsRuleSet",
priority=4,
override_action=_wafv2.CfnWebACL.OverrideActionProperty(
none={}
),
statement=_wafv2.CfnWebACL.StatementProperty(
managed_rule_group_statement=_wafv2.CfnWebACL.ManagedRuleGroupStatementProperty(
vendor_name="AWS",
name="AWSManagedRulesKnownBadInputsRuleSet"
)
),
visibility_config=_wafv2.CfnWebACL.VisibilityConfigProperty(
sampled_requests_enabled=True,
cloud_watch_metrics_enabled=True,
metric_name="AWSManagedRulesKnownBadInputsRuleSet"
)
)
ルール5: カスタムルール - 不正なファイル拡張子ブロック
今回のような.phpファイルへのスキャンを防止します。
まず、正規表現パターンセットを作成:
def _create_regex_pattern_set(self):
"""不正なファイル拡張子の正規表現パターンセットを作成"""
regex_pattern_set = _wafv2.CfnRegexPatternSet(
self,
"MaliciousFileExtensionsPatternSet",
scope="REGIONAL",
regular_expression_list=[
r".*\.php$",
r".*\.asp$",
r".*\.aspx$",
r".*\.jsp$",
r".*\.cgi$",
r".*\.exe$",
r".*\.dll$",
r".*\.sh$",
r".*\.bat$",
],
name="MaliciousFileExtensionsPatternSet"
)
return regex_pattern_set
どの拡張子をブロックするかは、配信するAPIやサイトによって検討ください。
例えば、.phpなどは動的サイトで利用するケースも多々あるため、構成ファイルと攻撃パターンによって変更をおおすすめします。
なお、末尾マッチだけのため、r"\.php$でも問題なく動作する想定です。
次に、このパターンセットを使用するルールを作成:
_wafv2.CfnWebACL.RuleProperty(
name="BlockMaliciousFileExtensions",
priority=5,
statement=_wafv2.CfnWebACL.StatementProperty(
regex_pattern_set_reference_statement=_wafv2.CfnWebACL.RegexPatternSetReferenceStatementProperty(
arn=self._create_regex_pattern_set().attr_arn,
field_to_match=_wafv2.CfnWebACL.FieldToMatchProperty(
uri_path={}
),
text_transformations=[
_wafv2.CfnWebACL.TextTransformationProperty(
priority=0,
type="LOWERCASE"
)
]
)
),
action=_wafv2.CfnWebACL.RuleActionProperty(
block=_wafv2.CfnWebACL.BlockActionProperty()
),
visibility_config=_wafv2.CfnWebACL.VisibilityConfigProperty(
sampled_requests_enabled=True,
cloud_watch_metrics_enabled=True,
metric_name="BlockMaliciousFileExtensions"
)
)
カスタムドメイン対応
環境によってカスタムドメインの有無が異なるため、設定ファイルで制御できるようにしました。
settings.ini:
[CUSTOM_DOMAIN]
# カスタムドメインを使用する場合はTrueに設定
ENABLED = False
# カスタムドメイン名
DOMAIN_NAME = example.com
app.py:
# カスタムドメイン設定の読み込み
custom_domain_enabled = config_ini.getboolean("CUSTOM_DOMAIN", "ENABLED", fallback=False)
custom_domain_name = config_ini.get("CUSTOM_DOMAIN", "DOMAIN_NAME", fallback=None)
# WAFスタックの作成
waf_stack = WAFStack(app, f"{project}-{stage}-cdk-stack-waf", env=env)
# カスタムドメインの有無に応じてWAFを適用
if custom_domain_enabled and custom_domain_name:
# カスタムドメインを使用する場合
custom_domain_arn = f"arn:aws:apigateway:{env.region}::/domainnames/{custom_domain_name}"
waf_stack.create_waf_for_api_gateway(custom_domain_arn, "YourAPIName")
else:
# API Gatewayステージに直接適用
api_gateway_arn = f"arn:aws:apigateway:{env.region}::/restapis/{rest_api_stack.api.rest_api_id}/stages/stage"
waf_stack.create_waf_for_api_gateway(api_gateway_arn, "YourAPIName")
waf_stack.node.add_dependency(rest_api_stack)
デプロイ
cd cdk_deploy
# 差分確認
cdk diff {your-stack-name}-waf
# デプロイ
cdk deploy {your-stack-name}-waf
まとめ
学んだこと
-
API Gatewayの実行ログは万能ではない
- 特定の4xxエラー(403 Missing Authentication Tokenなど)は実行ログに記録されない
- アクセスログの併用が重要
-
メトリクスとログの乖離に注意
- CloudWatchメトリクスに記録されていても、ログがない場合がある
- AWS公式ドキュメントで仕様を確認することが重要
-
Bot攻撃は予想以上に一般的
- 試験環境でも攻撃対象になる
- 早期の対策が重要
-
WAFは必須のセキュリティ対策
- コストよりもセキュリティとパフォーマンスの保護が重要
- CDKで簡単に実装できる
ベストプラクティス
ログ戦略
- 実行ログとアクセスログの両方を有効化
- アクセスログには必ず
$context.error.messageを含める
監視戦略
- 4xxエラー数ではなく、4xxエラー率で監視
- 正常なリクエストとエラーの比率を追跡
セキュリティ戦略
- WAFは早期に導入
- レート制限と地理的ブロックは基本
- AWS Managed Rulesを活用
コスト最適化
- 不要なBot攻撃をWAFでブロック
- スロットリング上限を正規ユーザーのために確保
参考資料
- API Gateway REST API の CloudWatch ログが見つからない場合のトラブルシューティング
- AWS WAF Developer Guide
- AWS Managed Rules for AWS WAF
- Set up CloudWatch logging for REST APIs in API Gateway
API Gatewayの4xxエラー急増という一見単純な問題でしたが、調査を進めることで、AWSのログ記録の仕様、Bot攻撃の実態、WAFの重要性など、多くの学びがありました。
特に、「実行ログに記録されないエラーがある」という仕様は、知らないと調査で行き詰まってしまいます。本記事が、同じような問題に直面した方の助けになれば幸いです。