AWSで管理画面やステージング環境を公開する際、IP制限を入れ忘れたまま公開してしまうというのは非常によくある事故です。セキュリティグループだけでは防ぎきれないケースも多く、確実なアクセス制御にはAWS WAFの活用が効果的です。
この記事では、Terraformを使ってAWS WAFのIP許可リスト(Allow List)を構築し、ALBやCloudFrontへ適用する方法を、ステップごとの実装例付きで解説します。「自分のIPを許可し忘れてアクセス不可になる」「CloudFrontでscopeを間違えてデプロイ失敗」といった、よくあるハマりポイントもあわせて紹介します。
AWS WAFとは#
AWS WAF(Web Application Firewall)は、Webアプリケーションへの不正なアクセスを検知・ブロックするためのAWSマネージドサービスです。ALB、CloudFront、API Gatewayなどの前段に配置して、以下のようなルールでトラフィックを制御できます。
| ルールの種類 | 用途 |
|---|---|
| IP制限 | 特定のIPアドレスのみ許可/ブロック |
| SQLインジェクション対策 | 悪意あるSQLクエリを検知しブロック |
| XSS対策 | クロスサイトスクリプティング攻撃を防止 |
| レート制限 | 短時間に大量のリクエストを送るIPをブロック |
| 国別アクセス制限 | 特定の国からのアクセスを許可/拒否 |
| Bot対策 | 悪質なBot・クローラーをブロック |
本記事ではこの中でも、実務で最も頻繁に利用されるIPアドレス制限にフォーカスします。
IP制限が必要になる場面#
筆者が実際にIP制限を導入したケースを紹介します。
| ユースケース | 具体例 | 理由 |
|---|---|---|
| 管理画面の保護 | admin.example.com | 社内の管理者のみアクセスを許可し、外部からの不正ログイン試行を防ぐ |
| ステージング環境 | stg.example.com | 開発チームとクライアントのみに限定公開し、検索エンジンにインデックスされるのを防ぐ |
| 開発環境 | dev.example.com | 社内ネットワークからのみアクセス可能にし、開発中の不安定な状態を外部に露出しない |
| メンテナンス時 | example.com | リリース直前に動作確認するため、一時的に開発チームのIPのみ許可する |
全体の構成#
今回構築するインフラの構成は以下の通りです。
Internet
│
▼
AWS WAF ← ここでIPを判定(許可 or ブロック)
│
▼
Application Load Balancer
│
▼
ECS / EC2(アプリケーション)WAFがALBの前段でリクエストを受け取り、許可リストに含まれるIPからのアクセスのみALBへ転送します。許可リストに含まれないIPからのアクセスは、WAFが自動的に 403 Forbidden を返します。
TerraformでIP制限を実装する(3ステップ)#
Step 1:IPSetを作成する#
まず、許可するIPアドレスを登録するためのIPSet(IPアドレスの集合)を作成します。
resource "aws_wafv2_ip_set" "allowed_ips" {
name = "allowed-office-ips"
scope = "REGIONAL"
ip_address_version = "IPV4"
addresses = [
"203.0.113.10/32", # 本社オフィス
"198.51.100.20/32", # リモートワーク拠点
]
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}| パラメータ | 説明 |
|---|---|
name | IPSetの識別名。AWS管理画面でも表示される |
scope | ALB/API Gatewayなら REGIONAL、CloudFrontなら CLOUDFRONT |
ip_address_version | IPV4 または IPV6 |
addresses | CIDR形式のIPアドレスリスト。/32 は単一IPを意味する |
Step 2:Web ACLを作成する#
次に、WAFのルール本体であるWeb ACLを作成します。ここがIP制限の核心部分です。
resource "aws_wafv2_web_acl" "main" {
name = "ip-restriction-acl"
scope = "REGIONAL"
# ★ 重要:デフォルトは「ブロック」
default_action {
block {}
}
# 許可IPからのアクセスのみ通す
rule {
name = "allow-office-ips"
priority = 1
action {
allow {}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.allowed_ips.arn
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AllowOfficeIPs"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "IPRestrictionACL"
sampled_requests_enabled = true
}
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
default_action { block {} }がIP制限の肝です。この設定により、「どのルールにもマッチしないリクエスト」はすべてブロックされます。つまり、明示的に許可したIPからのアクセスだけが通過する仕組みです。
Step 3:ALBに関連付ける#
作成したWeb ACLをALBに適用します。
resource "aws_wafv2_web_acl_association" "alb" {
resource_arn = aws_lb.main.arn
web_acl_arn = aws_wafv2_web_acl.main.arn
}あとは terraform plan で差分を確認し、terraform apply で適用すればIP制限が有効になります。
変数でIPアドレスを管理する#
実務ではIPアドレスをハードコードせず、変数として外出しにするのが一般的です。環境ごとにIPリストを切り替えやすくなります。
# variables.tf
variable "allowed_ips" {
description = "WAFで許可するIPアドレスのリスト(CIDR形式)"
type = list(string)
}
variable "environment" {
description = "環境名(dev / stg / prod)"
type = string
}# terraform.tfvars(環境ごとに用意)
allowed_ips = [
"203.0.113.10/32", # 本社
"198.51.100.20/32", # リモート拠点
"192.0.2.50/32", # VPN出口
]
environment = "production"# waf.tf(IPSetのaddressesに変数を渡す)
resource "aws_wafv2_ip_set" "allowed_ips" {
name = "${var.environment}-allowed-ips"
scope = "REGIONAL"
ip_address_version = "IPV4"
addresses = var.allowed_ips
}この構成にしておけば、dev.tfvars / stg.tfvars / prod.tfvars を使い分けることで、環境ごとに異なるIPリストを安全に管理できます。
ALBとCloudFrontの違い#
WAFのIP制限をALBとCloudFrontのどちらに適用するかによって、設定が異なります。最もよくあるエラーが**scope の指定ミス**です。
| 項目 | ALB / API Gateway | CloudFront |
|---|---|---|
scope | REGIONAL | CLOUDFRONT |
| デプロイリージョン | ALBと同じリージョン | us-east-1 固定 |
provider 指定 | 不要(デフォルトリージョン) | provider = aws.virginia 等が必要 |
CloudFrontにWAFを適用する場合、Terraformのproviderを us-east-1 に設定する必要がある点が特に重要です。
# CloudFront用のprovider(us-east-1)
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
# CloudFront用のIPSet・Web ACLにはこのproviderを指定
resource "aws_wafv2_ip_set" "allowed_ips_cf" {
provider = aws.virginia
name = "allowed-ips-cloudfront"
scope = "CLOUDFRONT"
ip_address_version = "IPV4"
addresses = var.allowed_ips
}注意: CloudFrontにWAFを適用する場合、scope = "REGIONAL" のまま適用しようとすると「WAFとCloudFrontのscopeが一致しない」というエラーになります。CloudFrontの場合は必ず CLOUDFRONT を指定し、かつリージョンは us-east-1 (バージニア北部) を指定する必要があります。
AWS Managed Rulesとの併用#
実務ではIP制限だけでなく、AWSが提供するManaged Rules(事前構築済みのセキュリティルール)を併用するのが一般的です。IP制限に加えて、SQLインジェクションやXSSなどの攻撃も自動で防御できます。
resource "aws_wafv2_web_acl" "main" {
name = "ip-restriction-acl"
scope = "REGIONAL"
default_action {
block {}
}
# ルール1:許可IPリスト
rule {
name = "allow-office-ips"
priority = 1
action {
allow {}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.allowed_ips.arn
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AllowOfficeIPs"
sampled_requests_enabled = true
}
}
# ルール2:AWS Managed Rules(許可されたIP内でさらに防御)
rule {
name = "aws-managed-common"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
vendor_name = "AWS"
name = "AWSManagedRulesCommonRuleSet"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedCommon"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "IPRestrictionACL"
sampled_requests_enabled = true
}
}注意: Managed Rulesでは
actionではなくoverride_action { none {} }を使用します。これはManaged Rules内の各ルールが独自のアクションを持っているため、それをそのまま適用するという意味です。
WAFの料金目安#
AWS WAFは従量課金制です。小規模な環境であればコストは最小限に抑えられます。
| 課金項目 | 料金(2026年5月時点、東京リージョン) |
|---|---|
| Web ACL | $5.00 / 月 |
| ルール(1つあたり) | $1.00 / 月 |
| リクエスト処理 | $0.60 / 100万リクエスト |
例: Web ACL 1つ + IPルール 1つ + Managed Rules 1つの構成で、月間100万リクエストの場合:
$5.00(ACL)+ $1.00(IPルール)+ $1.00(Managed Rules)+ $0.60(リクエスト)= 約$7.60/月開発・ステージング環境であればリクエスト数も少ないため、月額$7〜10程度で運用できるケースがほとんどです。
よくあるハマりポイントと対策#
1. 自分のIPを許可し忘れてアクセス不可になる#
これは最もよくある事故です。terraform apply した瞬間に自分自身がブロックされます。
対策: 適用前に以下のコマンドで現在のグローバルIPを確認し、許可リストに含まれているか必ずチェックしてください。
curl -s https://checkip.amazonaws.com万が一ロックアウトされた場合は、AWSマネジメントコンソール(WAFの管理画面はIPブロック対象外)からIPSetに自分のIPを追加するか、terraform apply を別のネットワーク(スマホのテザリング等)から実行してください。
2. NAT Gateway経由でIPが変わっている#
企業のオフィスネットワークでは、PCから直接インターネットに出るのではなく、NAT GatewayやProxyを経由している場合があります。この場合、許可すべきIPはPCのローカルIPではなく、NATの出口IPです。
ネットワーク管理者にグローバルIPを確認するか、上記の checkip.amazonaws.com で実際の出口IPを確認してください。
3. IPv6アクセスへの対応漏れ#
最近はIPv6でアクセスするユーザーが増えています。IPv4のIPSetだけ作成してIPv6を許可し忘れると、IPv6経由のアクセスがすべてブロックされます。
必要に応じて、IPv6用のIPSetも別途作成してください。
resource "aws_wafv2_ip_set" "allowed_ips_v6" {
name = "allowed-office-ips-v6"
scope = "REGIONAL"
ip_address_version = "IPV6"
addresses = ["2001:db8::/32"]
}4. terraform destroyでWAFを削除してしまう#
terraform destroy でインフラを削除する際、WAFも一緒に消えてしまいます。本番環境で destroy を実行する場合は、WAFリソースに lifecycle { prevent_destroy = true } を追加しておくと安全です。
まとめ#
| ポイント | 内容 |
|---|---|
| WAFの役割 | ALB/CloudFrontの前段でリクエストをフィルタリング |
| IP制限の仕組み | default_action { block {} } で全ブロック → 許可IPのみALLOW |
| 実装手順 | IPSet作成 → Web ACL作成 → ALBに関連付け |
| ALBとCFの違い | ALBは REGIONAL、CloudFrontは CLOUDFRONT + us-east-1 |
| 変数管理 | terraform.tfvars で環境ごとにIPリストを分離 |
| Managed Rules | IP制限と併用してSQLi/XSS/Bot対策も実装 |
| 料金 | 小規模なら月額$7〜10程度 |
AWS環境で管理画面や開発環境を保護する際は、まずWAFによるIP制限から導入するのがおすすめです。Terraformでコード管理すれば、設定ミスの防止・Git管理によるレビュー・環境間の設定統一が実現できます。
よくある質問(FAQ)#
Q. セキュリティグループのIP制限とWAFの違いは何ですか?#
セキュリティグループはEC2/ALBレベルのL3/L4(IPアドレス・ポート)のフィルタリングです。WAFはL7(HTTP/HTTPS)レベルで、URLパス・ヘッダー・リクエストボディなどの内容も含めた高度なフィルタリングが可能です。IP制限だけならセキュリティグループでも可能ですが、将来的にレート制限やBot対策を追加したい場合はWAFが適しています。
Q. WAFを適用するとレスポンス速度は遅くなりますか?#
ほぼ影響ありません。AWSの公式ドキュメントによると、WAFによるレイテンシの増加は通常1ms未満です。IP制限のような単純なルールであれば、パフォーマンスへの影響は無視できるレベルです。
Q. IPが頻繁に変わる場合はどうすればいいですか?#
リモートワークなどでIPが固定できない場合は、VPN(AWS Client VPN等)を導入し、VPNの出口IPをWAFで許可する方法が一般的です。あるいは、IP制限の代わりにCognito認証を使ったアクセス制御も検討してください。
Q. terraform applyの前にWAFの動作をテストできますか?#
Web ACLの default_action を一時的に count {} に変更すると、ブロックせずにログだけ記録する「カウントモード」で動作確認できます。CloudWatch LogsやS3にWAFログを出力して、想定通りのIPがマッチするか確認してから block {} に切り替えましょう。










