NAT 뒤의 OpenVPN 클라이언트가 인터넷 출구가 되는 방법
출구가 스스로 바깥으로 연결하므로 그 라우터에는 인바운드 포트가 필요 없습니다. 허브가 무엇을 라우팅하고 출구가 무엇을 전달하며 반환 경로가 어디서 오는지 살펴봅니다.
리눅스 호스트 세 대, 하나의 설계: 공개적으로 도달 가능한 OpenVPN 허브, 소비자 노트북, 그리고 안쪽으로 아무것도 전달하지 않는 평범한 가정용 라우터 뒤의 출구 머신. 출구가 스스로 허브로 연결한다. 그러면 소비자의 인터넷 트래픽은 노트북에서 터널로 들어가 출구 자체의 업링크로 나간다. 이 구성에서 놀라운 점은 모두 하나의 세부에서 나온다. 연결을 시작한 쪽이 출구이므로 안쪽으로 가는 경로가 이미 존재하고, 그것을 만들기 위해 포트 포워딩이 필요 없다.
다음은 구성 순서가 아니라 패킷 경로를 따라간다. 구성은 어느 구성 요소가 어느 결정을 소유하는지 알아야 비로소 의미가 있기 때문이다. 각 계층은 정확히 하나의 결정을 소유하고, 실수는 두 계층을 하나로 가정하는 지점에 몰린다.
두 개의 전송 세션, 하나의 내부 경로
각 호스트는 허브로 가는 자신의 외부 연결을 유지하며, 어느 연결도 다른 연결을 알지 못한다. 소비자의 인터넷 트래픽은 별도의 내부 흐름으로, 소비자의 터널 안에서 운반된 뒤 출구의 터널에서 다시 나간다. 허브가 라우팅해야 하는 것이 이 내부 흐름이고, 출구가 전달해야 하는 것도 이것이다.
outer: consumer --TLS--> hub
outer: exit --TLS--> hub
inner: consumer --tun--> hub --internal--> exit --tun--> uplink
두 외부 연결 모두 클라이언트 쪽에서 바깥으로 나가므로, 출구의 라우터는 인바운드 SYN을 결코 보지 않고 목적지 NAT 규칙도 결코 필요로 하지 않는다. 확립된 전송을 살아 있게 유지하기만 하면 되고, 그것은 오래 지속되는 연결에 대해 이미 하는 일이다. 공용 인터넷의 무엇도 먼저 출구에 도달할 필요가 없으므로 그 업링크의 동적 주소는 문제가 아니다. 라우터는 여전히 아웃바운드 세션을 허용해야 하지만, 그것은 브라우저가 이미 쓰는 것과 같은 허용이다.
iroute가 서버 안에 실제로 설치하는 것
허브는 어느 피어가 어느 목적지를 소유하는지 결정하는 유일한 장치이며, 그 결정을 커널이 아니라 자체 사용자 공간 라우팅 테이블에서 내린다. 출구에 대한 exit-node 이름의 client-config-dir 항목은 거기서 기본 접두사를 주장할 수 있다:
# /etc/openvpn/server/ccd/exit-node
iroute 0.0.0.0 0.0.0.0
그 줄은 서버 프로세스에 "어떤 목적지든 내부 패킷은 이 클라이언트의 것"이라고 알린다. 허브의 운영 체제에 기본 경로를 추가하지 않고, 허브를 인터넷 게이트웨이로 만들지도 않는다. OpenVPN 2.6 참조 는 클라이언트별 내부 라우팅과 커널 라우팅을 별개의 메커니즘으로 유지하며, 이 분리가 허브의 기본 경로를 건드리지 않고 이 설계가 동작하는 이유다.
소비자에게는 거울 같은 지시가 필요하고 push로 전달되어야 하며, 그래야 자기 트래픽이 로컬 게이트웨이로 나가지 않고 터널로 들어간다. 서브넷 토폴로지에서 풀은 출구의 tun 장치에 연결된 경로이므로 반환 방향에는 추가 push가 필요 없다. 출구가 받아서는 안 되는 것은 아래의 리다이렉션이며, 그것은 사용하려는 터널로 자기 업링크 트래픽을 되돌려 보낸다. 소비자에게만 보내고, CCD 파일이 선택되었다고 믿는 대신 클라이언트에서 적용된 경로를 읽어라.
# /etc/openvpn/server/ccd/consumer-01
push "redirect-gateway def1"
전달은 출구의 커널이 한다
출구 안에서 소비자의 패킷이 tun 인터페이스로 도착해 어느 인터넷 호스트를 향한다. 커널은 라우팅 테이블을 조회해 최적 일치로 실제 업링크를 찾고, 그다음 두 가지 독립 정책을 적용한다. 이 패킷을 전혀 전달할 수 있는지, 그리고 그 인터페이스로 그 출발지 주소를 가지고 나갈 수 있는지다. 전달은 sysctl이며 많은 배포판에서 기본적으로 꺼져 있다.
따라서 출구에는 지속적으로 켜진 IPv4 전달, 소비자 터널 출발지를 업링크로 허용하는 규칙, tun을 통한 응답 경로가 필요하다. 호스트 전체에 전달을 켜면 그를 통해 라우팅되는 다른 모든 것의 동작도 바뀌므로, 규칙은 모든 것을 받아들이는 대신 출발지와 인터페이스를 지목해야 한다.
반환 경로를 사는 것은 출발지 변환이다
인터넷 호스트에는 10.90.0.x 주소로 가는 경로가 없으므로 응답은 출구에 도달하기 전에 버려진다. 패킷이 업링크를 떠날 때 출발지를 다시 쓰면 그것이 해결되고, 연결 추적이 돌아오는 길에 그 재작성을 되돌린다.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.90.0.0/24 -o enp1s0 -j MASQUERADE
ip -4 route get 10.90.0.9 # run on the exit: must point at the tun
위 규칙은 업링크 인터페이스 하나를 위한 출발점이지 방화벽 정책이 아니다. 출발지를 실제로 승인한 할당으로 좁히고, 출구 자체의 입력 정책을 분리해 두어야 터널 트래픽용 매스커레이드가 조용히 로컬 서비스를 노출하지 않는다. 업링크 주소가 동적인 호스트에서는 매스커레이드가 주소를 직접 추적하는 수고도 덜어 준다.
허브가 강제할 수 있는 곳과 없는 곳
client-to-client 와 전통적인 사용자 공간 데이터 경로에서는 허브가 서버 프로세스 내부에서 피어 사이로 내부 패킷을 넘긴다. 그것들은 허브 커널의 FORWARD 체인을 결코 지나지 않으므로, 그곳의 커널 규칙은 아무리 주의 깊게 작성해도 피어 간 통과를 제한할 수 없다. 강제는 서버 자체의 정책에 있어야 한다. 누가 연결할 수 있는지, 어떤 CCD 파일을 받는지, 출구의 기본 소유권을 아예 부여하는지다.
그래서 기본 iroute는 폭넓은 신뢰 결정이다. 출구는 임의의 인터넷 주소에서 오는 반환 트래픽을 정당하게 받아들여야 하고, 당신이 통제하고 패치하고 감시하는 머신이어야 한다. 같은 소유권을 라우터를 맡기지 않을 피어에게 주는 것이 이 설계가 초대하는 실수다.
멤버십이 진짜 통제다. 자기 경로를 구성할 수 있는 클라이언트는 출구를 사용하려 시도할 수 있으므로, 누가 연결할 수 있는지 또는 출구가 어떤 피어를 섬길지를 제한하는 것은 설계의 일부이지 나중 생각이 아니다.
출구가 사라질 때 안전하게 실패하기
출구를 오프라인으로 만들면 내부 기본 소유권이 그 세션과 함께 사라진다. 일어나서는 안 되는 것은 허브가 조용히 대체 출구가 되는 것이다. VPN 풀에서 허브 업링크로 가는 기본 경로도, 같은 트래픽을 기꺼이 전달할 넓은 매스커레이드 규칙도 허브에 두지 않는다. 그 실패를 의도적으로 시험하고, 그다음 복구는 이미 열려 있던 페이지가 아니라 새 흐름으로 시험하라.
# on the consumer, with the exit up and then down
ip -4 route get 1.1.1.1
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com
출구가 사라진 뒤에도 소비자 트래픽이 인터넷에 도달한다면 다른 무언가가 그것을 나르고 있으며 의도한 경계는 존재하지 않는다. DNS와 IPv6도 별개의 결정으로 다뤄라. IPv4 경로와 매스커레이드는 AAAA 트래픽에 대해 아무것도 말하지 않으며, 허브가 밀어 넣은 리졸버는 질의가 실제로 나가는 곳에서 답한다.