라우터에 로그인해 포럼 게시물에서 킬 스위치 규칙 세트를 붙여 넣으면, 터널이 끊기는 순간 LAN의 브라우징이 멈춘다. 이틀 뒤에도 라우터는 평범한 WAN으로 동적 DNS 레코드를 계속 갱신하고, 게스트 노트북은 여전히 IPv6 사이트에 접속한다. 두 행동 모두 정상이다. 붙여 넣은 규칙이 하나의 체인과 하나의 주소 체계만 덮었고, 그 어느 것도 라우터 전체가 아니기 때문이다. 패킷 경로를 차례로 따라가면 무엇이 빠졌는지 정확히 드러난다.

노트북이 아니라 라우터에서 시작하라

라우터 수준 정책은 소규모 사무실에 매력적이다. 그 뒤의 모든 장치가 같은 동작을 물려받고, 클라이언트 스위치를 켜는 것을 아무도 잊을 수 없기 때문이다. 펌웨어 업데이트 때마다 시험할 한 곳도 제공한다. 하지만 라우터 자체의 트래픽이나 두 번째 주소 체계를 자동으로 덮어 주지는 않으며, 대부분의 실패한 구축 뒤에는 이 두 가지 공백이 있다.

무엇이든 편집하기 전에 종이에 세 가지를 적어 두라. WAN 인터페이스 이름, LAN 브리지 이름, VPN 엔드포인트 주소와 포트다. 규칙에 세 가지가 모두 필요하며, 인터페이스 이름을 추측하는 것이 올바르게 보이는 규칙 세트가 아무 일도 하지 않는 가장 흔한 이유다.

패킷이 지날 수 있는 세 경로를 따라가라

노트북에서 인터넷으로 가는 트래픽은 포워딩되므로 FORWARD 체인을 지난다. 라우터 자체가 만드는 트래픽, 예컨대 DNS 포워딩, NTP 질의, DDNS 갱신, VPN 제어 채널은 OUTPUT을 지난다. 라우터로 향하는 관리 트래픽은 INPUT을 통해 들어온다.

따라서 FORWARD에만 만든 킬 스위치는 클라이언트만 보호하고 그 밖의 것은 보호하지 않는다. 도입부의 누출이 신비롭지 않고 예측 가능한 이유를 표가 보여 준다.

Packet pathExampleChain that sees it
LAN client to WAN or tunnelBrowser requestFORWARD
Router-originatedDDNS update, NTPOUTPUT
Addressed to the routerSSH from the LANINPUT

대부분의 설계에서 INPUT에 킬 스위치가 필요하지는 않지만, 터널이 내려간 동안 관리 접속은 계속 동작해야 한다. 그렇지 않으면 VPN이 처음 실패할 때 콘솔 케이블을 들고 사무실로 운전해 가게 된다.

터널이 여전히 올라올 수 있도록 규칙을 정렬하라

동작하는 킬 스위치는 거부로 끝나는 허용 목록이며, 그것을 벽돌이 아니라 킬 스위치로 만드는 것은 순서다. 먼저 루프백, VPN 엔드포인트, 확립된 흐름의 응답, 터널 인터페이스를 허용하고 나머지는 마지막에 거부하라.

iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -d 198.51.100.7 -p udp --dport 51820 -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -A OUTPUT -o eth0 -j REJECT

이 시점에서 엔드포인트는 호스트 이름이 아니라 주소여야 한다. 마지막 거부가 DNS를 막으면 공급자의 호스트 이름을 지정한 규칙은 더 이상 해석될 수 없고, 재부팅 후 터널이 다시는 시작되지 않는다. 공급자가 엔드포인트를 교체한다면 정책을 적용하기 전에 이름을 해석하고 같은 스크립트에서 그 규칙을 갱신하라.

클라이언트 트래픽에도 FORWARD에서 같은 형태를 그대로 따르고, 거부 위에 명시적 관리 규칙을 둔다. 그 관리 규칙은 좁게 유지하되 도달 가능하게 하고, 첫 SSH 세션을 닫기 전에 두 번째 세션을 열어 확인하라. 킬 스위치를 바꾸다 스스로 잠기는 일은 계획할 만큼 흔하다. WireGuard를 돌리는 라우터에서는 피어의 allowed-ips가 터널로 들어오는 것을 정하고, 이 규칙들은 터널 없이 무엇이 나가도 되는지를 정한다.

IPv6에는 자체 정문이 필요하다

위의 모든 것은 IPv4다. LAN에 동작하는 IPv6가 있고 터널이 IPv4만 나른다면, 덮인 모든 장치에는 새 규칙이 결코 보지 못하는 인터넷으로의 직접 경로가 여전히 있다. 듀얼 스택 노트북은 기꺼이 그것을 선호하며, IPv4로 시험하는 사람에게만 킬 스위치가 고장 난 것처럼 보인다.

ip6tables -A FORWARD -i br-lan -j REJECT
ip6tables -A OUTPUT -o eth0 -j REJECT
ip6tables -A OUTPUT -o lo -j ACCEPT

여기서도 순서가 중요하다. 루프백 허용이 WAN 거부보다 앞서야 하며, 그렇지 않으면 라우터의 로컬 서비스가 이상한 방식으로 실패하기 시작한다. 네트워크가 IPv6를 전혀 쓰지 않는다면 WAN과 LAN에서 비활성화하는 편이 더 단순하고 잊을 것도 남지 않는다.

일부 VPN 클라이언트는 IPv4용 기본 경로만 설치하고 기존 IPv6 기본 경로는 그대로 둔다. 클라이언트가 처리했다고 가정하지 말고 터널이 연결된 뒤 라우팅 테이블을 확인하라.

터널을 내린 상태로 증명하라

라우터에서 터널을 멈추고 다음 순서로 세 곳에서 시험하라.

# systemctl stop wg-quick@wg0
# ip route get 203.0.113.1
# curl -m 5 -sS http://example.com || echo blocked
# iptables -vnL OUTPUT | head
# ip6tables -vnL OUTPUT | head

경로 조회는 패킷이 어디로 갈지 알려 주고, 카운터는 규칙이 실제로 일치한 적이 있는지 알려 준다. 실패한 시험 뒤 패킷이 하나도 없는 규칙은 아무것도 보호하지 않으며, 흔한 원인은 입력한 것과 다른 인터페이스 이름이다. LAN 쪽 시험으로는 OUTPUT을 행사할 수 없으므로 라우터가 만든 트래픽을 명시적으로 시험하라.

필요해지기 전에 실패 모드를 정하라. fail-closed는 터널이 돌아올 때까지 클라이언트를 오프라인으로 유지하고, fail-open은 평범한 WAN 접속을 복원해 신경 쓴 트래픽에 대한 목적을 무력화한다. 메트릭이 높은 VPN 풀 쪽으로 향하는 블랙홀 경로는 fail-closed 설계에 유용한 두 번째 계층이다. 터널 경로가 사라질 때 기본 경로로 되돌아가지 않고 살아남기 때문이다.

LAN 클라이언트, 라우터가 만든 요청, IPv6 요청이 모두 터널이 내려간 상태에서 실패하고 터널이 올라오면 셋 모두 다시 동작할 때에만 구성을 받아들여라.

관련 글