다크 화면에 모노스페이스로 작성된 FortiGate 방화벽 정책과 VPN 구성 코드
글 요약
Terraform × FortiGate 조합으로 방화벽 정책, Address 객체, IPsec VPN까지 코드로 관리하는 전 과정을 정리합니다. Provider 선택부터 State 관리, 실무 실패 사례까지 한 번에 보실 수 있습니다.
“FortiGate 설정 50대 장비, 정책 200개… 매번 GUI에서 클릭하다 보면 밤새요.”
Terraform으로 FortiGate를 코드로 다루면 정책 변경 이력, 백업, 멀티 사이트 배포까지 자동화됩니다. 이 글에서는 실무에서 바로 쓸 수 있는 HCL 코드 패턴과 흔한 실패 사례를 전부 공개합니다.
현업 네트워크 엔지니어분들 중에 FortiGate GUI에 로그인해서 정책 한 줄 추가하고, 다시 두 번째 장비에 접속해서 똑같은 작업을 반복해보신 적 있으실 거든요. 장비가 10대를 넘어가는 순간, 사람 손으로 하는 작업은 반드시 누락과 불일치라는 두 가지 문제를 만들고요. 특히 보안 정책은 한 줄 실수가 곧 인시던트로 직결되기 때문에, 코드로 관리하는 방식에 대한 수요가 매년 늘고 있습니다.
Terraform은 클라우드 자동화 도구로 유명하지만, fortios provider를 통해 FortiGate REST API를 그대로 호출할 수 있습니다. 단순히 명령을 던지는 수준이 아니라, State 파일을 통해 현재 설정과 코드의 차이점을 비교하고, 승인된 변경만 실제 장비에 반영하는 GitOps 워크플로우가 가능해지거든요. 이 글에서는 실무에서 자주 쓰는 3가지 리소스—Firewall Policy, Address Object, IPsec VPN—에 집중해서 설명드리겠습니다.
FortiGate 자동화 도구는 Terraform만 있는 게 아닙니다. Ansible, FortiManager, REST API 직접 호출까지 네 가지 정도의 선택지가 실무에서 경쟁하고 있어요. 각 도구가 어떤 강점과 한계를 가지는지 본문 섹션 1에서 비교표로 정리해드릴 테니, 본인 환경에 맞는 도구 선택에 참고하시기 바랍니다.
📌 이 글의 핵심 정리
- Terraform은 FortiGate의 단일 진실 공급원(SSOT) 역할을 하며, 정책 변경 이력을 Git으로 추적할 수 있게 해줍니다.
- fortios provider는 REST API를 추상화해 Firewall Policy, Address, VPN 자원을 HCL로 선언할 수 있도록 합니다.
- State 파일 백업과 Lock 관리를 소홀히 하면, 동시 작업 시 설정 충돌로 운영 장애가 발생할 수 있습니다.
- Ansible, FortiManager 대비 Terraform은 멀티 클라우드 통합과 선언적 모델에서 우위에 있습니다.
- VPN과 같이 비밀값이 많은 자원은 별도 변수 분리 + secret backend 연동이 필수입니다.
왜 FortiGate를 Terraform으로 관리해야 할까
FortiGate를 코드로 관리하는 방식은 사실 Terraform 이전에도 있었습니다. 다만 각각 명백한 한계가 존재했어요. 본 섹션에서는 현업에서 자주 비교되는 네 가지 자동화 방식의 차이를 표로 정리해드리고, 왜 여전히 Terraform이 우선 선택되는지 설명드립니다.
FortiGate 자동화 도구 4종 비교
| 구분 | Terraform + fortios | Ansible + fortios module | FortiManager | REST API 직접 호출 |
|---|---|---|---|---|
| 모델 | 선언적(Declarative) | 절차적(Procedural) | 중앙 관제형 | 명령형 |
| 상태 추적 | State 파일 | 없음 | ADOM DB | 없음 |
| 멀티 클라우드 통합 | 매우 강함 | 보통 | 약함 | 약함 |
| GUI 의존성 | 없음 | 없음 | 강함 | 없음 |
| Dry-run | terraform plan | check mode | policy package 미리보기 | 불가 |
| 라이선스 비용 | 무료(OSS) | 무료(OSS) | 유료 | 무료 |
| 적합한 환경 | 멀티 사이트 + 멀티 클라우드 | 단순 배포 스크립트 | 대규모 엔터프라이즈 | 소규모 즉시 작업 |
표를 보시면 알 수 있듯, Terraform은 Dry-run이 표준 기능이고 멀티 클라우드 통합이 무료로 가능하다는 점에서 우위가 명확합니다. 반면 FortiManager는 대기업 환경에서 중앙 관제와 규정 준수 감사 로그가 필요할 때 강점을 가지지만, 라이선스 비용과 GUI 의존성이라는 트레이드오프가 있어요.
선언적 모델이 왜 중요한가
Ansible처럼 절차적 스크립트를 쓰면, “이미 정책이 있는지 확인하고 없으면 추가” 같은 분기 로직을 매번 작성해야 합니다. 정책이 200개가 넘어가면 스크립트 유지보수가 사실상 불가능해지더라고요. Terraform은 “이 상태가 되어야 한다”만 선언하면, 실제 장비와 코드의 차이를 자동으로 계산해서 필요한 변경만 적용합니다. 이 점이 FortiGate처럼 정책이 계속 추가되는 환경에 특히 잘 맞아요.
Provider 설정과 인증 — 첫 번째 코드
FortiGate 방화벽 장비 측면 클로즈업 사진과 이더넷 케이블 연결 모습
FortiGate 자동화의 시작은 fortios provider 설치와 인증 설정입니다. Terraform이 FortiGate에 접근하려면 REST API 토큰이 필요한데, 보통 두 가지 방식 중 하나를 선택합니다.
인증 방식 두 가지
첫 번째는 FortiGate에서 직접 API 토큰을 발급받아 사용하는 방식입니다. FortiGate 7.0 이상에서는 System → Administrators → New → REST API Administrator 메뉴에서 발급이 가능하고, 이 토큰을 Terraform 변수에 넣어주면 돼요. 두 번째는 administrator 계정의 아이디/비밀번호를 그대로 사용하는 방식으로, 레거시 FortiOS 버전에서 유효합니다. 다만 보안상 API 토큰 방식이 권장됩니다.
기본 provider 블록 예시
terraform {
required_providers {
fortios = {
source = "fortinetdev/fortios"
version = "~> 1.17"
}
}
}
provider "fortios" {
hostname = "fortigate.example.com"
token = var.fgt_token
insecure = "false"
}
참고: fortios provider는 Fortinet이 직접 관리하는 공식 provider이며, Terraform Registry에서 fortinetdev/fortios 이름으로 검색 가능합니다.
여기서 insecure = “false”는 TLS 인증서 검증을 의미합니다. 사내 CA를 쓰는 경우 ca_cert 변수에 인증서 PEM을 넘기면 되구요. self-signed 인증서를 그대로 쓰면 plan 단계부터 인증 오류가 나기 때문에, 첫 연결 테스트 시점에는 임시로 insecure를 켜두는 경우가 많습니다.
⚠️ FortiGate Provider 인증 주의
- API 토큰은 절대 코드 저장소에 평문 커밋하지 마세요. Terraform Cloud Variables나 Vault 같은 secret backend를 반드시 사용하세요.
- 토큰에 trusthost 설정을 걸어두지 않으면, 외부에서 탈취 시 곧바로 관리자 권한으로 진입 가능합니다. FortiGate의 Administrator 설정에서 신뢰 IP 대역을 반드시 제한하세요.
Address Object와 Service를 코드로 선언하기
Firewall Policy를 작성하기 전에 반드시 선행되어야 할 작업이 Address Object 정의입니다. FortiGate에서 정책의 source/destination은 IP 주소 그대로가 아니라 대부분 Address Object를 참조하기 때문이죠. 이걸 코드로 관리하지 않으면 정책 코드가 사람이 읽기 어려운 숫자 나열이 되고, 변경 추적이 불가능해집니다.
단일 Address 정의 패턴
resource "fortios_firewall_address" "web_server" {
name = "WEB-SRV-01"
type = "ipmask"
subnet = "10.10.20.50 255.255.255.255"
comment = "Managed by Terraform"
}
subnet 필드는 FortiGate 특유의 “IP mask” 표기법을 따릅니다. 단일 호스트일 때는 IP와 마스크 사이에 공백을 두는 점에 주의하세요. 처음 Terraform으로 FortiGate를 다루시는 분들 대부분이 여기서 한 번씩 막히더라고요.
대량 Address를 변수 + for_each로 관리
실무에서는 서버 50대의 Address를 한 번에 등록해야 하는 경우가 많습니다. 이럴 때는 tfvars 파일과 for_each 조합이 가장 깔끔합니다.
variable "web_servers" {
type = map(object({
ip = string
comment = string
}))
default = {
"WEB-SRV-01" = { ip = "10.10.20.50", comment = "Frontend-1" }
"WEB-SRV-02" = { ip = "10.10.20.51", comment = "Frontend-2" }
"WEB-SRV-03" = { ip = "10.10.20.52", comment = "Frontend-3" }
}
}
resource "fortios_firewall_address" "webservers" {
for_each = var.web_servers
name = each.key
type = "ipmask"
subnet = "${each.value.ip} 255.255.255.255"
comment = each.value.comment
}
이렇게 작성하면 신규 서버 IP가 추가될 때 tfvars 파일만 수정하고 terraform apply를 실행하면 끝납니다. GUI에서 일일이 추가하던 시간을 90% 줄일 수 있어요.
💡 Address Object 관리 꿀팁
- Address 객체 명명 규칙을 환경-역할-번호 형태로 통일하면, 정책 작성 시 자동완성이 잘 동작합니다. 예: PRD-WEB-01, STG-DB-02.
- Service 객체도 함께 코드화하세요. 정책에 포트 번자를 직접 쓰면 추후 포트 변경 시 일일이 찾아 수정해야 하는 비효율이 발생합니다.
Firewall Policy 자동화 핵심 패턴
FortiGate의 핵심인 Firewall Policy는 fortios_firewall_policy 리소스로 정의합니다. 이 자원이 가장 변경 빈도가 높고, 보안 영향이 크기 때문에 IaC의 효과가 가장 큰 영역이기도 합니다.
기본 Policy HCL 패턴
resource "fortios_firewall_policy" "allow_web" {
name = "ALLOW-INTERNET-WEB"
srcintf = ["port1"]
dstintf = ["port2"]
srcaddr = [fortios_firewall_address.web_server.name]
dstaddr = ["all"]
action = "accept"
schedule = "always"
service = ["HTTP", "HTTPS"]
logtraffic = "all"
nat = "enable"
comments = "Managed by Terraform"
}
여기서 srcaddr에 fortios_firewall_address.web_server.name을 참조하는 부분이 중요합니다. 이렇게 써두면 Address 이름이 바뀌면 정책도 자동으로 따라 갱신되며, Terraform이 의존성을 추적해서 순서대로 적용해줍니다. 일반적으로 사람이 코드를 볼 때 숫자 IP보다 의미 있는 객체 이름이 훨씬 직관적이에요.
Policy 순서와 정책 ID 충돌 회피
FortiGate는 정책을 policyid라는 정수 순서로 처리하기 때문에, 중간에 정책을 끼워 넣을 때 기존 정책의 ID가 밀리거나 깨지는 문제가 종종 생깁니다. Terraform으로 관리하면 이 문제를 해결할 수 있는데, fortios_firewall_policy 리소스에서 policyid 필드를 명시적으로 지정하면 정책 순서를 코드로 통제할 수 있습니다.
⚠️ Firewall Policy 순서 관리 주의
- policyid를 명시적으로 지정하지 않으면 Terraform이 임의의 번호를 부여하고, FortiGate는 작은 번호부터 매칭하기 때문에 의도와 다른 정책 우선순위가 발생할 수 있습니다.
- 정책 사이에 100번 단위로 여유 ID를预留(예: 100, 200, 300)해두면 추후 끼워 넣기 작업이 깔끔해집니다.
IPsec VPN 구성을 IaC로 전환하는 법
IPsec VPN은 FortiGate 설정 중 가장 복잡한 자원 중 하나입니다. Phase 1, Phase 2 제안, pre-shared key, remote gateway, Ike 버전 등이 모두 조합되어야 하거든요. 그래도 Terraform으로 관리하면 큰 이점이 있습니다.
VPN 구성의 핵심 자원 3종
fortios_vpn_ipsec_phase1_interface, fortios_vpn_ipsec_phase2_interface, fortios_firewall_policy(VPN 트래픽용) 이렇게 세 자원이 함께 움직여야 VPN이 완성됩니다. Phase1에서 터널 인터페이스 정의, Phase2에서 암호화 도메인 정의, 정책에서 트래픽 허용이 이뤄지죠.
resource "fortios_vpn_ipsec_phase1_interface" "branch_vpn" {
name = "BRANCH-VPN"
type = "static"
interface = "port1"
remote_gw = "203.0.113.10"
ike_version = 2
proposal = "aes256-sha256"
authmethod = "psk"
psksecret = var.vpn_psk
dpd = "on-demand"
dpd_delaycount = 10
dpd_timeout = 60
comments = "Branch office tunnel"
}
psksecret 부분이 가장 민감합니다. 평문으로 코드에 적는 순간 보안 인시던트 위험이 급격히 올라가요. 반드시 variable로 분리하고 secret backend에서 주입받도록 설계하세요.
💡 VPN IaC 꿀팁
- PSK는 Terraform Cloud / Vault / AWS Secrets Manager 같은 secret backend에 저장하고, sensitive = true 변수로 선언해 로그 노출을 차단하세요.
- 멀티 사이트 VPN은 remote_gw만 다른 동일한 phase1/phase2 모듈로 분리해서 재사용하면, 신규 지점 추가 시 5분 안에 IaC로 배포가 끝납니다.
실무 운영 노하우와 실패 사례 정리
이론과 코드는 충분히 배웠으니, 이제 실무에서 실제로 겪는 실패 사례와 이를 해결하는 운영 노하우를 정리해드릴게요. 제가 직접 겪었던 케이스이기도 합니다.
실패 사례 — State 파일 충돌로 인한 정책 중복 생성
한 지사에서 두 엔지니어가 동시에 Terraform을 실행한 적이 있었어요. State 파일을 로컬에 두고 S3로 공유는 했지만, lock 설정이 없었기 때문에 두 사람이 같은 정책 자원을 동시에 apply하면서 FortiGate 측에 동일 정책이 두 번 생성되는 사고가 났습니다. 결국 해당 지사의 VPN이 중복 라우팅을 일으켜 약 40분간 트래픽이 불안정했어요.
해결 노하우 — 원격 Backend + Lock 테이블
이 경험을 계기로 DynamoDB lock 테이블 + S3 backend 조합을 표준화했습니다. terraform { backend “s3” } 블록에 dynamodb_table을 지정하면, apply 실행 시 자동으로 잠금이 걸려 동시 작업이 차단됩니다. 그리고 State 파일을 절대 로컬에 두지 않는 규칙을 팀 컨벤션으로 정착시켰어요. 이 두 가지만 지켜도 위와 같은 중복 생성 사고는 100% 방지됩니다.
실무 운영 체크리스트
- terraform plan 후 코드 리뷰 — apply 전에 동료의 PR 승인을 거치도록 GitHub Actions 등을 구성하세요.
- State 백업 자동화 — apply 성공 시 S3 versioning과 별도 스냅샷 저장으로 복원 가능 상태를 유지하세요.
- FortiGate 설정 백업 — Terraform은 전체 설정을 관리하지 않으므로, execute backup config 같은 정기 백업 스케줄을 별도로 운영하세요.
- Provider 버전 고정 — version = “~> 1.17” 같은 제약을 두어 예기치 않은 스키마 변경을 차단하세요.
- 모듈화 — Address, Service, Policy, VPN 각각을 별도 모듈로 분리하면 멀티 사이트 재사용성이 크게 올라갑니다.
이 정도 규칙만 정착시켜도, FortiGate 자동화는 단순한 명령 실행 도구가 아니라 조직의 보안 거버넌스 도구로 자리 잡게 됩니다. GUI에서 클릭하던 시간은 줄고, 변경 이력은 자동으로 Git에 남고, 신규 사이트 온보딩은 모듈 호출 한 번으로 끝나거든요.
자주 묻는 질문
Q. FortiGate 6.4 같은 구버전에서도 Terraform이 동작하나요?
A. fortios provider는 FortiOS 6.4 이상에서 안정적으로 동작합니다. 그 이하 버전에서는 일부 리소스 속성이 누락될 수 있으니, 가급적 FortiOS 7.0 이상으로 업그레이드 후 적용하는 것을 권장합니다.
Q. Terraform으로 관리하지 않는 설정은 어떻게 되나요?
A. Terraform은 코드에 선언된 자원만 관리합니다. GUI에서 수동으로 변경한 부분은 다음 apply 시 코드에 따라 덮어쓰기 되거나, 이미 일치한다면 그대로 유지됩니다. 전체 설정을 통합 관리하려면 FortiManager를 함께 검토해 보세요.
Q. 정책 변경 시 세션이 끊기나요?
A. 정책 변경 종류에 따라 다릅니다. 단순 객체 수정이나 정책 추가/삭제는 보통 기존 세션에 영향을 주지 않지만, 인터페이스 단위의 큰 변경은 세션 리셋이 발생할 수 있습니다. 운영 환경에서는 변경 시간대를 사전 공지하는 것이 안전합니다.
Q. 여러 FortiGate 장비를 한 번에 관리하려면 어떻게 해야 하나요?
A. Terraform의 count 또는 for_each 메타인수와 함께 module 패턴을 활용하세요. 장비 목록을 tfvars로 두고 provider 블록을 module 안에서 alias로 분리하면, 하나의 코드 베이스로 50대 이상도 일괄 관리할 수 있습니다.
Terraform과 FortiGate의 조합은 단순한 자동화를 넘어, 네트워크 보안 운영의 패러다임을 바꾸고 있습니다. GUI 클릭에 의존하던 일상에서 벗어나, 코드 리뷰 · 버전 관리 · 자동 백업이 가능한 인프라로 진화시킬 수 있거든요. 오늘 글에서 정리한 Provider 설정, Address/Service 객체화, Policy 자동화, VPN IaC 전환, 그리고 State 백업과 Lock 규칙까지 차근차근 도입해 보시기 바랍니다. 처음부터 모든 자원을 코드로 전환하려 하지 마시고, 가장 빈번하게 변경되는 Firewall Policy부터 시작하시는 것이 운영 리스크를 줄이는 가장 현실적인 방법입니다.
본 글의 내용은 작성자의 실무 경험을 바탕으로 정리된 일반적인 기술 가이드이며, 특정 환경에서의 적용 결과를 보장하지 않습니다. FortiGate 펌웨어 버전, fortios provider 버전, 그리고 조직의 보안 정책에 따라 권장 설정이 달라질 수 있으므로, 실제 운영 적용 전에는 반드시 검증 환경에서 충분한 테스트를 거치시기 바랍니다. 본문에 포함된 코드 예시는 학습 목적의 단순화된 형태이며, 프로덕션 환경에서는 secret 관리, 접근 제어, 백업 정책 등을 별도로 설계해야 합니다.