2019년 11월 19일 화요일

mitmproxy로 https를 forward proxy하면서 URL whitelist로 일부만 허용하기

인터넷이 안되는 특정서버에서 인터넷이 되는 DMZ영역의 서버를 통해서 구글스토리지를 gsutil로 접속해서 업로드/다운로드 등의 기능을 사용해야하는 상황이 생겼다. 이에 시행착오를 거쳐 mitmproxy라는 오픈소스로 해결한 사례를 공유한다.

인터넷이 안되는 특정서버 : A서버와 B서버
인터넷이 되는 DMZ영역의 게이트웨이서버 : G서버

1.
우선 DMZ영역도 외부 인터넷이 기본적으로 차단되어 있으므로 G서버에서 URL기반으로 외부 인터넷 방화벽을 443포트로 뚫어야한다.
  • https://accounts.google.com
  • https://oauth2.googleapis.com 
  • https://www.googleapis.com

2.
G서버에서 운영중인 nginx가 있는데 이게 ngx-connect 모듈을 넣어서 설치한 것이라 이걸 그대로 프락시 서버로 사용했다.

3.
A서버와 B서버에서 gsutil을 설치하기 위해 gsutil만 바이너리를 받아서 넣어도 되지만, gcloud의 인증관련 설정과 환경설정값을 그대로 활용하기 위해 gcloud를 설치해서 gsutil을 사용하기로 했다. gcloud는 conda로 설치가능하지만, gcloud가 python 2.x를 기본적으로 지원하고, 3.x는 일부만 지원하고 있기 때문에, python2.x로 포함하고 있는 버전지정 보관파일로 설치했다. 

4.
gcloud 설치 후 필요한 설정 (설정 페이지 참고)

proxy 설정
gcloud config set proxy/type http
gcloud config set proxy/address G서버IP
gcloud config set proxy/port G서버포트

그리고 아래 2개 설정을 추가해줘야 불필요한 접근이 사라진다.
gcloud config set core/disable_usage_reporting True
gcloud config set component_manager/disable_update_check True

5.
gsutil ls gs://버킷이름
이런 명령으로 구글스토리지의 특정 버킷에 존재하는 파일 목록을 조회할 수 있다.

그런데 이렇게 하면 모든 구글스토리지에 권한만 있다면 A서버와 B서버의 데이터를 업로드할 수 있게된다. 데이터보안측면에서 특정 버킷에만 접근을 허용해야 한다. 그런데 G서버의 프락시서버 역할을 하는 nginx의 ngx-connect 모듈은 CONNECT 이후의 https request에 대해서는 SSL 터널 내부에서 이뤄지기 때문에 request header나 body를 접근할 수 없다.

그래서 https request를 forward proxy할 수 있는 다른 오픈소스인 mitmproxy를 찾게되었다.

6.
mitmproxy는 다운로드 페이지에 각 OS마다 실행파일이 미리 컴파일되어 있고 파이썬 설치파일 형태로도 배포하고 있어서 쉽게 설치가 가능하리라 생각했다. 하지만 리눅스를 위한 실행파일은 GLIBC 2.18 버전을 찾으며 실행이 되지않고, 파이썬 설치파일은 파이썬 3.6 이상에서만 수행이 된다.
G서버는 게이트웨이서버라서 파이썬도 OS와 함께 설치된 2.7버전이었기 때문에 mitmproxy의 메이저버전을 하나씩 낮추면서 현재 G서버환경에서 실행가능한 2.0.2버전을 사용할 수 밖에 없었다. 2.0.2버전의 mitmproxy도 내가 원하는 기능은 모두 가능하기에 큰 문제는 없었다.

2.0.2버전은 문서페이지에 대한 링크가 공식홈페이지에 없는데, 구글링을 하다보니 1.x와 2.x 버전에 대한 문서페이지도 찾을 수 있었다. 오히려 일부 내용은 여기가 더 자세한 경우도 있었다.

7.
mitm은 man in the middle 공격의 약자라서 이름을 아주 잘 지었다고 생각했다.
mitmproxy는 대화형콘솔이 나타나면서 커맨드라인 콘솔환경에서 모니터링이 가능하다. mitmweb은 대화형콘솔을 web으로 제공하여 브라우저에서 모니터링이 가능하다. mitmdump는 로그만 남게되어 백그라운드로 실행하기에 적합하다.

G서버에서 아래 3가지 mitmdump를 nohup으로 실행한다.
./mitmproxy -p 8000 -s block.py
./mitmweb -p 8000 --no-browser --web-iface 0.0.0.0 --web-port 9000 -s block.py
./mitmdump -p 8000 -s block.py

8.
특정 버킷에만 접근을 허용하고 싶다면 아래처럼 block.py를 생성해서 mitmxxxx 실행시 -s 옵션으로 지정하면 된다. mitimproxy github의 샘플을 참고했다. 특정 버킷이 아닌 경우 glcoud는 403을 만나게되어 처리되지 않는다.

from mitmproxy import http
def requestheaders(flow):
    if "/버킷이름/" not in flow.request.path:
        flow.response = http.HTTPResponse.make(403)


9.
A서버나 B서버에서 whitelist로 넣은 버킷은 정상적으로 접근되지만, 아래 처럼 허용되지 않은 버킷에 접근하면 403 에러를 만나면서 아무 처리도 되지 않는다.
gcloud ls gs://허용되지않는버킷
AccessDeniedException: 403

G서버에서는 아래처럼 로그가 생성된다.
127.0.0.1:58186: clientconnect
127.0.0.1:58186: GET https://www.googleapis.com/storage/v1/b/허용되지않는버킷/o?delimiter=%2F&projection=noAcl&versions=False&fields=prefixes%2CnextPageToken%2Citems%2Fname&alt=json&maxResults=1000
              << 403 Forbidden 0b
127.0.0.1:58186: clientdisconnect


10.
인증을 위한 whitelist를 도메인단위로 추가해야 하고, 위 block.py에서는 버킷명을 디렉토리명으로 하는 다른 버킷도 접근이 허용되는 버그가 있으므로 url과 path를 확인하는 조건을 강화할 필요가 있겠다.