Proxy(프락시) 환경에서 client IP 를 얻기 위한 X-Forwarded-For(XFF) http header

개요

XFF 는 HTTP Header 중 하나로 HTTP Server 에 요청한 clinet 의 IP 를 식별하기 위한 사실상의 표준이다.


웹 서버나 WAS 앞에 L4 같은 Load balancers 나 Proxy server(HAProxy), caching server(Varnish), HTTP 서버용 WAS Connector(웹로직 커넥터 - mod_wl, 톰캣 커넥터 - mod_jk 등) 등이 있을 경우

이런 제품들은 웹서버/WAS 에 HTTP 나 전용 프로토콜(AJP)로 요청을 보낸후에 받은 결과를 가공하여 클라이언트에 재전송하게 된다.

이로 인해 처리한 웹 서버나 WAS에서 request.getRemoteAddr(); 등으로 클라이언트 IP를 얻을 경우 L4 나 Proxy 의 IP 를 얻게 되는데 이는 원하는 결과가 아니다.


X-Forwarded-For는 이 문제를 해결하기 위해 사용하는 http header 로 squid caching server 에서 처음 사용되었다.

다음과 같이 콤마를 구분자로 client 와 proxy IP 가 들어가게 되므로 첫번째 IP 를 가져오면 클라이언트를 식별할 수 있다.

X-Forwarded-For: client, proxy1, proxy2


WAS 에서 사용

여기까지 읽고 웹 어플리케이션을 개발할 경우 client ip 를 식별할 필요가 있다면 먼저 저 헤더가 있는지 확인한 후에 없으면 getRemoteAddr() 로 IP 를 얻으면 되겠지라고 생각할 수도 있겠지만 이게 끝은 아니다.

XFF 는 사실상의 표준이지 정식 RFC 에 포함된게 아니므로 대개는 착실하게 저 헤더를 사용하지만 엉뚱한 헤더를 사용하는 제품들이 있다.

그중에 하나인 WebLogic Connector(mod_wl) 는 저 헤더를 사용하지 않고 WL-Proxy-Client-IP 나 Proxy-Client-IP  같은 전혀 엉뚱한 헤더를 사용하므로 만약 만드는 웹 어플리케이션이 

WebServer, WAS, L4, proxy 종류에 상관없이 client IP 를 잘 가져오기를 바란다면 다음과 같은 순서로 IP 를 얻어내야 한다.


        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("Proxy-Client-IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("WL-Proxy-Client-IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("HTTP_CLIENT_IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getRemoteAddr();  
        }

이외에도 X-Real-IPX-RealIP 라는 헤더를 사용하는 제품도 있다고 한다.


Apache httpd Web Server 에서 사용

아파치 웹 서버에서는 LogFormat 지시자나 접근 권한 체크시 Remote Address 를  사용하는데 앞에 Reverse Proxy가 있다면 의도한 대로 동작하지 않으므로 XFF 헤더를 사용하도록 수정해야 한다.


로그포맷

기본 로그포맷은 %h 로 리모트 Address 를 사용하므로 다음과 같이 %{X-Forwarded-For}i 를 사용하도록 수정한다.

## 수정전
## LogFormat "%h %l %u %t \"%r\" %>s %b" common
 
## 수정후 
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b" common

접근 제어(ACL)

ACL이 필요할 경우 다음과 같이 전역 변수를 설정하고 ACL 이 필요한 곳에서 이 변수를 사용하는게 편리하다.

SetEnvIf X-Forwarded-For ^192\.168\.1\. admin_1
SetEnvIf X-Forwarded-For ^192\.168\.2\. admin_2
 
<Files wp-config.php>
    order Deny,Allow
    deny from all
</Files>

## 워드프레스 로그인은 내부 IP 에서만 허용
<Files ~ "^wp-login.php">
    Order Deny,Allow
    Deny from all
## 내부 IP 만 관리자 로그인 허용
    Allow From env=admin_1
    Allow From env=admin_2
</Files>


nginx

nginx 는 --with-http_realip_module 옵션을 주고 컴파일해야 실제 ip 를 얻어올 수 있다. 배포판에 포함된 nginx 는 기본 컴파일 옵션일것이라고 생각되며 다음 명령어로 지원 여부를 확인할 수 있다.

nginx -V


nginx 가 지원한다면 다음 설정을 nginx.conf 에 넣어주면 실제 ip 가 로그 파일에 기록되며  L4나 proxy 가 여러 개일 경우 set_real_ip_from 키워드 뒤에 IP 를 개행해서 적어주면 된다.

http {
set_real_ip_from  192.168.1.0/24; # proxy/L4 서버 IP
set_real_ip_from  192.168.2.1;
real_ip_header      X-Forwarded-For; #proxy/L4 가 실제 IP 를 설정하는 HTTP Header


tomcat 의 access log

tomcat 의 access log 는 XFF 헤더를 사용하지 않으므로 아래 밸브를 추가하고 Access Log 의 패턴을 수정한다.

<Valve className="org.apache.catalina.valves.RemoteIpValve" />
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%{x-forwarded-for}i %l %u %t &quot;%r&quot; %s %b %{User-Agent}i" />

X-Real-IP 헤더를 사용하는 제품일 경우 %{x-forwarded-for}i 대신 %{x-real-ip}i 사용

%{User-Agent}i 는 로그량이 많아지므로 불필요할 경우 삭제



같이 보기

Ref