웹 프로젝트 (IBAS)/Infra

프록시 요청 시 원래 사용자 ip 복원하기 (Nginx, Cloudflare)

동현 유 2022. 5. 5. 00:03

[배경]

  도메인 호스팅 업체 CloudFlare 를 사용 중. (이전에는 godaddy 를 사용하다가 호스팅이 불안정 했을 때, 급하게 옮긴뒤로 CloudFlare 가 더 편해서 계속 사용 중임.) CloudFlare에서는 프록시 모드로 https 통신을 지원해준다. 이 프록시 모드를 사용하면 웹서버로의 요청 ip가 실제 사용자의 ip가 아닌, CloudFlare의 프록시 서버 ip 로 찍히게 된다.

  현재 운영하고 있는 개발용 api는 개발 팀원들만 접속할 수 있도록 특정 ip 만 열어놓은 상태이다. godaddy의 인증서가 서브도메인에는 적용되지 않아서, lets encrypt의 무료인증서로 서브도메인에 tls 프로토콜을 적용한 상태였다. 하지만 이 무료 인증서의 치명적인 단점은 3개월마다 연장을 해주어야한다는 점이다. 물론 script 파일을 작성해서 3개월마다 한번씩 crontab으로 자동실행을 걸어놓을 수 있지만,, 저번에 급하게 aws로 서버 이관하면서,, 스크립트 파일을 날려먹었다. 인증서 확인을 위한 레코드 키(?) 같은 정보가 필요한데, 그게 없으면 다시 처음부터 발급받아야한다. 매우 귀찮은 작업이다.

  그래서 CloudFlare의 프록시 모드로 https 를 적용하면서, 사용자 ip를 복원시키기로 했다.


1. 복원 원리

  원리는 간단하다. nginx 리버스 프록시 기능을 사용해 본적 있는 사람들은 쉽게 감이 올 것 같다. request 헤더에 X-forwarded-for 이라는 헤더가 있다. 프록시 요청을 하게 되면, 프록시 서버를 거쳐가면서 ip 를 차례로 헤더에 적는다.

예로 client 의 ip 가 A에서 요청이 시작되어,  3개의 프록시 서버 B C D 를 차례로 지나갔다고 하자. 그러면 X-forwarded-for 헤더에는 A B C D 가 적히는 셈이다. 따라서 X-forwarded-for 헤더의 맨 첫번째에 적힌 ip로 요청ip 헤더를 변경하면 된다. X-Forwarded-For 헤더에 대해서 더 자세히 알고 싶으면 RFC 문서를 참고.

  

 

2. Nginx 설정 설명

 여러 웹 서버에 대한 설정을 CloudFlare 홈페이지에서 안내하고 있다. nginx 이외의 설정은 해당 페이지를 참고하면 될 듯하다.

ngx_http_realip_module

  헤더의 맨 마지막 ip 를 가져오는 작업을 해주는 nginx 모듈이 있다. (해당 페이지)

  (1) 지시어 set_real_ip_from 은 해당 ip에서 온 요청에 대해서 작업을 해달라는 선언이다. 이 경우에는 CloudFlare의 프록시 서버 주소를 적으면 된다. 프록시 서버 주소 목록은 여기서 확인할 수 있다. CloudFlare의 프록시 서버 주소 변경시에는 해당 사이트를 참고하면 된다.

  (2) real_ip_header 는, set_real_ip_from 으로 선언한 프록시 서버에서 요청이 들어왔을 때, real client ip 를 찾을 요청 헤더를 명시하는 지시어다. 위의 예시에서는 192.168.1.0/24, 192.168.2.1, 2001:0db8::/32 에서 요청이 들어왔을 때, client ip 를 X-Forwarded-For 이라는 헤더에서 찾겠다는 의미이다.

  (3) 마지막으로 real_ip_recursive 가 남았다. 이게 레퍼런스 설명만으로는 이해하기가 제일 힘들어서 이곳을 참고했다. 

위와 같은 nginx 설정이 있고, 아래와 같은 요청이 들어왔다고 해보자 

클라이언트는 123.123.123.123 이고 프록시 서버 192.168.2.1 과 127.0.0.1 을 차례로 거쳤다. 따라서 nginx 는 요청 ip를 123.123.123.123 으로 변경할 것이다. 하지만 악의적인 사용자가 요청을 보내기 전에 아래와 같이 헤더를 조작한다고 해보자.

그러면 서버에서 최종적으로 요청을 받았을 때는 아래와 같은 모양이 되고, 클라이언트를 11.11.11.11 로 선택할 수 밖에 없다. 이는 spoofing 공격 방식인데, 이런 공격을 막기 위해 real_ip_recursive 를 활성화하면 set_real_ip_from 으로 선언된 '신뢰할 수 있는' 프록시 서버 목록만을 믿겠다는 것이다. 따라서 192.168.2.1 과 127.0.0.1 만 프록시 서버라고 믿을 수 있으므로, nginx 는 123.123.123.123 을 클라이언트로 보는 것이다.

nginx 에서 실제로 요청을 받았을 때, 요청 ip가 담겨있는 변수는 $remote_addr 이라는 변수이다. 위의 설정을 하지 않으면, $remote_addr에는 마지막 프록시 서버 ip주소가 적혀있을것이다. 하지만 위 설정을 하면 nginx는 $remote_addr 에 최종적으로 선택한 클라이언트의 ip 주소를 적는다.

 

3. Nginx 설정 예시

  아래와 같은 설정을 http, server, location 블록 안에서 선언해주면 된다. (nginx.conf 또는 conf 파일로 따로 만든 후에 include 해도 된다.) 전역으로 설정했을 때는 더 작은 scope 의 설정이 우선하므로 적용이 되지 않는 것처럼 보이는 경우가 있다. 그런 경우에는 가장 좁은 scope 의 http 블록에 real_ip_header X-Forwarded-For; 을 한번더 적어주면 된다.

set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;

real_ip_header X-Forwarded-For;
real_ip_recurive on;

  참고로 nginx 를 다시 실행할 때는 nginx -t (설정파일 문법 테스트) 를 하고, restart가 아닌 reload를 해주자!

restart는 일단 nginx 를 끄고나서 재시작하는데, 문제가 있으면 웹서버가 다시 시작되지 않는다. reload는 설정파일을 다시 읽어들이는데, 문제가 있어도 꺼지지 않고 상태가 그대로 유지된다.