こしあん
2025-03-05

ALBで送信IPアドレスベースのルーティングを試す


5{icon} {views}


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の中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です