전체 터널 VPN이 올라온 뒤에도 호스트는 SSH에 계속 응답하지만, 같은 머신에서 Docker로 게시된 서비스는 조용해진다. 리스너에는 아무 문제가 없다. 컨테이너는 실행 중이고, 포트 매핑은 존재하며, 패킷은 도착한다. 컨테이너는 정상이고 요청은 실제로 그곳에 닿는다. 다른 점은 Linux가 경로를 선택할 때 응답이 어디서 시작하느냐이며, 그 차이가 아래 두 설계 중 무엇이 필요한지를 결정한다.

목적지 NAT를 통해 튜플을 따라가기

호스트 192.0.2.10 가 TCP 2222를 172.30.240.5:22 의 컨테이너로 게시한다고 하자. 198.51.100.25 에서 온 요청은 컨테이너에 도달하기 전에 변환되고, 컨테이너는 자기 자신으로 응답한다.

WAN request:      198.51.100.25 -> 192.0.2.10:2222
after DNAT:       198.51.100.25 -> 172.30.240.5:22
container reply:  172.30.240.5:22 -> 198.51.100.25

호스트의 경로 결정 시점에서 응답은 여전히 컨테이너의 출발지 주소를 지닌다. 역변환은 나중에 게시된 튜플을 복원하므로, 경로가 선택되는 바로 그 순간 패킷이 잘못되어 보인다.

호스트 전용 규칙이 이를 놓치는 이유

WAN 테이블을 192.0.2.10 에서 온 패킷에만 선택하는 규칙은 호스트 자체의 데몬을 포함하지만, 이 전달된 응답은 완전히 놓친다. 응답의 출발지 주소는 172.30.240.5 이며 호스트가 아니다.

과도한 수정은 컨테이너의 모든 것을 WAN으로 라우팅하는 것인데, 이는 컨테이너의 무관한 아웃바운드 연결까지 VPN에서 제외한다. 그것은 요청받은 정책과 다른 것이며, 보통 나중에 누군가를 놀라게 한다.

DNAT 이후 매칭은 다른 훅이다

Docker의 방화벽 문서 는 일반 사용자 필터링은 DNAT 이후의 목적지를 본다 고 언급한다. 따라서 일반 규칙에서 게시된 포트 2222를 매칭하는 것은 컨테이너 포트 22를 매칭하는 것과 같지 않다. 원래 튜플이 중요하다면 어떤 포트가 보이는지 추측하지 말고 지원되는 원래 목적지 메커니즘을 사용하라.

이 훅 혼동은 올바르게 보이지만 결코 일치하지 않는 규칙을 만든다. 정책을 다시 쓰기 전에 규칙 자체의 카운터를 확인하라. 히트가 없는 규칙은 패킷이 결코 도달하지 않는 곳에서 평가되고 있음을 알려준다.

설계 A: 결정을 연결 상태로 운반하기

연결 마크는 인그레스 결정을 변환 너머로 운반할 수 있지만, 중요한 경로 조회 전에 패킷으로 복원되어야 한다. 단계는 다음과 같다.

  1. Docker, VPN 또는 다른 정책 소유자와 충돌하지 않는 마크 필드와 규칙 우선순위를 확보하라.
  2. 연결 추적이 가능한 DNAT 이전 지점에서 의도된 새 WAN 연결만 식별하라.
  3. 원래 요청 자체를 WAN 테이블로 강제하지 않고 결정을 연결 마크에 기록하라.
  4. 응답 방향에서는 라우팅 전에 자신의 마크 비트만 패킷으로 복원하라.
  5. VPN 규칙이 먼저 이길 수 없도록 완전한 WAN 테이블을 충분히 일찍 선택하라.

그 테이블에는 실제 연결된 경로와 게이트웨이가 필요하다. 경로가 없을 때의 정의된 동작도 필요하다. 터널로 조용히 빠지는 것이 최악의 결과인데, 구성은 여전히 동작하는 것처럼 보이기 때문이다. 대신 닫힌 상태로 실패하고 기록하라.

설계 B: 호스트에서 종료하기

컨테이너 앞에 TCP 프록시나 리버스 프록시 같은 호스트 측 프런트엔드를 의도적으로 구성하면, 응답은 호스트 주소에서 시작된 채로 유지된다. 그러면 호스트를 위해 작성된 규칙이 예상대로 동작하고, 컨테이너는 사설 네트워크에 머문다.

비용은 자체 구성, 출발지 주소 처리, 접근 제어를 갖춘 또 하나의 움직이는 부품이다. 보통 더 단순한 지원 경계이며, 떠날 수도 있는 한 사람이 라우팅 정책을 유지해야 한다면 종종 정답이 된다.

의도를 검증하지 말고 변경을 검증하라

공용 측에서 새로 승인된 연결을 열고 응답이 의도한 인터페이스로 나가는지 확인하라. 그다음 컨테이너가 시작한 무관한 요청이 여전히 VPN을 따르는지 확인하라.

규칙 하나를 시험하려고 머신의 연결 테이블을 플러시하지 마라. 기존 항목은 이전 매핑을 유지하며, 플러시는 호스트의 다른 모든 것을 깨뜨린다. 포트를 게시하는 것은 도달 가능성을 정의한다. 응답 경로를 정의하지는 않는다. 선택한 설계를 테이블 이름과 함께 적어 두어, 다음 사람이 장애 중에 그것을 다시 발견하지 않도록 하라.

관련 글