アプリケーションのセキュリティ対策のため、API Gatewayの手前にAWS WAFを導入しました。
導入にあたり、WAFとIAMロールとCloudWatch Logsの設定をTerraformで行ったので、備忘録として記事にまとめました。
以下がAWS WAF導入の利点となります。
- Webアプリケーションを一般的なWeb攻撃から保護可能。
- AWS管理のルールセットを利用して、効率的にセキュリティルールを適用可能。
- 特定のルールをオーバーライドすることで、柔軟なセキュリティ対策が可能。
ディレクトリ構成
Terraformのディレクトリ構成は、モジュール化された構造をとっており、それぞれの環境(dev, stg, prod)ごとに設定を分けています。
modules
ディレクトリに、プロジェクトで使用されるTerraformのモジュール(waf, iam_roles, cloudwatch_logsなど)を格納しています。
-- terraform-project/
-- environments/
-- dev/
-- backend.tf
-- main.tf
-- stg/
-- backend.tf
-- main.tf
-- prod/
-- backend.tf
-- main.tf
-- modules/
-- waf/
-- main.tf
-- variables.tf
-- outputs.tf
-- provider.tf
-- README.md
-- iam_roles/
-- main.tf
-- variables.tf
-- outputs.tf
-- provider.tf
-- README.md
-- cloudwatch_logs/
-- main.tf
-- variables.tf
-- outputs.tf
-- provider.tf
-- README.md
-- ...other…
-- docs/
-- architecrture.drowio
-- architecrture.png
実装
WAF
以下のTerraformコードで、API GatewayにWAFを適用し、様々なセキュリティルールを設定しています。
resource "aws_wafv2_web_acl" "api_gateway_waf" {
name = "api-gateway-waf"
description = "Managed rule WAF"
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "AWS-AWSManagedRulesCommonRuleSet"
priority = 0
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
rule_action_override {
action_to_use {
allow {}
}
name = "SizeRestrictions_BODY"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesCommonRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesAmazonIpReputationList"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesAmazonIpReputationList"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesAmazonIpReputationList"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesSQLiRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesSQLiRuleSet"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "apiGatewayWafMetrics"
sampled_requests_enabled = true
}
}
resource "aws_wafv2_web_acl_logging_configuration" "waf_logging_config" {
log_destination_configs = [var.log_group_arn]
resource_arn = aws_wafv2_web_acl.api_gateway_waf.arn
depends_on = [aws_wafv2_web_acl.api_gateway_waf]
redacted_fields {
single_header {
name = "user-agent"
}
}
}
// attach API Gateway dev
data "aws_ssm_parameter" "api_gateway_api_dev_arn" {
name = "/api/gateway/arn/api/dev"
}
resource "aws_wafv2_web_acl_association" "api_dev" {
resource_arn = data.aws_ssm_parameter.api_gateway_api_dev_arn.value
web_acl_arn = aws_wafv2_web_acl.api_gateway_waf.arn
depends_on = [aws_wafv2_web_acl.api_gateway_waf]
}
// attach API Gateway stg
data "aws_ssm_parameter" "api_gateway_api_stg_arn" {
name = "/api/gateway/arn/api/stg"
}
resource "aws_wafv2_web_acl_association" "api_stg" {
resource_arn = data.aws_ssm_parameter.api_gateway_api_stg_arn.value
web_acl_arn = aws_wafv2_web_acl.api_gateway_waf.arn
}
// attach API Gateway prod
data "aws_ssm_parameter" "api_gateway_api_prod_arn" {
name = "/api/gateway/arn/api/prod"
}
resource "aws_wafv2_web_acl_association" "api_prod" {
resource_arn = data.aws_ssm_parameter.api_gateway_api_prod_arn.value
web_acl_arn = aws_wafv2_web_acl.api_gateway_waf.arn
}
variable "log_group_arn" {
description = "The ARN of the CloudWatch Logs group"
type = string
}
-
aws_wafv2_web_acl
リソースで、Web ACLを作成します。ここでは、api-gateway-waf
という名前で、REGIONALスコープのWeb ACLを作成しています。 -
default_action
ブロックでは、特定のルールにマッチしないリクエストに対するデフォルトのアクションを設定しています。ここでは、許可(allow)アクションを設定しています。設定されたルールにリクエストがマッチする場合、そのルールで定義されたアクション(例:ブロック)が実行されます。 -
rule
ブロックでは、各ルールを設定しています。このコードでは、AWSが管理する3つのルールセットを使用しています。-
AWSManagedRulesCommonRuleSet
:一般的な脆弱性やエクスプロイトを防ぐためのルールセット(XSS、サイズ制限など)。 -
AWSManagedRulesAmazonIpReputationList
:Amazonが管理するIPリピュテーションリスト(スパム送信者やマルウェア配布者のIPアドレス)を使用して、不正なトラフィックをフィルタリングするルールセット。 -
AWSManagedRulesSQLiRuleSet
:SQLインジェクション攻撃を検出・防ぐためのルールセット。
-
-
visibility_config
ブロックでは、CloudWatchにメトリクスを送信するための設定をしています。 -
log_group_arn
では、CloudWatch LogsグループのARNを変数として定義しています。このARNは、WAFのログをCloudWatch Logsに送信する際に使用されます。
AWS管理ルールセットで一部のルールを許可
AWS管理のルールセットを使うと楽ではあるのですが、一部のルールを許可したい(ブロックしたくない)ときもあります。その場合はrule_action_override
で設定を上書きします。以下では、AWSManagedRulesCommonRuleSet
からSizeRestrictions_BODY
ルール(APIリクエストのサイズ制限に関するルール)に当てはまるリクエストについては許可する設定になっています。
rule_action_override {
action_to_use {
allow {}
}
name = "SizeRestrictions_BODY"
}
マネジメントコンソールでいうと、各ルールのRule action設定の更新と同じです。
IAMロール
以下のTerraformコードで、WAFのログをCloudWatch Logsに送信するためのIAMロールを作成しています。
resource "aws_iam_role" "waf_logging" {
name = "waf_logging"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "waf.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy" "waf_logging" {
name = "waf_logging"
role = aws_iam_role.waf_logging.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
-
aws_iam_role
リソースで、waf_logging
という名前のIAMロールを作成しています。 -
aws_iam_role_policy
リソースで、このロールにポリシーをアタッチしています。このポリシーにより、WAFはCloudWatch Logsにログを送信できます。
CloudWatch Logs
以下のTerraformコードで、WAFのログを保存するためのCloudWatch Logsグループを作成しています。
resource "aws_cloudwatch_log_group" "waf_logs" {
name = "aws-waf-logs-api-gateway"
}
output "log_group_arn" {
description = "The ARN of the CloudWatch Logs group"
value = aws_cloudwatch_log_group.waf_logs.arn
}
aws_cloudwatch_log_group
リソースで、aws-waf-logs-api-gateway
という名前のロググループを作成しています。
コードの適用
各modulesの作成が完了したら、./environments/{environment}/main.tf
でmodulesを読み込んで、terraform apply
で適用します。
module "waf" {
log_group_arn = module.cloudwatch_logs.log_group_arn
source = "../../modules/waf"
}
module "cloudwatch_logs" {
source = "../../modules/cloudwatch_logs"
}
module "iam_roles" {
source = "../../modules/iam_roles"
}