Suricata NAT Instance 기반 IPS/IDS 실습 랩 구축 (1편) : Terraform 으로 AWS 리소스생성

Terraform으로 최소 비용 실습 인프라 만들기

⚠️ 사전 안내

  • 이 구성은 실습용이다. 프로덕션에서는 단일 NAT Instance 같은 SPOF 구조를 권장하지 않는다.
  • 운영 환경에서는 GSLB + 다중 센서(다중 AZ/다중 인스턴스) 구조를 권장한다.
  • Terraform이 익숙하지 않으면: AWS Console에서 리소스를 수동으로 만들고이 편(1편)은 스킵하고
    다음 편의 Suricata/ELK 설치 및 시그니처 테스트 가이드부터 바로
    참조하도록 한다.
  • 인스턴스 사양 관련 참고 사항

    본 실습에서는 Suricata(NAT Instance)와 타겟 서버를 모두
    t3.medium으로 구성했다.
    이는 초기 구성과 성능 검증을 단순화하기 위한 선택이다.
    다만 비용을 고려하면 두 인스턴스를 동일 사양으로 유지할 필요는 없다.

    Suricata NAT Instance
    패킷 처리, NFQUEUE, ELK(Logstash/Elasticsearch)까지 함께
    수행하므로 최소 t3.medium 이상을 권고한다.

    웹 서버 / PBX 타겟 인스턴스
    단순 트래픽 발생 및 공격 테스트 용도이므로
    t3.micro 또는 t3.small로 낮춰도 무관하다.
    단, 이 경우 타겟 인스턴스의 사양 변경은 Terraform 코드에서
    직접 수정해야 한다.
    (본 실습에서는 단순화를 위해 동일 사양으로 유지한다)

1. 목표

  • NAT Instance 위에 Suricata + ELK를 구성 (비용문제로 opensearch 사용안함)
  • Target 서버(Private Subnet)를 만들고 모든 트래픽이 NAT를 강제 경유하도록 라우팅 구성
  • EIP를 붙여서 NAT의 퍼블릭 IP를 고정
  • Terraform으로 한 번에 생성 / 한 번에 삭제

📌 Terraform 코드 적용 범위에 대한 안내 (중요)

이 시리즈에서 제공하는 Terraform 코드는
Suricata Inline IPS 구성을 설명하기 위한 핵심 예제만을 포함한다.

실제 운영 환경에서는 이미 다음과 같은 리소스들이
각자 다른 구조와 코드로 관리되고 있을 가능성이 높다.

  • VPC
  • Subnet (Public / Private)
  • Route Table
  • Internet Gateway
  • 기존 EC2 및 Security Group
  • 기타 네트워크 모듈

고로, 각자의 운영환경이 달라서 오히려 혼선만 초래할 우려가 있어, 이 시리즈에서는
내가 운영하고 있는 환경의 전체 Terraform 코드를 모두 공개하지 않는다.

대신 다음 원칙을 기준으로 설명한다.


이 시리즈의 Terraform 가이드 원칙

  • Suricata와 직접적으로 연관된 리소스만 코드로 제시
  • 기존 인프라는
    • data 리소스로 참조하거나
    • 상위 모듈에서 변수로 전달받는 것을 전제로 설명
  • 독자는 자신의 Terraform 구조에 맞게 응용해야 한다

즉, 이 시리즈의 코드는:

복사·붙여넣기용이 아니라
구조 이해와 응용을 위한 기준 예제다.


2. 아키텍처

핵심은 2개다.

  • NAT Instance는 source/dest check OFF
  • Private Subnet 라우팅은 0.0.0.0/0 → NAT Instance ENI 로 강제

3. 폴더 구조

이미지처럼 모듈은 파일 3개로 구분한다.

my-site/
├─ envs/
│  └─ prod/
│     ├─ main.tf
│     ├─ variables.tf
│     └─ terraform.tfvars
└─ modules/
   └─ suricata_lab/
      ├─ main.tf        
      ├─ variables.tf   
      └─ outputs.tf     
  • modules/suricata_lab/main.tf에 리소스 전부 몰아 넣는다.
  • variables.tf, outputs.tf에는 정의만 최소로 둔다.
  • envs/prod는 모듈 호출 + 변수 선언만 한다.

4. envs/prod 쪽에 추가해야 하는 것

4.1 envs/prod/variables.tf

variable "key_name" {
  type        = string
  description = "EC2 key pair name"
}

4.2 envs/prod/main.tf

