はじめに
みなさん、こんにちは。今回はTerraformの入門ということでCloudFrontおよびAPI Gatewayに割り当てるAWS WAFのサンプルコードを書いてみましたのでこちらを紹介していきたいと思います。
なお、サンプルコードを書いた際のTerraformおよびAWSプロバイダーのバージョンは次のとおりです。最新バージョンでは定義方法が異なっている可能性があるため、実際にコードを書く際は最新の「Terraformドキュメント」と「AWSプロバイダードキュメント」を確認しながら開発を進めていただければと思います。
# Requirements
terraform {
required_version = "1.3.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.46.0"
}
}
}
サンプルコードを書いてみた
今回は次のようなサンプルコードを書いてみました。なお、変数定義部分などの一部省略している点、ならびにステップごとの細かい説明などは省いていますのでご承知おきください。詳細については「AWSプロバイダードキュメント」をご参照ください。
- 例1. 汎用的なCloudFront向け定義
- 例2. 汎用的なAPI Gateway向け定義
- 例3. IPホワイトリストを用いたアクセス制御
- 例4. カスタムヘッダーを用いたアクセス制御
- 例5. CloudWatch Logsを活用したロギング設定
- 例6. S3バケットを活用したロギング設定
ちなみに、すべてのサンプルコードに共通してプロバイダー定義は次のようにしています。
# デフォルトのプロバイダー設定
provider "aws" {
region = var.aws_region
default_tags {
tags = {
terraform = "true"
}
}
}
# CloudFront向けのプロバイダー設定
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
default_tags {
tags = {
terraform = "true"
}
}
}
例1. 汎用的なCloudFront向け定義
次のAWSマネージドルールを組み合わせてそれっぽい汎用的なCloudFront向けのWAF設定を記載してみました。Linux OS以降はアプリケーションの実装により適宜変えるイメージです。
- Amazon IP 評価リストマネージドルールグループ
- 匿名 IP リストマネージドルールグループ
- コアルールセット (CRS) マネージドルールグループ
- 既知の不正な入力マネージドルールグループ
- Linux オペレーティングシステムマネージドルールグループ
- POSIX オペレーティングシステムマネージドルールグループ
- SQL データベースマネージドルールグループ
- CloudFrontリソースは
us-east-1
リージョンに配置されるため、WAFリソースも同様にus-east-1
へ作成する必要があるのでご留意ください。 - 優先度についてはAWSマネジメントコンソールでポチポチした際に付与される優先度と同じ順序にしています。
- CloudFrontへのWAF割り当てはaws_wafv2_web_acl_associationではなく、aws_cloudfront_distributionにて実施するため今回は省略しています。
- 8KB以上のリクエストボディを扱えるように
SizeRestrictions_BODY
ルールを、他のクラウドプロバイダーからのアクセスを誤ってブロックしないようにHostingProviderIPList
ルールを除外しています。
# WAFのWeb ACL設定
resource "aws_wafv2_web_acl" "cloudfront" {
provider = aws.us_east_1
name = var.aws_gloal_wafv2_name
scope = "CLOUDFRONT"
default_action {
allow {}
}
rule {
name = "AWS-AWSManagedRulesAmazonIpReputationList"
priority = 0
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-AWSManagedRulesAnonymousIpList"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesAnonymousIpList"
vendor_name = "AWS"
excluded_rule {
name = "HostingProviderIPList"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesAnonymousIpList"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesCommonRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
excluded_rule {
name = "SizeRestrictions_BODY"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesCommonRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
priority = 3
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesKnownBadInputsRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesLinuxRuleSet"
priority = 4
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesLinuxRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesLinuxRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesUnixRuleSet"
priority = 5
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesUnixRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesUnixRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesSQLiRuleSet"
priority = 6
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 = var.aws_gloal_wafv2_name
sampled_requests_enabled = true
}
}
例2. 汎用的なAPI Gateway向け定義
「例1. 汎用的なCloudFront向け定義」と同等内容のそれっぽい汎用的なAPI Gateway向けのWAF定義です。CloudFront向けとの差異はスコープがREGIONAL
になっている点とデフォルトのプロバイダー設定を利用している点の2点です。
# WAFのWeb ACL設定
resource "aws_wafv2_web_acl" "apigw" {
name = var.aws_apigw_wafv2_name
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "AWS-AWSManagedRulesAmazonIpReputationList"
priority = 0
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-AWSManagedRulesAnonymousIpList"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesAnonymousIpList"
vendor_name = "AWS"
excluded_rule {
name = "HostingProviderIPList"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesAnonymousIpList"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesCommonRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
excluded_rule {
name = "SizeRestrictions_BODY"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesCommonRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
priority = 3
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesKnownBadInputsRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesLinuxRuleSet"
priority = 4
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesLinuxRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesLinuxRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesUnixRuleSet"
priority = 5
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesUnixRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesUnixRuleSet"
sampled_requests_enabled = true
}
}
rule {
name = "AWS-AWSManagedRulesSQLiRuleSet"
priority = 6
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 = var.aws_apigw_wafv2_name
sampled_requests_enabled = true
}
}
# API GatewayへのWAFの割り当て
resource "aws_wafv2_web_acl_association" "apigw" {
resource_arn = var.aws_apigw_arn # or aws_api_gateway_stage.*.arn
web_acl_arn = aws_wafv2_web_acl.wafv2_web_acl_regional.arn
}
例3. IPホワイトリストを用いたアクセス制御
許可したIPアドレスからのみアクセスを許可するホワイトリストベースのアクセス制御を用いた例です。デフォルトアクションはブロックにして、ホワイトリストに合致した場合のみ許可するようにしています。
# WAFのWeb ACL設定
resource "aws_wafv2_web_acl" "cloudfront" {
provider = aws.us_east_1
name = var.aws_cloudfront_wafv2_name
scope = "CLOUDFRONT"
default_action {
block {}
}
rule {
name = "AllowedIPAddressList"
priority = 0
action {
allow {}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.cloudfront.arn
}
}
visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "AllowedIPAddressList"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = var.aws_cloudfront_wafv2_name
sampled_requests_enabled = true
}
}
# WAFのIP sets設定
resource "aws_wafv2_ip_set" "cloudfront" {
provider = aws.us_east_1
name = var.aws_wafv2_ipset_name
description = var.aws_wafv2_ipset_description
addresses = var.aws_wafv2_ipset_addresses
scope = "CLOUDFRONT"
ip_address_version = "IPV4"
}
例4. カスタムヘッダーを用いたアクセス制御
CloudFrontなどで付与した特定のカスタムヘッダーが含まれていた場合にのみアクセスを許可するといったケースの例です。なお、カスタムヘッダー名と設定値はAWS Systems Manager Parameter Storeへ事前に格納しておき、Terraform実行時にdataブロックを用いて値を取得してくるようにしています。
# WAFのWeb ACL設定
resource "aws_wafv2_web_acl" "apigw" {
name = var.aws_apigw_wafv2_name
scope = "REGIONAL"
default_action {
block {}
}
rule {
name = "AllowedCustomHeader"
priority = 0
action {
allow {}
}
statement {
byte_match_statement {
field_to_match {
single_header {
name = data.aws_ssm_parameter.custom_header_name.value
}
}
positional_constraint = "CONTAINS"
search_string = data.aws_ssm_parameter.custom_header_value.value
text_transformation {
priority = 0
type = "NONE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = var.aws_apigw_wafv2_name
sampled_requests_enabled = true
}
}
# SSMパラメータストアからのデータ取得
data "aws_ssm_parameter" "custom_header_name" {
name = var.aws_ssmparam_custom_header_name
}
data "aws_ssm_parameter" "custom_header_value" {
name = var.aws_ssmparam_custom_header_value
}
例5. CloudWatch Logsを活用したロギング設定
WAFログをCloudWatch Logsへ出力する際の例です。WAFログのロググループ名は"aws-waf-logs-*"とする必要があること、CloudFront向けWAFについてはus-east-1
にすることを忘れがちなのでご注意ください。
# CloudFront向けWAFログ設定
resource "aws_wafv2_web_acl_logging_configuration" "cloudfront" {
provider = aws.us_east_1
log_destination_configs = [aws_cloudwatch_log_group.cloudfront_waf.arn]
resource_arn = aws_wafv2_web_acl.cloudfront.arn
}
# CloudFront向けWAFログ用のロググループ設定
resource "aws_cloudwatch_log_group" "cloudfront_waf" {
provider = aws.us_east_1
name = "aws-waf-logs-${var.aws_cloudfront_wafv2_name}"
}
# API Gateway向けWAFログ設定
resource "aws_wafv2_web_acl_logging_configuration" "apigw" {
log_destination_configs = [aws_cloudwatch_log_group.apigw_waf.arn]
resource_arn = aws_wafv2_web_acl.apigw.arn
}
# API Gateway向けWAFログ用のロググループ設定
resource "aws_cloudwatch_log_group" "apigw_waf" {
name = "aws-waf-logs-${var.aws_apigw_wafv2_name}"
}
例6. S3バケットを活用したロギング設定
WAFログをS3バケットへ出力する際の例です。WAFログを格納するS3バケット名は"aws-waf-logs-*"とする必要があることを忘れがちなのでご注意ください。また、CloudFront向けWAFのログ用S3バケットは、他のリソースとは異なりデフォルト側のプロバイダー設定を利用するのでこちらもご注意ください。
# CloudFront向けWAFログ設定
resource "aws_wafv2_web_acl_logging_configuration" "cloudfront" {
provider = aws.us_east_1
log_destination_configs = [aws_s3_bucket.cloudfront_waf_log.arn]
resource_arn = aws_wafv2_web_acl.cloudfront.arn
}
# CloudFront向けWAFログ用のログバケット設定(詳細は割愛)
resource "aws_s3_bucket" "cloudfront_waf_log" {
bucket = "aws-waf-logs-${var.aws_cloudfront_wafv2_name}"
# 以下省略
}
# API Gateway向けWAFログ設定
resource "aws_wafv2_web_acl_logging_configuration" "apigw" {
log_destination_configs = [aws_s3_bucket.apigw_waf_log.arn]
resource_arn = aws_wafv2_web_acl.apigw.arn
}
# API Gateway向けWAFログ用のログバケット設定(詳細は割愛)
resource "aws_s3_bucket" "apigw_waf_log" {
bucket = "aws-waf-logs-${var.aws_apigw_wafv2_name}"
# 以下省略
}
終わりに
今回はTerraformの入門ということで、CloudFrontおよびAPI Gatewayに割り当てるAWS WAFのサンプルコードをいくつかご紹介してきましたがいかがだったでしょうか。こんな記事でも誰かの役に立っていただけるのであれば幸いです。
なお、今回ご紹介したコードはあくまでサンプルであり、動作を保証するものではございません。そのまま使用したことによって発生したトラブルなどについては一切責任を負うことはできませんのでご注意ください。
- AWS は、米国その他の諸国における Amazon.com, Inc. またはその関連会社の商標です。
- Terraform は、HashiCorp, Inc. の米国およびその他の国における商標または登録商標です。
- その他、記載されている会社名および商品・製品・サービス名は、各社の商標または登録商標です。