Port-Share가 모든 웹사이트 방문자를 localhost처럼 보이게 하는 이유
백엔드는 전달 홉을 보고 브라우저를 보지 않습니다. 소스 주소가 어디서 사라지는지 확인한 다음 어떤 구성 요소가 이를 보증할 수 있는지 결정하세요.
접속 로그의 모든 줄이 동일한 loopback 주소를 보여 주는데, 페이지 자체는 로드되고 인증서도 올바르게 검증됩니다. 접근 불가라는 의미에서는 아무것도 망가지지 않았고, 그래서 이 상황이 몇 달씩 이어집니다. 망가진 것은 귀속입니다. 웹 백엔드는 연결을 전달한 홉의 주소를 기록하고, 그 로그를 신뢰한 모든 후속 결정은 같은 잘못된 답을 물려받았습니다.
아래 검사는 케이블에서 안쪽으로 진행됩니다. 보이는 값을 설명하는 첫 검사에서 멈추세요. 이후의 각 단계는 앞선 단계가 이미 참이라고 가정하기 때문입니다. 어느 것도 프런트엔드 구성을 추측할 것을 요구하지 않습니다.
로그 줄이 아니라 두 연결을 따라가세요
Port-Share는 하나의 TCP 리스너에서 두 프로토콜을 다중화합니다. OpenVPN은 들어오는 연결의 첫 바이트를 검사하고, 자체 프로토콜처럼 보이면 그 연결을 유지하며, 나머지는 모두 두 번째 목적지로 넘깁니다. 그 목적지는 보통 같은 호스트의 웹 서버이므로, 웹 서버가 받아들인 소켓의 상대는 OpenVPN 프로세스입니다.
ss -ltnp 'sport = :443'
ss -tnp 'dport = :8080'
첫 명령은 공용 리스너를 누가 소유하는지 보여 줍니다. 두 번째는 백엔드 포트로 도착하는 연결을 보여 주며, 그 상대 주소가 바로 접속 로그에 찍히는 값입니다. 그 상대가 loopback이라면 로그는 정확하고 정보는 한 계층 앞에서 사라진 것입니다. 로그 필드 이름을 바꾸거나 클라이언트 IP 모듈을 켜도 연결이 애초에 담지 않았던 주소는 돌아오지 않습니다.
이 검사가 나머지 모든 것을 결정합니다. 백엔드의 상대가 브라우저 주소라면 포트 공유는 문제가 아니고 로그 형식이 문제입니다.
전송 포워딩은 헤더 주입이 아닙니다
포워더는 바이트를 옮깁니다. HTTP를 파싱하지 않으므로 요청으로 읽지 않은 요청에 헤더를 삽입할 수 없고, 건드리지 않고 통과시키는 TLS 스트림에 무엇도 더할 수 없습니다. 프록시 헤더를 기대하는 모듈은 어떤 구성 요소도 만들지 않는 헤더를 계속 기다릴 것입니다.
원래 주소를 실을 수 있는 메커니즘은 두 가지이며 둘 다 양쪽 끝의 합의가 필요합니다. PROXY 프로토콜은 연결 앞에 작은 헤더를 덧붙이므로 백엔드가 이를 기대하도록 구성되어야 합니다. 기대하지 않는 백엔드는 그 바이트를 요청의 시작으로 처리하고 혼란스러운 방식으로 실패합니다. X-Forwarded-For 같은 신뢰할 수 있는 프록시 헤더는 HTTP를 이해하는 프록시가 이를 설정하고 어떤 상대가 이를 주장할 수 있는지 백엔드에 알려 줄 때 작동합니다.
둘 다 자동이 아니며 둘 다 기본적으로 안전하지 않습니다. 들어오는 전달 헤더를 무엇이든 믿는 백엔드는 클라이언트가 자기 주소를 고르게 하며, 이는 로그의 부정확성을 접근 제어 우회로 바꿔 놓습니다. 운영 경로에서 어떤 헤더든 신뢰하기 전에 스테이징 호스트에서 한 번 시연하세요.
curl -sS -H 'X-Forwarded-For: 127.0.0.1' https://site.example.com/health
grep -F '127.0.0.1' /var/log/nginx/access.log | tail -n 3
선택적 소스 매핑 디렉터리
포트 공유에는 기능이 구현된 플랫폼에서 원래 소스 주소와 포워딩 연결 사이의 매핑을 기록하는 선택적 디렉터리 인자가 있습니다. 웹 측이 그 매핑을 참조할 것으로 기대됩니다. 그것은 헤더가 아니며 요청에 주입되지도 않습니다.
그 설계를 알아 둘 가치가 있는 이유는, 겉보기에 같은 프런트엔드 구성인데도 어떤 배포는 실제 주소를 보고하고 어떤 배포는 그렇지 않은지를 설명하기 때문입니다. 차이는 받는 쪽에 있으며 보통 패키지된 웹 서버 대부분이 기본으로 활성화하지 않는 모듈이나 패치에 있습니다. 웹 서버 문서에서 제공하는 정확한 훅을 읽고, 모듈이 로드되었다고 가정하지 말고 확인하세요.
이 경로를 택한다면 두 반쪽으로 된 통합으로 다루세요. 포워더가 쓰는 매핑과 백엔드가 수행하는 조회입니다. 둘 다 검증하고, 디렉터리가 재시작을 견디며 바쁜 리스너에서 무한정 커지지 않는지 확인하세요.
어떤 구성 요소가 주소를 보증하는지 결정하세요
구성을 바꾸기 전에 클라이언트 주소를 주장할 수 있는 구성 요소와 그것을 믿을 수 있는 구성 요소를 지목하세요. 답을 하나 적어 두면 세 계층이 각각 다른 출처를 신뢰하는 흔한 표류를 막을 수 있습니다.
세 가지 설계로 대부분의 요구를 덮습니다. 공용 리스너와 웹 리스너를 분리해 두면 웹 서버가 실제 상대를 보게 되어 귀속 문제가 존재하지 않습니다. HTTP를 이해하는 리버스 프록시를 앞에 두고 거기서 요청을 종료하거나 파싱한 뒤, 그 프록시만 신뢰하는 백엔드를 위해 전달 헤더를 설정하게 하세요. 또는 포트 공유를 유지하고 로그에 홉이 보인다는 점을 받아들이되, 접근 규칙은 주소가 아니라 애플리케이션 신원에 대해 작성하세요.
작동하지 않는 것은 전달된 loopback 주소를 요청이 이 머신에서 발생했다는 증거로 취급하는 것입니다. 그 추론은 정확히 반대입니다. 주소가 존재하는 것은 바로 로컬 구성 요소가 연결을 만들었기 때문입니다. 그렇게 작성된 규칙은 리스너에 닿을 수 있는 누구든 받아들입니다.
페이지가 아니라 규칙을 시험하세요
로드되는 페이지는 포워딩 경로가 작동함을 증명할 뿐 귀속에 대해서는 아무것도 말하지 않습니다. 주소의 소비자를 시험하세요. 접근 규칙, 속도 제한, 감사 기록, 지리 조회입니다.
curl -sS -o /dev/null -w '%{http_code}\n' https://site.example.com/
tail -n 5 /var/log/nginx/access.log
grep -c '' /var/log/nginx/access.log
허용된 외부 요청을 프런트엔드와 백엔드 양쪽 기록과 비교하고, 거부해야 할 규칙이 여전히 거부하며 허용해야 할 규칙이 여전히 허용하는지 확인하세요. 로그 줄은 맞는데 규칙이 여전히 모든 사람을 로컬로 취급한다면, 그 규칙은 고친 필드와 다른 필드를 읽고 있습니다.
마지막으로 귀속 사슬을 배포 노트에 적으세요. 어떤 리스너, 어떤 포워더, 어떤 헤더나 매핑, 그리고 어떤 백엔드가 그것을 신뢰하도록 구성되었는지입니다. 다음에 로그 줄을 읽는 사람에게는 네 가지가 모두 필요하며, 그 메모가 없는 것이 한 해 뒤 같은 티켓이 다시 열리는 이유입니다. 채팅 스레드가 아니라 구성 옆에 두세요.