(기존 다른 모듈이 이미 있어도 상관 없음. 아래 모듈만 추가하면 됨)

module "suricata_lab" {
  source = "../../modules/suricata_lab"
  name          = "sec-lab"
  key_name      = var.key_name
  my_ip         = "162.120.XXX.XXX/32"  #실제 접속할 자신의 IP 적용
  instance_type = "t3.medium"
}

5. modules/suricata_lab 코드

modules/suricata_lab/main.tf

############################################################
# modules/suricata_lab/main.tf
# - Ubuntu 24.04
# - NAT Instance (Suricata IPS + ELK) in Public Subnet
# - Target EC2 in Private Subnet
# - Private RT: 0.0.0.0/0 -> NAT Instance
# - NAT Instance: source_dest_check = false (필수)
############################################################



############################
# VPC / Subnet / IGW
############################
resource "aws_vpc" "lab" {
  cidr_block           = "10.20.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "${var.name}-vpc"
  }
}

resource "aws_internet_gateway" "lab" {
  vpc_id = aws_vpc.lab.id

  tags = {
    Name = "${var.name}-igw"
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.lab.id
  cidr_block              = "10.20.0.0/24"
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.name}-public"
  }
}

resource "aws_subnet" "private" {
  vpc_id     = aws_vpc.lab.id
  cidr_block = "10.20.1.0/24"

  tags = {
    Name = "${var.name}-private"
  }
}

############################
# Route Table - Public
############################
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.lab.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.lab.id
  }

  tags = {
    Name = "${var.name}-rt-public"
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}




############################
# Security Group - NAT/Suricata/ELK
############################
resource "aws_security_group" "nat" {
  name        = "${var.name}-sg-nat"
  description = "NAT Instance (Suricata+ELK) SG"
  vpc_id      = aws_vpc.lab.id

  # SSH (관리)
  ingress {
    description = "SSH from my IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.my_ip]

  }

# HTTP 접속허용 (only my ip)
  ingress {
    description = "HTTP from my IP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [var.my_ip]
  }

  # Kibana 
  ingress {
    description = "Kibana from my IP"
    from_port   = 5601
    to_port     = 5601
    protocol    = "tcp"
    cidr_blocks = [var.my_ip]
  }

#private target 허용
  ingress {
    description = "All from target ip"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["10.20.1.0/24"]

  }


  # Outbound all
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.name}-sg-nat"
  }
}

############################
# Security Group - Target (Private)
############################
resource "aws_security_group" "target" {
  name        = "${var.name}-sg-target"
  description = "Target EC2 (private) SG"
  vpc_id      = aws_vpc.lab.id

  # SSH (관리)
ingress {
  description     = "SSH from NAT Instance only"
  from_port       = 22
  to_port         = 22
  protocol        = "tcp"
  security_groups = [aws_security_group.nat.id]
}



  # HTTP: NAT SG만 허용(필요하면)
  ingress {
    description     = "HTTP 80 from NAT SG"
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.nat.id]
  }

  # Outbound all
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.name}-sg-target"
  }
}

############################
# EC2 - NAT/Suricata/ELK (Public)
############################
resource "aws_instance" "nat" {
  ami                         = "ami-0a71e3eb8b23101ed"
  instance_type               = var.instance_type
  subnet_id                   = aws_subnet.public.id
  vpc_security_group_ids      = [aws_security_group.nat.id]
  key_name                    = var.key_name
  associate_public_ip_address = true

 root_block_device {
    volume_size           = 20
    volume_type           = "gp3"
    delete_on_termination = true
  }

  # NAT Instance 필수
  source_dest_check = false

  tags = {
    Name = "${var.name}-nat-suricata-elk"
  }
}

############################
# EC2 - Target (Private)
############################
resource "aws_instance" "target" {
  ami                    = "ami-0a71e3eb8b23101ed"
  instance_type          = var.instance_type
  subnet_id              = aws_subnet.private.id
  vpc_security_group_ids = [aws_security_group.target.id]
  key_name               = var.key_name

  tags = {
    Name = "${var.name}-target"
  }
}

############################
# Route Table - Private
#  - 0.0.0.0/0 -> NAT Instance
############################
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.lab.id

  route {
  cidr_block              = "0.0.0.0/0"
  network_interface_id    = aws_instance.nat.primary_network_interface_id
}


  tags = {
    Name = "${var.name}-rt-private"
  }
}

resource "aws_route_table_association" "private" {
  subnet_id      = aws_subnet.private.id
  route_table_id = aws_route_table.private.id
}

