[Terraform]API Gateway+WAFで短期間の同一IPからのアクセスをブロックする
API Gateway+WAFで、短時間の同一IPからのアクセスをブロックするレートベースのルールを試してみました。Terraformだと数個リソースを追加するだけでよく、手軽にできます。この例では、スロットリングとして動作します。
目次
はじめに
Blackbelt OnlineのWAF見てたら、「5分間に同一IPから一定のリクエストがあったらブロックする」というルールがあったので、API Gatewayに統合してみた。WAF扱うの初めてだったが、Terraformでコード生成させたらそこまで難しくなかったのでやってみた。
GPT Log
WAFの部分とAPI Gatewayの統合
https://chat.openai.com/share/69504a1d-ac1e-4a9a-9115-6dd80db1941c
コード
ディレクトリ構成
* main.tf
* lambda_functgion.py
lambda_function.py
def lambda_handler(event, context):
return {
"statusCode": 200,
"body": "Hello World form WAF Applied API!"
}
main.tf
terraform {
backend "local" {
path = ".cache/terraform.tfstate"
}
}
provider "aws" {
region = "ap-northeast-1"
profile = "develop"
}
# Deploy Lambda
data "aws_caller_identity" "current" {}
data "archive_file" "lambda_archive" {
type = "zip"
source_file = "lambda_function.py"
output_path = ".cache/lambda_function.zip"
}
resource "aws_lambda_function" "discord_lambda" {
function_name = "WAFAPIGatewayTestLambda"
handler = "lambda_function.lambda_handler"
runtime = "python3.12"
role = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/LambdaBasicExecutionRole"
filename = data.archive_file.lambda_archive.output_path
source_code_hash = data.archive_file.lambda_archive.output_base64sha256
}
# API Gateway
resource "aws_api_gateway_rest_api" "example_api" {
name = "WAFExampleAPI"
description = "Example API Gateway with WAF"
}
resource "aws_api_gateway_resource" "example_resource" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
parent_id = aws_api_gateway_rest_api.example_api.root_resource_id
path_part = "examplepath"
}
resource "aws_api_gateway_method" "example_method" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
resource_id = aws_api_gateway_resource.example_resource.id
http_method = "POST"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "lambda_integration" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
resource_id = aws_api_gateway_resource.example_resource.id
http_method = aws_api_gateway_method.example_method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.discord_lambda.invoke_arn
}
resource "aws_api_gateway_deployment" "example_deployment" {
depends_on = [
aws_api_gateway_integration.lambda_integration
]
rest_api_id = aws_api_gateway_rest_api.example_api.id
}
resource "aws_api_gateway_stage" "dev_stage" {
stage_name = "dev"
rest_api_id = aws_api_gateway_rest_api.example_api.id
deployment_id = aws_api_gateway_deployment.example_deployment.id
}
resource "aws_lambda_permission" "api_gateway_permission" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.discord_lambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.example_api.execution_arn}/*/*/${aws_api_gateway_resource.example_resource.path_part}"
}
# WAF
resource "aws_wafv2_web_acl" "example_waf" {
name = "example-web-acl"
scope = "REGIONAL" # REGIONAL for API Gateway, CLOUDFRONT for CloudFront
description = "An example Web ACL"
default_action {
allow {} # デフォルトではすべてのリクエストを許可
}
rule {
name = "RateLimit5Min100Req"
priority = 1
action {
block {} # 条件に合致したリクエストをブロック
}
statement {
rate_based_statement {
limit = 100
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimit5Min100Req"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "exampleWebACL"
sampled_requests_enabled = true
}
}
resource "aws_wafv2_web_acl_association" "example" {
resource_arn = aws_api_gateway_stage.dev_stage.arn
web_acl_arn = aws_wafv2_web_acl.example_waf.arn
}
WAFの部分だけ追加した。あとは普通のAPI Gateway+Lambdaと同じ。
テストする
ローカルから
テストコードは以下の通り。こんな感じでローカルからリクエストを送る。
import requests
import time
def main(num_send=1):
for i in range(num_send):
result = requests.post(
"https://6u6rolssf5.execute-api.ap-northeast-1.amazonaws.com/dev/examplepath")
print(i, result.status_code, result.text)
time.sleep(0.1)
if __name__ == "__main__":
main()
結果はこの通りに、結果が返ってくる。WAF自体は認証をしていないので、特にブロックされなければ結果が常に返ってくる。
0 200 Hello World form WAF Applied API!
Colabから大量アクセスをかける
ColabのIPをBANさせるように大量アクセスをかけてみる。ルールの最小値の5分あたり100でかける。
この通り、最初の100アクセスぐらいは普通にリクエストが通る。ただ、100アクセスしたら厳密にブロックされるわけではなくWAFからブロックされるまで少し遅延があるようだ。
あまり調子に乗ってるとブロックされる(403)になる。
この間に、ローカルからアクセスすると正常に結果が返ってくる(ローカルのIPはブロックされていないため)
ちなみに一定期間してからColabからアクセスすると、また200になるため、この設定だと永続的にブロックされているわけではないようだ。あくまでIPに対応するスロットリングとしての機能になっている。
WAFの画面
ちなみにWAFの画面はこのようになっていた。WAF→WebACLから。作ったACLが出てこない場合は、リージョンを変える。右上ではなく、WebACLのリスト内にリージョンの切り替えがある。
作ったルールを見ると、Blockが発生していた。これはColabからアクセスしたもの。
このルールはなんと2WCUsでいいそうだ。1500WCUsまでWCU部分に追加料金はかからない。
所感
- WAFの初期費用(月額5ドル)と、ルール単位(月額1ドル)がかかってしまうが、時間按分されるのでテストで作ってすぐ
terraform destroy
してしまえば、ほとんどコストがかからないと思われる。特に大量アクセスの関係で、WAF以上のコストが発生しているなら割と効果ありそう - 最低6ドルかかるので個人で使うのは微妙な気がするが、簡易DDoS攻撃対策にはなるし、あとマネージドルールもいくつかあるし、使いやすいサービスな気がする。WAFはどっちかというと、DDoSよりSQLインジェクション対策な気がするが、少なくともShield Advancedなんかよりかは全然安い
- デフォルトの無料のAWS Shieldはあるが、カバーしてるのが「Elastic Load Balancing (ELB)、Application Load Balancer、Amazon CloudFront、Amazon Route 53など」と書いてあり、API Gatewayはカバーしていないのかもしれない。
公式のKnowledge CenterだとWAFを使う例を紹介している。 - API Gatewayの大量アクセス対策は、デフォルトでついてるスロットリングで一定の効果はあるが、スロットリングしかできないので、マネージドのルールと組み合わせるならWAFが効果があるだろう。
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー