터널을 통해 웹사이트를 게시하는 것과 사용자에게 VPN 접속을 주는 것은 같은 케이블을 공유하는 서로 다른 두 가지 배치다. 전자에서는 공개 리버스 프록시가 방문자의 요청을 받아 터널 너머로 아무도 직접 도달할 수 없는 오리진에 전달한다. 후자에서는 애플리케이션이 아예 도달 가능해지기 전에 사용자가 VPN에 가입해야 한다. 이 두 설명을 뒤섞는 것이 프라이빗 백엔드가 결국 공개적으로 노출되는 경로다.

다이어그램에서는 비슷해 보이는 두 모델

무엇을 고르기 전에 화살표를 그려라. 공개 게시 모델은 브라우저에서 프록시로 HTTPS를 거쳐, 그다음 프록시에서 오리진으로 터널을 거쳐 가며, 오리진은 인터넷에서 오는 연결을 결코 받지 않는다. VPN 전용 모델은 클라이언트에서 게이트웨이로 가며, 애플리케이션은 그 경로 내부에서만 도달할 수 있다. 사용자의 노트북은 애플리케이션과 같은 네트워크에 있으며, 그것이 그 모델의 요점 전부다.

모델은 누가 인증하고 어디서 인증하는지에서 다르다. 전자에서는 프록시가 공개 진입점이며 그에 구성된 정책에 따라 도착하는 모든 요청을, 적대적인 요청까지도 중계한다. 후자에서는 도달 가능성 자체가 관문이며, 이는 거칠지만 실제 허용이다.

TLS가 멈추는 지점, 그리고 그것이 실제로 증명하는 것

브라우저와 프록시 사이의 TLS는 그 구간을 보호하고 브라우저가 방문하려던 사이트를 검증할 수 있게 한다. 프록시가 TLS를 종료하면 요청은 전달되기 전에 그 프로세스 내부에 읽을 수 있는 형태로 존재한다. 오리진으로 가는 두 번째 TLS 세션은 다음 구간을 보호하고 애플리케이션 엔드포인트를 따로 검증할 수 있다. 어느 세션도 어떤 사람이 애플리케이션을 사용할 수 있는지 결정하지 않는다. 프록시의 와일드카드 인증서도 그 두 번째 구간에는 미치지 않는다.

이것이 사람들이 암호화라는 단어로 뭉뚱그리는 부분이다. 기밀성, 무결성, 서버 신원은 전송 계층의 속성이고, 인가는 애플리케이션의 결정이다. 터널 뒤의 프라이빗 주소는 누가 소켓에 도달할 수 있는지를 바꾸지, 도달한 뒤 누가 로그인할 수 있는지를 바꾸지 않는다.

전달된 헤더는 프록시에서 온 것만 신뢰하라

클라이언트 주소로 로그를 남기거나 인가하는 애플리케이션은 보통 프록시가 추가하는 헤더를 읽는다. 그 헤더는 그저 텍스트이므로 오리진에 직접 도달할 수 있는 무엇이든 원하는 주소를 주장할 수 있다. 오리진을 제한해 프록시의 터널 주소만 접속할 수 있게 하고, 애플리케이션이 그 주소에서 오는 헤더만 받아들이도록 구성하라.

curl -s -o /dev/null -w '%{http_code}
' -H 'X-Forwarded-For: 203.0.113.9' http://172.20.5.40/

그 요청이 프록시가 아닌 호스트에서 성공한다면, 오리진은 무시해야 할 헤더를 신뢰하고 있는 것이다. 애플리케이션을 건드리기 전에 리스너를 고쳐라. 거부가 위조된 주소를 담은 로그 줄보다 훨씬 나은 결과다.

오리진은 정확히 한 곳에서만 도달 가능하게 유지하라

오리진의 리스너를 터널 주소에 바인딩하거나 프록시 쪽으로 방화벽으로 막고, 그다음 실제로 무엇이 수신 중인지 확인하라. 와일드카드 바인딩으로 시작한 서비스는 호스트가 가진 모든 인터페이스에서 연결을 받는다. 라우팅 가능한 줄 잊고 있던 인터페이스까지 포함해서다. 수신 테이블이 근본 진실이며, 적용하려던 구성 파일이 아니다. 와일드카드 바인딩이 의도된 것이라면, 그것을 좁히는 구성 요소는 여전히 방화벽이어야 한다.

ss -ltnp | grep -E ':(80|443)'

관리 인터페이스는 우발적 노출의 단골이므로 같은 검토를 하라. 그다음 두 모델을 따로 적어라. 프록시를 통한 공개 사이트, 그리고 터널을 통해서만 도달할 수 있는 관리 인터페이스다. 서버가 VPN 뒤에 있다는 문장은 그중 어느 것도 말하지 않는다.

관련 글