# NAT Instance용 EIP
resource "aws_eip" "nat" {
  domain = "vpc"

  tags = {
    Name = "${var.name}-eip-nat"
  }
}

# NAT Instance에 EIP 연결
resource "aws_eip_association" "nat" {
  allocation_id = aws_eip.nat.id
  instance_id   = aws_instance.nat.id
}

6. modules/suricata_lab/variables.tf

리소스는 main.tf에 전부 들어갔고, 여기는 변수 선언만 둔다.

modules/suricata_lab/variables.tf

variable "name" {
  type        = string
  description = "Lab name prefix"
}

variable "region" {
  type    = string
  default = "ap-northeast-2"
}

variable "key_name" {
  type = string
}

variable "my_ip" {
  type        = string
  description = "162.120.xxx.xxx/32"     # 실제 접속할 IP 적용
}

variable "instance_type" {
  type    = string
  default = "t3.medium"
}

7. modules/suricata_lab/outputs.tf

outputs는 최소로만 둔다.
실습할 때 NAT 접속/타겟 확인 용도만 출력.

modules/suricata_lab/outputs.tf

output "nat_public_ip" {
  value = aws_eip.nat.public_ip
}


output "target_private_ip" {
  value = aws_instance.target.private_ip
}

8. terrafrom 실행할 때 변수 주입 (윈도우 환경)

키페어는 Terraform에서 만들 필요 없다.AWS console에서 만든 걸 이름만 넣는다.

##visual studio code 터미널에서 아래경로 이동##
cd C:\projects\my-site\envs\prod

PS C:\projects\my-site\envs\prod> terraform init

##key pair 확인##
PS C:\projects\my-site\envs\prod> aws ec2 describe-key-pairs --region ap-northeast-2 --query "KeyPairs[].KeyName" --output table

##key pair 변수 주입 및 테라폼 실행##
PS C:\projects\my-site\envs\prod>terraform apply -var="key_name=blog"

Step-by-Step: 실제 테라폼 실행과 인프라 생성 과정


9. 이 편(1편)에서 끝나는 지점

여기까지 하면:

  • NAT Instance (Suricata + ELK 자리) 생성
  • EIP 연결 완료 (고정 Public IP)
  • Private Subnet 트래픽 NAT 강제 경유 라우팅 완료
  • Target 서버 생성 완료

이제 다음 편에서 할 일은 명확하다.

  • NAT 인스턴스에 Suricata + ELK 설치
  • ip_forward + iptables(NFQUEUE) 구성
  • 룰 업데이트 + SIP 시그니처 검증

마무리 및 당부 : Terraform이 익숙하지 않다면

  • Console에서 VPC/Subnet/IGW/RT/EC2/EIP를 수동 생성하고
  • 이 편은 스킵하고
  • 2편부터 따라가면 된다.

🛠 마지막 수정일: 2025.12.19

ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. 본문 및 이미지를 무단 복제·배포할 수 없습니다. 공유 시 반드시 원문 링크를 명시해 주세요.
ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. All rights reserved. Unauthorized copying or redistribution of the text and images is prohibited. When sharing, please include the original source link.

💡 도움이 필요하신가요?
Zabbix, Kubernetes, 그리고 다양한 오픈소스 인프라 환경에 대한 구축, 운영, 최적화, 장애 분석, 광고 및 협업 제안이 필요하다면 언제든 편하게 연락 주세요.

📧 Contact: jikimy75@gmail.com
💼 Service: 구축 대행 | 성능 튜닝 | 장애 분석 컨설팅

📖 E-BooK [PDF] 전자책 (Gumroad): Zabbix 엔터프라이즈 최적화 핸드북
블로그에서 다룬 Zabbix 관련 글들을 기반으로 실무 중심의 지침서로 재구성했습니다. 운영 환경에서 바로 적용할 수 있는 최적화·트러블슈팅 노하우까지 모두 포함되어 있습니다.


💡 Need Professional Support?
If you need deployment, optimization, or troubleshooting support for Zabbix, Kubernetes, or any other open-source infrastructure in your production environment, or if you are interested in sponsorships, ads, or technical collaboration, feel free to contact me anytime.

📧 Email: jikimy75@gmail.com
💼 Services: Deployment Support | Performance Tuning | Incident Analysis Consulting

📖 PDF eBook (Gumroad): Zabbix Enterprise Optimization Handbook
A single, production-ready PDF that compiles my in-depth Zabbix and Kubernetes monitoring guides.