Skip to content

Commit defb4f3

Browse files
committed
fix: update existing default stage for throttling and improve CORS headers
1 parent 582cf83 commit defb4f3

1 file changed

Lines changed: 40 additions & 116 deletions

File tree

infra/lib/api-stack.ts

Lines changed: 40 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,19 @@ export class ApiStack extends cdk.Stack {
5050
const vapidSecret = secretsmanager.Secret.fromSecretNameV2(this, 'VapidSecret', vapidSecretName)
5151

5252
// 3) SNS Topic for error notifications(オプション: メール通知が必要な場合のみ)
53-
const errorTopic = props.alertEmail ? new sns.Topic(this, 'ErrorNotificationTopic', {
54-
displayName: 'Handwash API Error Notifications',
55-
}) : undefined
56-
53+
const errorTopic = props.alertEmail
54+
? new sns.Topic(this, 'ErrorNotificationTopic', {
55+
displayName: 'Handwash API Error Notifications',
56+
})
57+
: undefined
58+
5759
// メールアドレスが指定されている場合はサブスクリプションを追加
5860
if (props.alertEmail && errorTopic) {
59-
errorTopic.addSubscription(
60-
new subscriptions.EmailSubscription(props.alertEmail)
61-
)
61+
errorTopic.addSubscription(new subscriptions.EmailSubscription(props.alertEmail))
6262
}
6363

6464
// 3) Lambda functions(コスト最適化: 最小限の設定)
65-
const lambdaEnv = {
65+
const lambdaEnv = {
6666
TABLE_NAME: table.tableName,
6767
...(errorTopic && { ERROR_TOPIC_ARN: errorTopic.topicArn }),
6868
}
@@ -206,12 +206,21 @@ export class ApiStack extends cdk.Stack {
206206
// SNS Topicへのパブリッシュ権限(エラー通知がある場合のみ)
207207
if (errorTopic) {
208208
const lambdaFunctions = [
209-
meFn, createFamilyFn, listFamiliesFn, joinFamilyFn,
210-
createHandwashEventFn, listHandwashEventsFn, pushSubscribeFn,
211-
sendReminderFn, listFamilyMembersFn, sendPushToUserFn,
212-
leaveFamilyFn, deleteFamilyFn, updateProfileFn,
209+
meFn,
210+
createFamilyFn,
211+
listFamiliesFn,
212+
joinFamilyFn,
213+
createHandwashEventFn,
214+
listHandwashEventsFn,
215+
pushSubscribeFn,
216+
sendReminderFn,
217+
listFamilyMembersFn,
218+
sendPushToUserFn,
219+
leaveFamilyFn,
220+
deleteFamilyFn,
221+
updateProfileFn,
213222
]
214-
lambdaFunctions.forEach(fn => errorTopic.grantPublish(fn))
223+
lambdaFunctions.forEach((fn) => errorTopic.grantPublish(fn))
215224
}
216225

217226
// 5) EventBridge Scheduler for daily reminder (20:00 JST = 11:00 UTC)
@@ -223,114 +232,30 @@ export class ApiStack extends cdk.Stack {
223232
targets: [new targets.LambdaFunction(sendReminderFn)],
224233
})
225234

226-
// 6) WAF v2 WebACL
227-
// 注意: HTTP API (apigatewayv2) にはWAFを直接アタッチできません
228-
// WAFを使う場合は REST API (apigateway) に切り替える必要があります
229-
// または CloudFront + WAF (CLOUDFRONT scope) を前段に置く方法もあります
230-
// 一旦、WAF関連のコードはコメントアウトしてデプロイを通します
231-
//
232-
// const webAcl = new wafv2.CfnWebACL(this, 'ApiWebACL', {
233-
// defaultAction: { allow: {} },
234-
// scope: 'REGIONAL', // API Gateway用
235-
// visibilityConfig: {
236-
// sampledRequestsEnabled: true,
237-
// cloudWatchMetricsEnabled: true,
238-
// metricName: 'ApiWebACL',
239-
// },
240-
// rules: [
241-
// // AWS Managed Rules - Core Rule Set
242-
// {
243-
// name: 'AWSManagedRulesCommonRuleSet',
244-
// priority: 1,
245-
// overrideAction: { none: {} },
246-
// statement: {
247-
// managedRuleGroupStatement: {
248-
// vendorName: 'AWS',
249-
// name: 'AWSManagedRulesCommonRuleSet',
250-
// },
251-
// },
252-
// visibilityConfig: {
253-
// sampledRequestsEnabled: true,
254-
// cloudWatchMetricsEnabled: true,
255-
// metricName: 'CommonRuleSet',
256-
// },
257-
// },
258-
// // AWS Managed Rules - Known Bad Inputs
259-
// {
260-
// name: 'AWSManagedRulesKnownBadInputsRuleSet',
261-
// priority: 2,
262-
// overrideAction: { none: {} },
263-
// statement: {
264-
// managedRuleGroupStatement: {
265-
// vendorName: 'AWS',
266-
// name: 'AWSManagedRulesKnownBadInputsRuleSet',
267-
// },
268-
// },
269-
// visibilityConfig: {
270-
// sampledRequestsEnabled: true,
271-
// cloudWatchMetricsEnabled: true,
272-
// metricName: 'KnownBadInputs',
273-
// },
274-
// },
275-
// // レート制限ルール(1分間に100リクエスト/IP)
276-
// {
277-
// name: 'RateLimitRule',
278-
// priority: 0, // 最優先
279-
// action: {
280-
// block: {},
281-
// },
282-
// statement: {
283-
// rateBasedStatement: {
284-
// limit: 100, // 1分間あたり100リクエスト
285-
// aggregateKeyType: 'IP',
286-
// },
287-
// },
288-
// visibilityConfig: {
289-
// sampledRequestsEnabled: true,
290-
// cloudWatchMetricsEnabled: true,
291-
// metricName: 'RateLimitRule',
292-
// },
293-
// },
294-
// ],
295-
// })
296-
297235
// 6) HTTP API
298236
const httpApi = new apigwv2.HttpApi(this, 'HttpApi', {
299237
corsPreflight: {
300-
allowOrigins: [
301-
'http://localhost:5173',
302-
`https://${props.webDistributionDomain}`,
238+
allowOrigins: ['http://localhost:5173', `https://${props.webDistributionDomain}`],
239+
allowMethods: [
240+
apigwv2.CorsHttpMethod.GET,
241+
apigwv2.CorsHttpMethod.POST,
242+
apigwv2.CorsHttpMethod.PUT,
243+
apigwv2.CorsHttpMethod.OPTIONS,
303244
],
304-
allowMethods: [apigwv2.CorsHttpMethod.GET, apigwv2.CorsHttpMethod.POST, apigwv2.CorsHttpMethod.PUT, apigwv2.CorsHttpMethod.OPTIONS],
305-
allowHeaders: ['authorization', 'content-type'],
306-
},
307-
})
308-
309-
// HTTP API Stage(極端に低いスロットリング設定)
310-
// 既存の$defaultステージを更新するため、CfnStageを使用
311-
const httpStage = new apigwv2.CfnStage(this, 'HttpStage', {
312-
apiId: httpApi.apiId,
313-
stageName: '$default',
314-
// 極端に低いスロットリング: 1秒あたり2リクエスト、バースト5
315-
defaultRouteSettings: {
316-
throttlingRateLimit: 2, // 1秒あたり2リクエスト(過剰リクエストを完全にブロック)
317-
throttlingBurstLimit: 5, // バースト時5リクエスト
245+
// 大文字小文字揺れに備えて両方許可(ブラウザ/ライブラリ差異対策)
246+
allowHeaders: ['authorization', 'Authorization', 'content-type', 'Content-Type'],
318247
},
319248
})
320249

321-
// WAFをAPI Gatewayにアタッチ
322-
// 注意: HTTP API (apigatewayv2) にはWAFを直接アタッチできません
323-
// WAFを使う場合は REST API (apigateway) に切り替える必要があります
324-
// または CloudFront + WAF (CLOUDFRONT scope) を前段に置く方法もあります
325-
// 一旦、WAF Associationはコメントアウトしてデプロイを通します
326-
//
327-
// const httpApiId = httpApi.apiId
328-
// const httpApiStageArn = `arn:aws:apigateway:${this.region}::/apis/${httpApiId}/stages/$default`
329-
//
330-
// new wafv2.CfnWebACLAssociation(this, 'ApiWebACLAssociation', {
331-
// resourceArn: httpApiStageArn,
332-
// webAclArn: webAcl.attrArn,
333-
// })
250+
// ★重要:$default stage を「新規作成」しない(Stage already exists 対策)
251+
// 既存の defaultStage を更新してスロットリングを設定する
252+
const cfnDefaultStage = httpApi.defaultStage?.node.defaultChild as apigwv2.CfnStage
253+
if (cfnDefaultStage) {
254+
cfnDefaultStage.defaultRouteSettings = {
255+
throttlingRateLimit: 2, // 1秒あたり2リクエスト
256+
throttlingBurstLimit: 5, // バースト時5リクエスト
257+
}
258+
}
334259

335260
// 7) JWT Authorizer
336261
const jwtAuthorizer = new authorizers.HttpJwtAuthorizer(
@@ -425,7 +350,6 @@ export class ApiStack extends cdk.Stack {
425350
})
426351

427352
// 9) CloudWatch Alarms(最小限に削減: エラー通知がある場合のみ)
428-
// 利用されない予定なので、アラームは最小限に(または完全に無効化)
429353
if (errorTopic) {
430354
// 重大なエラーのみ監視(API Gatewayの5xxエラーのみ)
431355
const apiServerErrorAlarm = new cloudwatch.Alarm(this, 'ApiServerErrorRateAlarm', {
@@ -446,7 +370,7 @@ export class ApiStack extends cdk.Stack {
446370
new cdk.CfnOutput(this, 'ApiUrl', { value: httpApi.apiEndpoint })
447371
new cdk.CfnOutput(this, 'TableName', { value: table.tableName })
448372
if (errorTopic) {
449-
new cdk.CfnOutput(this, 'ErrorTopicArn', {
373+
new cdk.CfnOutput(this, 'ErrorTopicArn', {
450374
value: errorTopic.topicArn,
451375
description: 'SNS Topic ARN for error notifications',
452376
})

0 commit comments

Comments
 (0)