사용자는 VPN을 끊고 내부 사이트에서 403을 만나 프록시가 고장 났다고 보고하는데, 그 페이지를 만들 수 있는 것은 세 가지다. 오리진이 요청을 거부했거나, 포워드 프록시가 터널 열기를 거부했거나, 프록시가 자체 자격 증명을 요구한 경우다. 각각은 다른 질문에 대한 정답이며, 이를 하나의 일반 장애 페이지로 뭉개면 가진 유일한 증거가 사라진다. 거부를 만든 구성 요소별로 분류한 다음, 각 구성 요소가 실제로 사용하는 네트워크 경로를 비교하라.

403을 만든 구성 요소의 이름을 밝혀라

HTTP 상태 코드는 그것을 보낸 쪽의 것이다. 오리진이 403을 반환할 때는 요청을 해석하고 호출자가 리소스를 갖지 못할 수 있다고 판단한 것이며, 이는 전송 문제가 아니라 인가에 대한 답이다. 포워드 프록시가 CONNECT에 403으로 응답할 때는 터널 구축 자체를 거부한 것이고 상류에는 아무것도 접촉하지 않았다. 407로 응답할 때는 자체 자격 증명을 원하는 것이며, 이는 애플리케이션 로그인과 아무 관련이 없다.

What you observeWhat it means
Origin replies 403The origin understood the request and denied it
Proxy refuses CONNECT with 403The proxy declined to open that tunnel
Proxy replies 407Proxy authentication is required first
Gateway replies 502 or 504Upstream failure or timeout, not a permission decision

RFC 9110은 프록시 고유의 챌린지를 포함해 이 역할들을 정의하므로, 본문을 읽기 전에 응답 자체의 상태와 모든 Proxy-Authenticate 헤더를 읽어라. 친절한 HTML 오류 페이지는 보통 어느 구성 요소가 작성했는지를 숨긴다.

CONNECT 터널이 바이트 릴레이로부터 숨기는 것

프록시가 CONNECT에 200을 반환하면 바이트를 중계하고 브라우저는 오리진과 자체 TLS 세션을 유지한다. 따라서 바이트를 중계하는 프록시는 그 세션 안의 암호화된 403을 읽을 수 없고, 거부의 작성자가 될 수 없다. 터널이 열리기 전의 거부는 완전히 다른 사건이며, 한 계층 아래에서 생성된다.

TLS를 종료하는 리버스 프록시는 TLS 경계를 소유하고 HTTP를 해석하며 응답을 정당하게 다시 쓰거나 가로챌 수 있다는 점에서 다른 위치에 있다. 그 능력은 구성 선택이지 프록시 일반의 속성이 아니며, 애플리케이션의 접근 정책을 우회할 권한을 주지 않는다. 응답을 읽는 것은 능력이고, 호출자가 무엇을 볼 수 있는지 결정하는 것은 여전히 오리진의 일이다.

실무적 결과는 403을 해석하기 전에 세 가지 형태 중 무엇을 운영하는지 알아야 한다는 것이다. 스트림 프록시에 HTTP 검사 규칙을 추가해도 가시성이 생기지 않으며, 바이트 릴레이가 오리진의 거부를 설명해 주리라 기대하면 잘못된 로그를 보게 된다.

프록시의 송신 경로를 브라우저의 터널 주소와 비교하라

VPN에 연결된 브라우저가 별도의 프록시 서버를 자동으로 그 터널로 옮기지는 않는다. 오리진은 프록시의 송신 주소를 보며, 프록시가 여전히 로컬 기본 경로로 나간다면 보호된 사이트는 신뢰하도록 설정된 적 없는 주소를 본다. 노트북의 내부 VPN 주소는 그 오리진에 보이지 않는다. 이것이 작동하는 터널이 다른 모두에게 올바르게 동작하는 사이트에서 여전히 403을 내는 가장 흔한 이유다.

주소가 혼동되지 않도록 두 경로를 따로 시험하라.

curl -sS -o /dev/null -w 'status=%{http_code}\n' --noproxy '*' https://198.51.100.20/private
curl -sS -o /dev/null -w 'status=%{http_code}\n' -x http://203.0.113.10:3128 https://198.51.100.20/private

첫 요청은 오리진이 자신의 주소에 무엇이라 답하는지 보여 주고, 둘째는 프록시의 주소에 무엇이라 답하는지 보여 준다. 일치하는 403 쌍은 오리진의 인가 규칙을 가리키고, 200 뒤에 403이 오면 프록시 경로를 가리킨다. VPN 풀이 포함되어 있다고 가정하지 말고 규칙 집합이 실제로 어떤 소스 주소를 허용하는지 오리진 소유자에게 물어라.

경계를 분리하는 요청으로 각 경계를 시험하라

애플리케이션 이유로 거부할 수 없는 것을 프록시에 요구해 프록시 계층을 분리하라. 프록시가 도달할 수 있는 호스트로의 CONNECT를 출력을 버리고 실행하면 터널 허용이 애초에 작동하는지 알 수 있다.

curl -sSv -o /dev/null -x http://203.0.113.10:3128 https://198.51.100.20/ 2>&1 | grep -E 'CONNECT|HTTP/1.1|Proxy-Authenticate'

CONNECT에 200이 찍히고 이어 TLS 핸드셰이크가 나오면 프록시 허용은 정상이고 이후의 403은 오리진에서 온 것이다. 407이 찍히면 --proxy-user로 프록시 자격 증명을 제공하고 사이트에 대한 어떤 결론도 내리기 전에 반복하라. CONNECT 줄에 403이 찍히면 살펴볼 경계는 프록시 자체의 허용 목록이며 애플리케이션은 관여하지 않는다.

자격 증명을 셸 기록과 붙여 넣은 기록 밖에 두라. 명령줄에 입력한 프록시 비밀번호는 다른 로컬 사용자에게 보이며, 자격 증명이 포함된 CONNECT 추적을 캡처하는 것은 추적이 없는 것보다 나쁘다.

페이지를 다시 쓰는 대신 판정을 읽을 수 있게 유지하라

어느 구성 요소가 거부했는지 알았다면 그 답을 보존하라. 오리진의 403을 504 타임아웃으로 다시 표기하거나 성공 페이지로 바꾸는 리버스 프록시는 인가 결정을 가용성 이야기 뒤에 숨기고 다음 대응자를 잘못된 팀으로 보낸다. 사용자 지정 오류 페이지는 상태를 유지하고 맥락을 더할 때 괜찮다.

캐싱도 같은 주의가 필요하다. RFC 9111은 공유 캐시가 응답을 재사용할 수 있는 방법을 규정하며, 한 호출자를 위해 만들어진 거부를 허가인 것처럼 다른 이에게 넘겨서는 안 된다. 거부 응답을 저장한다면 비공개이고 범위가 제한된 상태로 유지하고, 상류 트래픽을 줄이기 위해서만 공개 신선도 규칙을 추가하지 마라.

구성 요소, 그것이 만든 상태, 오리진이 본 소스 주소를 밝히는 서면 판정으로 마무리하라. 정확한 요청, 적용 중인 프록시 설정, 시각을 기록해 다음 보고서를 기억에서 다시 도출하는 대신 같은 증거와 맞출 수 있게 하라. 그 기록은 다음 보고서에 몇 초 만에 답하고 같은 403이 다시 VPN 장애로 진단되는 것을 막는다.

관련 글