ALBで送信IPアドレスベースのルーティングを試す
Posted On 2025-03-05
ALBのリスナールールでソースIPを指定し、WAFを使わずに簡易的なアクセス制限を行える。Terraformにより特定IP向けの固定レスポンスルールを設定し、EC2インスタンスから動作を検証した。
目次
はじめに
- 接続元のIPベースでのルーティングは、WAFとか使わなくてもALBのリスナールールでできるというのを見たので試してみた
- リスナールールの条件につければいいだけ
アーキテクチャー図
Terraformでの実装方法
以下のようにすれば特定のIPにヒットするルールが作れる。
# ルールA: 特定IPからのアクセスに対する固定レスポンス
resource "aws_lb_listener_rule" "rule_a" {
listener_arn = aws_lb_listener.http.arn
priority = 100
condition {
source_ip {
values = ["${var.rule_a_ip}/32"]
}
}
action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "これはルールAのレスポンスです"
status_code = "200"
}
}
}
マネジメントコンソールで確認
このようなルーティングになっている
EC2から確認
EC2のプライベートIPは指定して起動することが可能。これでEC2のIPを固定して専用のレスポンスが出るか確認する。EC2のIPは以下の通りになった。EC2は3つ起動し、うち2つ(A,B)は固定する。Cは起動時のIP設定は行わずに自動設定に任せる。
instance_a_ip = "172.18.196.10" # 固定
instance_b_ip = "172.18.196.100" # 固定
instance_c_ip = "172.18.197.96" # 自動設定
インスタンスA
優先度100のルールAが引っかかった(想定通り)
インスタンスB
優先度200のルールBが引っかかった(想定通り)
インスタンスC
どれもIPの条件にかからないのでデフォルトのルールが引っかかった(想定通り)
応用
- ALBまでリクエストが届いてしまうというデメリットはあるが、ALBだけでも一応は簡易的なアクセス制限は可能
- 単純にアクセスを弾きたいというのであれば単純な例だとALBのセキュリティグループ、本格的にやるならWAFを使うのが良い
- とりあえずルーティングできたという確認まで
全体コード
# 変数定義
variable "vpc_id" {
description = "VPC ID"
type = string
}
variable "vpc_ip_cidr" {
description = "VPC IP CIDR"
type = string
}
variable "private_subnet_ids" {
description = "プライベートサブネットのID一覧"
type = list(string)
}
variable "rule_a_ip" {
description = "ルールAに対応するIP"
type = string
default = "172.18.196.10"
}
variable "rule_b_ip" {
description = "ルールBに対応するIP"
type = string
default = "172.18.196.100"
}
# 最新のAmazon Linux 2 AMIの取得
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["137112412989"] # AmazonのAMI所有者ID
filter {
name = "name"
# Amazon Linux 2023 AMIの名前パターン。minimumを除外する
values = ["al2023-ami-2023*-kernel-*-x86_64"]
}
filter {
name = "architecture"
values = ["x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# セキュリティグループ定義
resource "aws_security_group" "alb_sg" {
name = "internal-alb-sg"
description = "Security group for internal ALB"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = [var.vpc_ip_cidr] # プライベートネットワーク範囲(要調整)
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = "internal-alb-sg"
}
}
resource "aws_security_group" "ec2_sg" {
name = "ec2-ssm-sg"
description = "Security group for EC2 instances with SSM access"
vpc_id = var.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = "ec2-ssm-sg"
}
}
# IAMロールとポリシー(Session Manager用)
resource "aws_iam_role" "ssm_role" {
name = "ec2-ssm-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "ssm_policy" {
role = aws_iam_role.ssm_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_instance_profile" "ssm_profile" {
name = "ec2-ssm-profile"
role = aws_iam_role.ssm_role.name
}
# Internal ALB
resource "aws_lb" "internal_alb" {
name = "alb-internal"
internal = true
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = var.private_subnet_ids
tags = {
Name = "alb-internal"
}
}
# ALBリスナー
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.internal_alb.arn
port = 80
protocol = "HTTP"
# ルールC: デフォルトレスポンス
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "これはデフォルトのレスポンスです(ルールC)"
status_code = "200"
}
}
}
# ルールA: 特定IPからのアクセスに対する固定レスポンス
resource "aws_lb_listener_rule" "rule_a" {
listener_arn = aws_lb_listener.http.arn
priority = 100
condition {
source_ip {
values = ["${var.rule_a_ip}/32"]
}
}
action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "これはルールAのレスポンスです"
status_code = "200"
}
}
}
# ルールB: 別の特定IPからのアクセスに対する固定レスポンス
resource "aws_lb_listener_rule" "rule_b" {
listener_arn = aws_lb_listener.http.arn
priority = 200
condition {
source_ip {
values = ["${var.rule_b_ip}/32"]
}
}
action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "これはルールBのレスポンスです"
status_code = "200"
}
}
}
# EC2インスタンス - ルールAに対応するIP
resource "aws_instance" "instance_a" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
subnet_id = var.private_subnet_ids[0]
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
iam_instance_profile = aws_iam_instance_profile.ssm_profile.name
private_ip = var.rule_a_ip
tags = {
Name = "instance-a"
}
}
# EC2インスタンス - ルールBに対応するIP
resource "aws_instance" "instance_b" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
subnet_id = var.private_subnet_ids[0]
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
iam_instance_profile = aws_iam_instance_profile.ssm_profile.name
private_ip = var.rule_b_ip
tags = {
Name = "instance-b"
}
}
# EC2インスタンス - IPを指定しない
resource "aws_instance" "instance_c" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
subnet_id = var.private_subnet_ids[0]
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
iam_instance_profile = aws_iam_instance_profile.ssm_profile.name
tags = {
Name = "instance-c"
}
}
# 出力
output "alb_dns_name" {
description = "ALBのDNS名"
value = aws_lb.internal_alb.dns_name
}
output "instance_a_ip" {
description = "インスタンスAのプライベートIP"
value = aws_instance.instance_a.private_ip
}
output "instance_b_ip" {
description = "インスタンスBのプライベートIP"
value = aws_instance.instance_b.private_ip
}
output "instance_c_ip" {
description = "インスタンスCのプライベートIP"
value = aws_instance.instance_c.private_ip
}
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー