클라우드 경계에서 VPN 클라이언트 IP를 유지할 것인가, SNAT할 것인가
소스를 유지하면 모든 로그에서 클라이언트 신원이 유지되고, SNAT를 쓰면 라우팅 테이블이 작게 유지됩니다. 애플리케이션에 무엇이 필요한지는 세 가지 조건이 알려 줍니다.
VPN 클라이언트의 주소를 애플리케이션까지 유지하면 모든 로그 줄과 모든 허용 목록 항목이 같은 의미를 갖게 됩니다. 클라우드 경계에서 주소를 다시 쓰면 라우팅 테이블은 단순해지고 로그는 덜 유용해집니다. 어느 선택도 그 자체로 틀리지 않으며, 이를 결정하는 논점이 성능인 경우는 드뭅니다. 세 가지 조건, 즉 어떤 사람이 연결했는지 애플리케이션이 알아야 하는지, 보내려는 소스를 클라우드 패브릭이 받아들일지, 그리고 나중에 반환 경로를 누가 책임질지가 결정합니다.
세 가지 조건이 결정한다
네트워크 다이어그램이 아니라 애플리케이션의 요구 사항에서 시작하십시오. 권한 부여나 감사가 개별 클라이언트 주소에 의존한다면 그것을 유지하는 것은 기능 요구 사항이고 변환은 퇴행입니다. 애플리케이션이 원격 접속 경로로 트래픽이 도착했다는 사실만 알면 된다면 변환된 주소로 충분하며 운영 비용이 훨씬 쌉니다.
두 번째 조건은 기계적입니다. 가상 네트워크는 송신 인터페이스에 할당되지 않은 주소를 주장하는 패킷을 자동으로 신뢰하지 않습니다. 대부분의 클라우드 패브릭은 이를 서로 다른 방식과 이름으로 강제하며, 이를 무시하는 설계는 다이어그램에서는 맞아 보이고 프로덕션에서는 조용히 사라집니다.
세 번째는 조직적입니다. 소스를 유지한다는 것은 애플리케이션 서브넷의 라우팅 테이블이 클라이언트 접두사에 대해 게이트웨이를 되가리켜야 하고, 나중에 서브넷을 추가하는 모든 팀이 그 경로를 일관되게 유지해야 한다는 뜻입니다. 한 번은 작은 비용이고 수십 개 계정에 걸쳐 반복되는 비용입니다. 그 동작을 약속하기 전에 누가 비용을 낼지 결정하십시오.
경로는 위조에 대한 허가가 아니다
반환 경로를 추가하는 것은 응답이 어디로 가야 하는지 서브넷에 알려 줄 뿐이고, 그 패킷을 전달하는 인스턴스가 외부 소스로 패킷을 내보내도 된다고 패브릭에 알려 주지는 않습니다. AWS에서 해당 제어는 소스 및 대상 확인이며 인스턴스별로 볼 수 있습니다.
aws ec2 describe-instances --instance-ids i-0a1b2c3d4e5f60718 \
--query "Reservations[].Instances[].SourceDestCheck"
aws ec2 modify-instance-attribute --instance-id i-0a1b2c3d4e5f60718 \
--no-source-dest-check
문서화된 역할이 전달인 인스턴스에만 그것을 끄고 다른 무엇에도 끄지 마십시오. 확인을 켜 둔 모든 애플리케이션 VM은 빌린 소스로 트래픽을 내보내는 데 쓰일 수 있는 기계가 하나 줄어드는 것입니다. 플랫폼이 AWS가 아니라면 동작이 그대로 이어질 것이라고 가정하지 말고 동등한 안티 스푸핑 또는 허용 주소 설정을 찾으십시오.
빠른 패킷 캡처가 콘솔을 읽는 것보다 질문을 빨리 정리합니다. 응답은 나가는데 클라이언트가 결코 보지 못한다면 애플리케이션이 실패하는 것이 아니라 패브릭이 위조된 소스를 버리는 것입니다.
변환이 사는 것과 치르는 값
게이트웨이에서 소스를 다시 쓰면 반환 경로 문제가 사라집니다. 애플리케이션은 이미 도달 방법을 아는 주소 하나를 보게 되고, 클라이언트 접두사는 클라우드 라우팅 테이블에 나타날 필요가 없으며, Linux에서는 규칙이 한 줄이므로 가능한 가장 좁은 버전을 쓰는 것이 좋습니다.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 172.20.8.0/24 -o ens5 -j SNAT --to-source 10.40.3.10
애플리케이션은 이제 모든 사용자에 대해, 모든 연결에서, 영원히 10.40.3.10를 기록합니다. 그 값에 기반해 만든 주소 허용 목록은 이제 원격 접속 인구 전체에 접근을 부여하며, 어떤 사고 검토든 이것이 누구였는지 답하려면 게이트웨이로 돌아가야 합니다.
해결책은 변환을 버리는 것이 아니라 주소가 신원이라고 가장하는 것을 멈추는 것입니다. 클라이언트 신원을 인증된 필드에 담아 전달하고, 변환된 튜플과 함께 게이트웨이에서 기록하고, 애플리케이션이 그것으로 조인하게 하십시오. 조인 키가 없으면 변환은 네트워크 문제를 영구적인 포렌식 문제로 바꿉니다.
릴레이는 다른 인터페이스다
애플리케이션 앞의 리버스 프록시는 라우팅된 경로 문제를 완전히 우회합니다. 프록시는 자체 백엔드 연결을 열기 때문에 클라이언트 접두사가 가상 네트워크 안에서 라우팅 가능할 필요가 없고, 애플리케이션은 프록시가 설정하는 헤더로 신원을 받을 수 있습니다.
그 헤더는 백엔드가 프록시에서만 받아들이고 다른 어디에서도 받아들이지 않을 때만 신뢰할 수 있으며, nginx 용어로는 도착하는 무엇이든 믿는 대신 신뢰할 피어를 명시적으로 지정하는 것입니다.
set_real_ip_from 10.40.3.10;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
백엔드에 직접 도달할 수 있는 클라이언트는 같은 헤더를 위조할 수 있으므로, 백엔드는 프록시를 통하지 않고는 도달할 수 없어야 합니다. 이것은 설정 의견이 아니라 방화벽 사실이며, 스니펫을 복사할 때 사람들이 건너뛰는 가정입니다.
릴레이는 무엇을 사는지도 바꿉니다. 애플리케이션 수준의 신원과 그것을 기록할 자연스러운 자리를 얻는 대신, 새로운 프로토콜마다 자체 프록시 지원이 필요하므로 범용성을 포기합니다. HTTP 서비스에는 그 거래가 대개 좋고, HTTP가 아닌 것에는 대개 아예 쓸 수 없습니다.
각 경계가 무엇을 보는지 적으십시오
무엇을 고르든 설계 검토는 각 홉에서 소스 주소와 증명 가능한 신원을 명시한 표 하나로 끝나야 합니다. 그것이 없으면 첫 사고에서 세 팀이 각각 다른 클라이언트를 설명하게 됩니다.
| Boundary | Source the next hop sees | What is actually proven |
|---|---|---|
| Client to gateway | 172.20.8.14 | A certificate or credential was presented |
| Gateway to app subnet, preserved | 172.20.8.14 | The same session, if the return route exists |
| Gateway to app subnet, translated | 10.40.3.10 | Only that traffic came through the gateway |
| Proxy to backend | 10.40.3.10 plus a header | The proxy asserted an identity it authenticated |
그다음 고르십시오. 애플리케이션의 권한 부여가 정말 클라이언트 주소에 의존하고 경로를 감당할 수 있다면 소스를 유지하십시오. 그렇지 않고 신원을 대역 밖에서 전달할 수 있다면 변환하고, 플랫폼별 전달 설정에는 AWS 전달 인스턴스 지침을 사용하십시오. 서비스가 HTTP를 쓰고 신원이 아예 주소가 아닌 것을 원한다면 릴레이를 택하십시오.