NGINX 스트림 릴레이를 통한 SSH, 깨뜨리지 않고
PROXY 헤더는 그것을 기대하는 수신자에게 보내는 메시지이고, 프록시된 DNS 레코드는 TCP 릴레이가 아닙니다. 엣지와 업스트림을 각각 따로 고치세요.
릴레이를 통한 공용 SSH는 서로 무관한 두 경계에서 실패하며, 하나를 고치면 보통 다른 하나가 드러납니다. 엣지가 스트림을 나르는 대신 HTTP 응답을 반환하거나, 릴레이가 백엔드가 읽기로 동의한 적 없는 헤더를 앞에 붙입니다. 어느 쪽을 보고 있는지는 세 가지 조건이 결정합니다: 공용 레코드가 실제로 무엇을 가리키는지, 스트림 블록이 업스트림으로 무엇을 보내는지, SSH 클라이언트가 어떤 이름을 검증하는지입니다. 이 순서대로 처리하세요.
두 경계, 두 증상
엣지의 실패는 SSH 배너가 있어야 할 자리에 HTTP 상태 페이지가 도착하는 모습입니다. 연결은 TCP 수준에서 완료되고 첫 바이트는 웹 서버에 속한 텍스트입니다. 이는 프로토콜 불일치이며 패킷 손실이 아닙니다. 또한 엣지가 그 이름에 대해 의도적으로 다른 것을 제공하고 있음을 알려줍니다.
업스트림의 실패는 다르게 보입니다. 연결이 sshd에 도달하고, sshd는 기대하는 식별 교환이 아닌 바이트를 읽으며, 세션은 키를 한 번도 제시하지 않은 채 죽거나 멈춥니다.
둘은 둘 다 연결한 뒤 실패하는 클라이언트를 만들기 때문에 혼동하기 쉽습니다. 받는 첫 프로토콜 응답이 둘을 구분해 주므로, 그것이 티켓에 들어가야 합니다.
PROXY 헤더는 메시지이지 스위치가 아니다
스트림 모듈에서 proxy_protocol on은 NGINX에 페이로드 전에 업스트림 쪽으로 PROXY 프로토콜 헤더를 쓰라고 지시합니다. TCP 프록시를 켜는 것이 아니며 SSH 암호화와는 아무 관련이 없습니다. 이것은 수신자에게 보내는 메모입니다. 수신자가 읽을 수 없으면 스트림 시작 부분의 쓰레기가 됩니다.
받는 서비스는 그 헤더를 의도적으로 소비해야 합니다. 일반 sshd는 연결의 첫 바이트로 SSH 식별 교환을 기대하므로, 예상치 못한 서두는 손상된 서버처럼 보이는 방식으로 핸드셰이크를 깨뜨립니다.
stream {
server {
listen 2222;
proxy_pass 10.77.0.20:22;
}
}
그 조각이 VPN을 통해 도달 가능한 백엔드에 대한 작동하는 릴레이의 전부입니다. PROXY 지시문이 나타나지 않는 것은 수신자가 그 프로토콜을 말하지 않기 때문이며, 그 부재는 누락이 아니라 결정입니다.
두 방향도 분리하세요. listen 줄에 proxy_protocol을 설정한다는 것은 NGINX가 자신의 클라이언트가 헤더를 먼저 보내기를 기대한다는 뜻이며, 일반 SSH 클라이언트에 이를 켜면 사람들이 보통 해결하려는 문제의 정반대를 만듭니다.
공용 호스트 이름이 실제로 도달하는 곳
SSH 세션에서 HTTP 400 페이지는 엣지에 대한 증거입니다. 경로 어딘가가 HTTP를 말하고 있으며, 백엔드에서 키나 계정을 어떻게 다뤄도 그것은 바뀌지 않습니다. 엣지가 정리될 때까지 백엔드 변경을 멈추세요.
Cloudflare의 일반 프록시 레코드는 지원되는 HTTP 및 HTTPS 트래픽을 지정된 포트에서 운반합니다. 호스트 이름에 프록시를 켠다고 일반 TCP 릴레이가 생기지는 않으며, SSH를 443 포트로 옮긴다고 HTTPS가 되지는 않습니다.
실행 가능한 방향은 두 가지입니다. DNS 전용 레코드는 자체 TCP 릴레이를 가리킬 수 있고, 그러면 노출에 대한 결정이 여러분 손에 남습니다. 또는 SSH 전송을 명시적으로 문서화한 제품을 사용하고, 그 제품 자체의 오리진, 클라이언트, 접근 설정을 따르세요.
무엇을 고르든 릴레이는 여전히 백엔드로 가는 VPN 경로, 허용된 연결, 돌아오는 경로가 필요합니다. 공용 쪽이 동작한다고 해서 경로가 선택 사항이 되지는 않습니다.
별칭으로 SSH 정체성을 안정적으로 유지하라
사용자는 보통 기존 호스트 이름을 계속 입력하고 싶어 합니다. SSH 설정의 별칭은 릴레이에 아무것도 바꾸지 않고 그것을 가능하게 하며, 변경을 본래 있어야 할 클라이언트 쪽에 남깁니다.
Host git.example.com
HostName ssh-entry.example.net
Port 2222
User git
HostKeyAlias git.example.com
HostKeyAlias가 없으면 OpenSSH는 연결하는 이름 아래에서 호스트 키를 찾으므로 항목이 이동하고 모든 사용자가 첫 사용 시 키 변경 경고를 봅니다. 별칭을 설정하면 연결은 여전히 릴레이로 가면서 known-hosts 항목이 안정적으로 유지됩니다.
이 별칭은 SSH 기반 원격에만 적용됩니다. HTTPS Git URL은 이를 참조하지 않으며, 일반적인 원격 URL은 실제로 준비한 전송 방식과 일치해야 합니다.
하지 말아야 할 것은 경고를 없애기 위해 호스트 키 검증을 끄는 것입니다. 그것은 전달 수리가 건드리라고 요청한 적 없는 보호를 제거하고, 겉모습의 불편을 실제 노출로 바꿉니다.
각 홉을 따로 검증하라
공용 리스너를 단독으로 시험하고, 다음으로 릴레이 호스트에서 릴레이-백엔드 연결을, 그다음에야 클라이언트를 끝에서 끝까지 시험하세요. 그 세 시험은 각각 다른 계층을 배제합니다. 하나를 건너뛰면 나머지 두 결과를 해석할 수 없습니다. 세 가지 관찰, 실패가 살 수 있는 세 곳입니다.
각 결과와 함께 목적지 주소, 포트, 첫 프로토콜 바이트를 남기세요. 그 삼중주가 엣지 오류와 VPN 라우팅 문제, SSH 인증 실패를 구분하며, 그것이 수리와 추측의 차이입니다.
마지막으로 의도된 노출을 문서화하세요. SSH를 사설 백엔드로 나르는 TCP 릴레이는 공용 경계에 의도적으로 낸 구멍이며, 다른 어떤 인바운드 서비스와 같은 검토를 받을 자격이 있습니다.