レートリミットとは
レートリミットとは、リソースの過剰使用やサービス拒否攻撃を防ぐために使用されます。
サーバーへのリクエストの頻度を制御し、特定の時間内にクライアントやIPアドレスからのリクエスト数を制限するために使用されます。
今回はIPアドレスベースでの制限になります。
背景
最近ではポケモン徹底攻略さんのサイトが大量リクエストで落とされるなど、DDos攻撃対策も必要ですね。
基本的に、サーバーサイドを使用するときは、Nestjsを使用していて、Nestjsでの実装方法もあります。
ですがサーバーサイドで実装すると、
- オリジンにリクエストは届くので、サーバーサイドがスケールしてマシーンリソースを食ってしまう(リソース割きたくない)
- 実装するには、Redisなどを用意して、IPアドレスを保存する仕組みが必要
以上を考えると、サーバーサイドにリクエストが届くよりも前段のほうでレートリミットかけたほうがいいので
今回はALBにアタッチします。場合によっては、cloudfrontにもアタッチできると思います。
参考
クラスメソッドさんで紹介されている、AWS WAF を使用した方法を、AWS CDKのコードに落としてみました。
環境
"typescript": "~4.9.4"
"aws-cdk": "2.60.0",
コード
rate-limit-construct.ts
rate-limit-construct.ts
import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { CfnWebACL, CfnWebACLAssociation } from 'aws-cdk-lib/aws-wafv2';
import { Construct } from 'constructs';
export interface RateLimitConstructProps {
limit?: number;
immunityTime?: number;
searchString?: string;
}
export class RateLimitConstruct extends Construct {
public acl: CfnWebACL;
constructor(scope: Construct, id: string, props?: RateLimitConstructProps) {
super(scope, id);
const aggregateKeyType = 'IP';
const nonPriority = 0;
const minLimit = 100; // See https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/limits.html
const minImmunityTime = 300; // See https://docs.aws.amazon.com/ja_jp/waf/latest/APIReference/API_ImmunityTimeProperty.html
const limit = props?.limit ?? minLimit;
const immunityTime = props?.immunityTime ?? minImmunityTime;
const searchString = props?.searchString ?? '/';
const RuleAction = { Allow: { allow: {} }, Block: { block: {} } };
const visibilityConfig = {
metricName: `target:${searchString}`,
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
};
this.acl = new CfnWebACL(this, CfnWebACL.name, {
name: 'PcmStgRateLimit',
defaultAction: RuleAction.Allow,
scope: 'REGIONAL',
visibilityConfig,
challengeConfig: { immunityTimeProperty: { immunityTime } },
description: `Limit ${aggregateKeyType} Base.Up to ${limit} Requests Per ${immunityTime} seconds`,
rules: [
{
name: `${aggregateKeyType}BaseLimit${limit}Per${immunityTime}SecondsRule`,
priority: nonPriority,
action: RuleAction.Block,
visibilityConfig,
statement: {
rateBasedStatement: {
limit,
aggregateKeyType,
scopeDownStatement: {
byteMatchStatement: {
fieldToMatch: { uriPath: {} },
positionalConstraint: 'STARTS_WITH',
searchString,
textTransformations: [
{
type: 'NONE',
priority: nonPriority,
},
],
},
},
},
},
},
],
});
}
attachToALB(alb: ApplicationLoadBalancer, waf: CfnWebACL) {
new CfnWebACLAssociation(this, `AttachToALB`, {
resourceArn: alb.loadBalancerArn,
webAclArn: waf.attrArn,
});
}
}
上記constructを使った例
sample-stack.ts
sample-stack.ts
import { RateLimitConstruct } from './rate-limit-construct';
import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
const loadBalancer = new ApplicationLoadBalancer(this, ApplicationLoadBalancer.name, options);
const rateLimit = new RateLimitConstruct(this, RateLimitConstruct.name);
rateLimit.attachToALB(loadBalancer, rateLimit.acl);
- ApplicationLoadBalancerを初期化する
options
は適宜引数に渡してください。