로드 순서: tailscaled → Docker →
/etc/nftables.conf의존 객체: tailscale0, foxirain4, DOCKER-USER
검증: 실행 로그, nftables counter, tcpdump
Linux 기반 악성코드 분석 VM 격리망 · 5/5
ruleset 의존 객체시작 순서 수정start_firewall.sh원격 실행 방식실행 로그정책 로드 전 확인구문 및 ruleset 검증주소 및 route 검증허용·차단 테스트Tailnet → WindowsWindows → Linux 호스트Windows → WANWindows → Docker gdbservernftables counter 확인tcpdump 패킷 경로 확인service listener 확인재시작 후 검증 체크리스트정리Sources작업 기록공식 문서시리즈 이동
ruleset 의존 객체
최종 ruleset에는 tailscaled, dockerd,
/etc/nftables.conf가 생성하는 객체가 함께 포함된다.- tailscaled가 tailscale0와 ts-input·ts-forward 규칙을 만든다.
- dockerd가 Docker bridge, NAT, FORWARD, DOCKER-USER 계열 chain을 만든다.
- 마지막에
/etc/nftables.conf가 Docker chain을 정리하고 native inet filter를 추가한다.
ruleset flush 직후에는 tailscale0, Docker bridge, DOCKER-USER가 존재하지 않을 수 있다. Docker를 nftables 정책 로드 뒤에 재시작하면 DOCKER-USER가 다시 생성되어 packet mark 규칙이 변경된다.
적용 순서는 다음과 같다.
1. 기존 ruleset flush 2. tailscaled restart 3. tailscale0와 Tailscale chain 생성 대기 4. docker restart 5. Docker bridge와 DOCKER-USER 생성 대기 6. /etc/nftables.conf 로드
이 과정은 에서 시작해 으로 정리했다.
시작 순서 수정
초기에는 다음 순서를 사용했다.
sudo nft flush ruleset sudo systemctl restart tailscaled.service sleep 5 sudo nft -f /etc/nftables.conf sudo systemctl restart docker
/etc/nftables.conf는 DOCKER-USER를 flush한 뒤 packet mark 규칙을 추가한다. 따라서 Docker가 DOCKER-USER를 생성한 다음 nftables 파일을 로드하도록 순서를 변경했다.start_firewall.sh
#!/bin/bash timestamp=$(date '+%Y%m%d-%H%M%S') logfile="start-${timestamp}.log" log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$logfile" } log "INFO: 초기화 스크립트 시작 (로그 파일: $logfile)" # 1. 기존 ruleset 초기화 if sudo nft flush ruleset; then log "INFO: nft ruleset flush 성공" else log "ERROR: nft ruleset flush 실패" exit 1 fi # 2. Tailscale chain과 interface 생성 if sudo systemctl restart tailscaled.service; then log "INFO: tailscaled 재시작 성공" else log "ERROR: tailscaled 재시작 실패" exit 1 fi sleep 5 log "INFO: 5초 대기 완료" # 3. Docker chain과 bridge 생성 if sudo systemctl restart docker; then log "INFO: docker 재시작 성공" else log "ERROR: docker 재시작 실패" exit 1 fi sleep 5 # 4. 최종 정책 로드 if sudo nft -f /etc/nftables.conf; then log "INFO: /etc/nftables.conf 로드 성공" else log "ERROR: /etc/nftables.conf 로드 실패" exit 1 fi log "INFO: 모든 작업 완료"
각 명령의 종료 상태를 확인하고 실패 시
exit 1로 종료한다. timestamp별 로그 파일에 완료된 단계를 기록한다.원격 실행 방식
sudo nohup ./start_firewall.sh &
스크립트는 nft ruleset을 flush하고 tailscaled를 재시작한다. Tailscale SSH 세션에서 foreground로 실행하면 관리 연결이 끊기면서 프로세스가 종료될 수 있다.
nohup으로 프로세스를 shell session에서 분리한다. Tailscale 연결이 재설정되는 동안에도 Docker restart와 nftables load를 계속 실행한다.실행 로그
2025-10-05 18:42:08 INFO: 초기화 스크립트 시작 2025-10-05 18:42:08 INFO: nft ruleset flush 성공 2025-10-05 18:42:09 INFO: tailscaled 재시작 성공 2025-10-05 18:42:14 INFO: 5초 대기 완료 2025-10-05 18:42:26 INFO: docker 재시작 성공 2025-10-05 18:42:31 INFO: /etc/nftables.conf 로드 성공 2025-10-05 18:42:31 INFO: 모든 작업 완료
이 로그로 ruleset flush, tailscaled·Docker 재시작, 최종 정책 로드가 서버에서 순서대로 완료됐는지 확인했다.
정책 로드 전 확인
systemctl is-active tailscaled systemctl is-active docker ip link show tailscale0 ip link show enx00e04c637a20 ip link show foxirain4 sudo nft list chain ip filter DOCKER-USER
확인 조건은 다음과 같다.
- tailscaled와 docker가 active다.
- tailscale0가 존재한다.
- USB Ethernet인 enx00e04c637a20가 존재한다.
- Docker network가 만들어져 foxirain4가 존재한다.
- ip filter의 DOCKER-USER chain이 존재한다.
최종 파일은
flush chain ip filter DOCKER-USER를 실행하므로 이 chain이 없으면 로드가 실패한다. 또한 iif $IF_INTERNAL처럼 interface index 기반 이름 해석을 사용하는 규칙은 interface가 존재해야 안정적으로 로드된다.구문 및 ruleset 검증
sudo nft -c -f /etc/nftables.conf sudo nft -f /etc/nftables.conf sudo nft list ruleset -a
-c는 규칙을 적용하지 않고 구문과 참조를 검사한다. 다만 DOCKER-USER 같은 기존 객체를 참조하므로 단순 문법뿐 아니라 현재 동적 chain 상태도 영향을 준다.전체 ruleset에서는 다음을 확인한다.
- Tailscale chain이 존재하는가
- Docker NAT·FORWARD chain이 존재하는가
- DOCKER-USER에 mark 규칙이 들어갔는가
- inet filter input policy가 drop인가
- inet filter forward policy가 drop인가
- FWD-STATE → FWD-DOCKER → FWD-TS → FWD-VM 순서인가
주소 및 route 검증
ip -br address ip route sysctl net.ipv4.ip_forward tailscale status sudo tailscale debug prefs docker network inspect foxirain4_net
기대 상태는 다음과 같다.
항목 | 기대값 |
분석 NIC | enx00e04c637a20 · 10.10.0.10/24 |
Windows VM | 10.10.0.20 |
Docker bridge | foxirain4 · 10.20.0.1/24 |
IPv4 forwarding | 1 |
Tailscale advertise route | 10.10.0.0/24 |
Docker subnet | 10.20.0.0/24 |
허용·차단 테스트
허용 규칙과 기본 DROP을 각각 확인하기 위해 성공해야 하는 연결과 실패해야 하는 연결을 함께 테스트한다.
Tailnet → Windows
# MacBook nc -vz 10.10.0.20 22
- SSH 22: 허용
- Sunshine/Moonlight 포트: 허용
- 임의의 테스트 포트: 차단
Windows → Linux 호스트
Test-NetConnection 10.10.0.10 -Port 22
분석 NIC에서 Linux 호스트로 시작하는 신규 연결은 INPUT internal drop counter를 증가시켜야 한다.
Windows → WAN
Test-NetConnection 1.1.1.1 -Port 443
Linux를 경유한 일반 WAN 연결은 FWD-VM에서 차단되어야 한다.
Windows → Docker gdbserver
Test-NetConnection 10.20.0.10 -Port 31234
gdbserver가 실제로 listen 중일 때만 성공해야 한다. 다른 destination port는 차단되어야 한다.
nftables counter 확인
sudo nft list chain inet filter IN-STATE sudo nft list chain inet filter FWD-STATE sudo nft list chain inet filter FWD-DOCKER sudo nft list chain inet filter FWD-TS sudo nft list chain inet filter FWD-VM
테스트 전후의 packet·byte counter를 비교해 패킷이 일치한 rule을 확인한다.
테스트 | 증가해야 하는 counter |
MacBook → Sunshine 신규 연결 | FWD-TS TS→VM |
Sunshine 정상 응답 | FWD-STATE established |
Windows → Linux 신규 연결 | INPUT internal drop |
Windows → WAN | FWD-VM drop |
Windows → TCP 31234 | FWD-DOCKER gdbserver |
tcpdump 패킷 경로 확인
sudo tcpdump -ni tailscale0 host 10.10.0.20 sudo tcpdump -ni enx00e04c637a20 host 10.10.0.20 sudo tcpdump -ni enx00e04c637a20 tcp port 31234 sudo tcpdump -ni foxirain4 tcp port 31234
같은 연결이 ingress와 egress 인터페이스에 모두 나타나면 Linux FORWARD를 통과한 것이다. 한쪽에만 나타나면 route, firewall verdict, service listener 순서로 확인한다.
service listener 확인
sudo ss -lntup docker ps --format 'table {{.Names}} {{.Ports}}' docker exec my_container ss -lntp
방화벽이 허용해도 destination에서 서비스가 listen하지 않으면 연결은 성공하지 않는다. 특히 31234는 컨테이너 Config에서 자동 실행되지 않으므로 gdbserver를 별도로 실행했는지 확인해야 한다.
재시작 후 검증 체크리스트
tailscaled가 먼저 올라왔는가
Docker가 foxirain4와 DOCKER-USER를 만들었는가
전용 USB NIC가 연결되어 있는가
nftables 파일이 오류 없이 로드됐는가
input·forward policy가 drop인가
Tailnet → Windows 허용 counter가 증가하는가
Windows → Linux·WAN 차단 counter가 증가하는가
Windows → 10.20.0.10:31234만 필요한 시점에 열리는가
재부팅 후에도 route와 Docker 고정 IP가 유지되는가
성공 로그가 남았는가
정리
start_firewall.sh는 ruleset flush, tailscaled restart, Docker restart, nftables load 순서로 실행된다. 각 단계의 종료 상태와 실행 시각은 로그 파일에 기록된다.허용·차단 테스트 후 nftables counter를 비교한다. tailscale0·전용 NIC와 전용 NIC·foxirain4에서 tcpdump를 실행해 ingress와 egress 패킷을 확인한다.