-
-
Notifications
You must be signed in to change notification settings - Fork 3
Open
Description
問題の概要
Chromebookから本番環境のmeshV2 (https://graphql.api.smalruby.app/graphql) に接続すると、CORSエラーが発生します。
エラーメッセージ
Access to fetch at 'https://graphql.api.smalruby.app/graphql' from origin 'https://smalruby.app'
has been blocked by CORS policy: Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
環境情報
- Origin:
https://smalruby.app(正しい) - リクエスト先:
https://graphql.api.smalruby.app/graphql(正しい) - 環境: 本番環境 (prod)
- デバイス: Chromebook
原因分析
1. WAF設定の問題
infra/mesh-v2/lib/mesh-v2-stack.ts:139-192 のWAF設定を確認したところ、以下の問題が見つかりました:
現在の設定:
const webAcl = new wafv2.CfnWebACL(this, 'MeshV2ApiWebAcl', {
defaultAction: { block: {} }, // デフォルトでブロック
scope: 'REGIONAL',
rules: [
{
name: 'AllowSpecificOrigins',
priority: 1,
action: { allow: {} },
statement: {
orStatement: {
statements: allowedOrigins.map(origin => ({
byteMatchStatement: {
fieldToMatch: {
singleHeader: { name: 'origin' } // Originヘッダーのみチェック
},
positionalConstraint: 'EXACTLY',
searchString: origin,
// ...
},
})),
},
},
// ...
},
],
});問題点:
- デフォルトアクションが
blockのため、許可ルールに一致しないリクエストはすべてブロックされる - preflight OPTIONSリクエストが正しく処理されていない可能性がある
- Originヘッダーは正しくチェックされているが、HTTPメソッド (OPTIONS) の特別な扱いがない
2. AppSyncのCORS動作
AppSync GraphQL APIは通常、以下のCORSヘッダーを自動的に返します:
Access-Control-Allow-Origin: *Access-Control-Allow-Headers: Content-Type, Authorization, x-api-key, ...Access-Control-Allow-Methods: POST, OPTIONS
しかし、カスタムドメイン + WAFの組み合わせでは、以下のいずれかの問題が発生する可能性があります:
- WAFがpreflight OPTIONSリクエストをブロックしている
- AppSyncがレスポンスにCORSヘッダーを追加していない
- WAFがレスポンスからCORSヘッダーを削除している
3. preflight OPTIONSリクエストの流れ
-
ブラウザが
OPTIONSリクエストを送信 (preflight)- Header:
Origin: https://smalruby.app - Header:
Access-Control-Request-Method: POST - Header:
Access-Control-Request-Headers: content-type, x-api-key
- Header:
-
WAFが Originヘッダーをチェック →
https://smalruby.appなので許可 -
AppSyncがOPTIONSリクエストを処理
-
問題: レスポンスに
Access-Control-Allow-Originヘッダーがない- WAFがヘッダーを削除している可能性
- AppSyncがヘッダーを返していない可能性
解決策の提案
解決策1: WAFルールにOPTIONSメソッドの特別なルールを追加 (推奨)
preflight OPTIONSリクエストを常に許可し、AppSyncがCORSヘッダーを返せるようにする。
変更内容:
rules: [
{
name: 'AllowPreflightOptions',
priority: 0, // 最優先
action: { allow: {} },
statement: {
andStatement: {
statements: [
{
byteMatchStatement: {
fieldToMatch: {
method: {} // HTTPメソッドをチェック
},
positionalConstraint: 'EXACTLY',
searchString: 'OPTIONS',
textTransformations: [{ priority: 0, type: 'UPPERCASE' }]
}
},
{
orStatement: {
statements: allowedOrigins.map(origin => ({
byteMatchStatement: {
fieldToMatch: { singleHeader: { name: 'origin' } },
positionalConstraint: 'EXACTLY',
searchString: origin,
textTransformations: [{ priority: 0, type: 'LOWERCASE' }]
}
}))
}
}
]
}
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'AllowPreflightOptions',
sampledRequestsEnabled: true
}
},
{
name: 'AllowSpecificOrigins',
priority: 1,
// ... (既存のルール)
}
]メリット:
- preflight OPTIONSリクエストが確実に許可される
- AppSyncがCORSヘッダーを返せるようになる
- 他のOriginからのOPTIONSリクエストはブロックされる (セキュリティ保持)
解決策2: CloudFrontディストリビューションを追加
AppSyncの前段にCloudFrontを配置し、CORSヘッダーを追加する。
メリット:
- より柔軟なCORS設定が可能
- キャッシュによるパフォーマンス向上
- Lambda@Edgeでヘッダーをカスタマイズできる
デメリット:
- 設定が複雑
- コスト増加
- WebSocketサポートに注意が必要
解決策3: AppSyncのデフォルトエンドポイントを使用
カスタムドメインを使わず、AppSyncのデフォルトエンドポイントを使用する。
メリット:
- AppSyncがCORSヘッダーを自動的に返す
- 設定不要
デメリット:
- カスタムドメインが使えない
- URLが長くなる
- ブランディングが弱くなる
推奨アクション
解決策1 (WAFルールの追加) を推奨します。理由:
- 最小限の変更で解決できる
- セキュリティを保ちながらCORSを有効化できる
- カスタムドメインを維持できる
- 追加コストなし
実装手順
infra/mesh-v2/lib/mesh-v2-stack.tsを更新してOPTIONSルールを追加npx cdk diff --context stage=prodで変更を確認npx cdk deploy --context stage=prodでデプロイ- Chromebookでテストして動作確認
- CloudWatch Logsで WAFメトリクスを確認
検証方法
デプロイ後、以下の方法で検証:
1. curlでpreflight OPTIONSリクエストをテスト
curl -X OPTIONS https://graphql.api.smalruby.app/graphql \
-H "Origin: https://smalruby.app" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type, x-api-key" \
-v期待される結果:
< HTTP/2 200
< access-control-allow-origin: https://smalruby.app
< access-control-allow-methods: POST, OPTIONS
< access-control-allow-headers: content-type, x-api-key
2. ブラウザのDevToolsで確認
- Chromebookで
https://smalruby.appを開く - DevTools → Network タブ
- meshV2に接続
- OPTIONSリクエストのレスポンスヘッダーを確認
3. WAFメトリクスの確認
aws cloudwatch get-metric-statistics \
--namespace AWS/WAFV2 \
--metric-name AllowedRequests \
--dimensions Name=Rule,Value=AllowPreflightOptions \
--start-time 2025-01-18T00:00:00Z \
--end-time 2025-01-18T23:59:59Z \
--period 3600 \
--statistics Sum参考情報
追加調査
もし解決策1で解決しない場合、以下を確認:
- Route53のAレコード設定が正しいか
- ACM証明書が正しく設定されているか
- AppSyncのカスタムドメイン設定が正しいか
- WAFログでリクエストがどのルールで処理されているか確認
Metadata
Metadata
Assignees
Labels
No labels