우리는 인터넷 환경에서 다양한 경우에 Proxy를 사용한다. 보안상의 이유이거나 성능적 이슈에서나 아니면 약간의 불법적인 이유에서 우리는 Proxy를 사용한다.
오늘 이야기할 부분은 크롤링 과정에서 서버 아이피가 차단되었을 때 Proxy 서버를 구성하여 우회하는 방법을 이야기하고자 한다. 약간 불법적인 면이 있으니 사용 유무는 사용자가 알아서 판단해야 한다. 다만 개인적인 목적으로 자동화 툴을 사용하거나, 약간?의 정보 수집 목적으로 사용하는 경우 개인적 생각에 문제가 없지 않을까 한다.

Squid

title

이번에 사용한 Proxy 서버는 Squid(http://www.squid-cache.org/) 이다. 아래 공식 홈페이지에도 나와있듯이 Proxy 캐시 서버이다. 우리가 원하는 기능은 항상 최신 페이지*를 *차단된 클라이언트 대신 요청하고 전달해 줄 Proxy 서버이지 Cache 서버가 아니다. 크롤링 과정에서 이미 크롤링한 기존 페이지가 다시 크롤링되면 크롤링하는 의미가 없어진다.

What is Squid?
Squid is a fully-featured HTTP/1.0 proxy which is almost (but not quite - we’re getting there!) a fully-featured HTTP/1.1 proxy. Squid offers a rich access control, authorization and logging environment to develop web proxy and content serving applications. Squid offers a rich set of traffic optimization options, most of which are enabled by default for simpler installation and high performance.

Why should I deploy Squid?
(Or.. “Why should I bother with web caching? Can’t I just buy more bandwidth?”)
The developers of the HTTP protocol identified early on that there was going to be exponential growth in content and, concerned with distribution mechanisms, added powerful caching primitives

하지만 설치 및 구성이 쉽고 성능도 우수하고 캐시 기능을 끌 수 있는 옵션도 있기에 문제가 되지 않아 Squid를 사용하기로 했다.
추가로 아래 기능도 지원하기에 해당 솔루션을 선택했다.

  • 불특정 IP 대역에서 요청을 했을때 요청 제한을 할 수 있는가(AWS Lambda에서 크롤링 진행 중, VPC 미 설정)?
    • htpassword 기능 지원
    • outbound 도메인 제한
    • 특정 패턴으로 차단 가능
  • 클라이언트 사이드에서 요청 시 별다른 설정이나 구성이 필요 없는가?
    • python requests package에서 별다른 추가 작업 없이 바로 사용 가능.

Squid 설치

  • 서버 설치는 기본 Red Hat 계열 리눅스를 베이스로 진행 내용입니다.

RPM 설치

Squid 설치하는 방법은 다른 오픈소스 솔루션과 유사하게 컴파일해서 별도로 설치하거나 패키지로 설치하는 방법이 있다. 지금 Proxy 서버를 사용하는 목적상 Proxy 서버가 차단될 경우 바른 다른 서버를 세팅해야 하기에 패키지 설치를 선택했다. 기능상으로도 기본 기능만 사용할 것이기에 특정 옵션을 활성화하기 위한 컴파일 과정에서 필요 없다. 이에 패키지 설치를 진행하였다. 논외로 이후에 다시 언급하겠지만 별도의 기능상에 이유로 컴파일 필요한 부분이 존재하긴한다. 필요 시 선택해서 사용하자.

1
2
3
4
5
$> sudo yum install squid
...
$> sudo systemctl start squid
...
$> sudo systemctl enable squid

데몬 재시작을 원하면 아래 명령으로 재시작하면 된다.

1
$>  sudo systemctl restart squid

설치가 완료되고 아래 명령으로 데몬의 상태와 로그를 확인할 수 있다. 정상적으로 데몬이 시작된 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$> systemctl status squid
● squid.service - Squid caching proxy
Loaded: loaded (/usr/lib/systemd/system/squid.service; disabled; vendor preset: disabled)
Active: active (running) since 화 2023-04-18 16:47:10 KST; 1 day 23h ago
Process: 17156 ExecStop=/usr/sbin/squid -k shutdown -f $SQUID_CONF (code=exited, status=0/SUCCESS)
Process: 17164 ExecStart=/usr/sbin/squid $SQUID_OPTS -f $SQUID_CONF (code=exited, status=0/SUCCESS)
Process: 17158 ExecStartPre=/usr/libexec/squid/cache_swap.sh (code=exited, status=0/SUCCESS)
Main PID: 17166 (squid)
CGroup: /system.slice/squid.service
├─17166 /usr/sbin/squid -f /etc/squid/squid.conf
├─17168 (squid-1) -f /etc/squid/squid.conf
├─17169 (logfile-daemon) /var/log/squid/access.log
└─17171 (basic_ncsa_auth) /etc/squid/passwords

4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 systemd[1]: Stopped Squid caching proxy.
4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 systemd[1]: Starting Squid caching proxy...
4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 squid[17166]: Squid Parent: will start 1 kids
4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 systemd[1]: Started Squid caching proxy.
4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 squid[17166]: Squid Parent: (squid-1) process 17168 started

/var/log/message

1
2
3
4
4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 systemd[1]: Starting Squid caching proxy...
4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 squid[17166]: Squid Parent: will start 1 kids
4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 systemd[1]: Started Squid caching proxy.
4월 18 16:47:10 gcloud-seoul-b574f08e6762de6cd29c7508e28a29e6 squid[17166]: Squid Parent: (squid-1) process 17168 started

추가로 htpasswd 인증을 위해서 아래 패키지를 설치하자.

1
$> yum install httpd-tools

설정

기본 설정 파일

RPM 패키지로 설치했을 경우 아래 경로에 기본 설정 파일이 존재한다.

/etc/squid

1
2
-rw-r----- 1 root squid  1734  4월 18 16:47 squid.conf
-rw-r--r-- 1 root root 2315 10월 21 18:55 squid.conf.default

우리가 필요한 파일은 위 파일이다.
squid.conf 파일을 수정하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#
# Recommended minimum configuration:
#

# Example rule allowing access from your local networks.
# Adapt to list your (internal) IP networks from where browsing
# should be allowed
# IP를 한정할 수 없어서 전체 오픈. 필요시 지정 가능.
# acl localnet src 10.0.0.0/8 # RFC1918 possible internal network
acl allnetwork src 0.0.0.0/0

# 허용 포트 지정. HTTPS만 사용.
acl SSL_ports port 443
acl Safe_ports port 443 # https

acl CONNECT method CONNECT

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports

http_access allow localhost manager
http_access deny manager

# htpasswd 인증 설정 추가
auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid/passwords
auth_param basic realm proxy
acl authenticated proxy_auth REQUIRED
http_access allow authenticated

# 허용 도메인 추가.
acl allow_dst dstdomain .xxx.com
http_access allow allow_dst

http_access allow localnet
http_access allow localhost
http_access allow allnetwork

http_access deny all

# 해킹 방지를 위해서 기존 오류 페이지 변경을 위해서 페이지 위치 변경
error_directory /etc/squid/page

# Squid normally listens to port 3128
# HTTPS만 사용할거라서 인증서 위치 지정. 기본 포트 변경하는것이 좋음
#http_port 13128
https_port 13128 cert=/etc/squid/ssl/squid.crt key=/etc/squid/ssl/squid.key

# Uncomment and adjust the following to add a disk cache directory.
#cache_dir ufs /var/spool/squid 100 16 256
# 캐쉬는 사용하지 않음
cache deny all

# Leave coredumps in the first cache dir
coredump_dir /var/spool/squid

설정 파일 자체가 복잡하지 않다. 기본적으로 오픈할 정책을 정의하고 전체 차단하는 구조이다.

1
2
3
http_access allow 허용 항목
...
http_access deny all

지금 사용할 서버는 크롤링 목적으로 https 요청만 포워딩할 것이기에 다른 서비스는 전체 제거했고, IP 역시 요청 클라이언트가 AWS Lambda로 국한됨으로 IP를 한정할 수 없어 모두 오픈했다. 세부적으로 IP 대역으로 오픈할 수 도 있으니 본인에 맞게 설정하길 바란다.

HTTPS용 인증서 생성 및 세팅

신경써야하는 부분은 기본적으로 사용할 기능이 HTTPS 요청을 포워딩 하는 것이기에 Proxy 서버가 인증서를 가지고 있어야한다. 요청 자체가 HTTPS로 진행하기때문에 당연한 이야기이다. 사설 인증서를 하나 만들어서 넣어주자.

1
2
3
4
5
6
7
$> cd /etc/squid
$> mkdir ssl
$> openssl genrsa -des3 -out squid.key 1024
$> openssl req -new -key squid.key -out squid.csr
$> cp squid.key squid.key.org
$> openssl rsa -in squid.key.org -out squid.key
$> openssl x509 -req -days 365 -in squid.csr -signkey squid.key -out squid.crt
  • 인증서 생성 시 만료일자는 본인에 맞게 넣자.

그리고 설정 파일에 나와 있듯이 인증서 경로를 지정해주면 된다.

1
https_port 13128 cert=/etc/squid/ssl/squid.crt key=/etc/squid/ssl/squid.key

htpasswd 인증 추가

현재 설정에 전체 IP 대역이 열려 있어서 추가적인 인증이 필요하다. 그래서 htpasswd로 인증 기능을 추가했다.
RPM 설치 설명할 때 설치한 htpasswd 명령으로 비밀번호 파일을 생성하자.

1
$> htpasswd -bc /etc/squid/passwords user password

그리고 설정파일에 아래 코드를 추가하자.

/etc/squid/squid.conf

1
2
3
4
5
# htpasswd 인증 설정 추가
auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid/passwords
auth_param basic realm proxy
acl authenticated proxy_auth REQUIRED
http_access allow authenticated

포워딩 도메인 제한

마지막으로 아웃바운딩 도메인을 제한해서 보안 이슈 발생을 한번더 차단하자.

/etc/squid/squid.conf

1
2
3
# 허용 도메인 추가.
acl allow_dst dstdomain .xxx.com
http_access allow allow_dst

Caching 기능 끄기

Squid 강력한 기능이라고 하는 Caching 기능이 우리에겐 독이다. 크롤링 시 캐싱된 오래된 페이지를 가져올 경우 문제가 될 수 있기에 끄자. 아래 페이지 링크를 참조하자.

https://wiki.squid-cache.org/SquidFaq/ConfiguringSquid#can-i-make-squid-proxy-only-without-caching-anything

1
2
cache deny all
cache_dir null /tmp

이제 다 완료됐다. 데몬을 재시작하자.

Python 클라이언트 구현

SOCKS 미지원

솔직히 SOCKS에 대해서 본인은 잘 모른다. 사용해 본적도 없고 Proxy 서버 구성시 사용한다는 정도이다. 인터넷에 Proxy 사용 코드를 보면 아래와 같은 코드를 자주 보게 될 것이다.

1
2
3
4
5
6
7
8
9
import requests

proxies = {
'https':'socks5://userid:password@x.x.x.x:portno',
'http':'socks5://userid:password@x.x.x.x:portno'
}

resp = requests.get('https://xxx.com', proxies=proxies, verify=False)
print(resp.text)

현재 구성한 시스템에는 해당 소스가 정상동작하지 않는다. 한참 헤매고 나서 원인을 찾았다. squid는 기본으로 SOCKS 프로토콜을 지원하지 않는다. 공식 문서에서 해당 내용을 찾았다.

https://wiki.squid-cache.org/Features/Socks

Squid handles many HTTP related protocols. But presently is unable to natively accept or send HTTP connections over SOCKS.

The aim of this project will be to make http_port accept SOCKS connections and make outgoing connections to SOCKS cache_peers so that Squid can send requests easily through to SOCKS gateways or act as an HTTP SOCKS gateway itself.

해당 문서에 컴파일 옵션으로 주고 소스코드를 컴파일을하면 단일 SOCKS을 지원하는 데몬을 만들어낼수 있다고 한다.

1
2
3
4
5
6
export CFLAGS=" -Dbind=SOCKSbind "
export CXXFLAGS=" -Dbind=SOCKSbind "
export LDADD=" -lsocks "

export CFLAGS=" -Dbind=SOCKSbind -Dconnect=SOCKSconnect "
export CXXFLAGS=" -Dbind=SOCKSbind -Dconnect=SOCKSconnect "

본인은 굳이 SOCKS을 원하는 것도 아니고 이점을 잘 모르기에 클라이언트 코드를 수정했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
# 사설 인증서라서 verify=False 파라미터를 주어도 인증서 오류가 발생한다.
# 아래 코드로 라이브러리에서 발생하는 오류도 무시하게 만들자.
impport urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

proxies = {
'https':'https://<user id>:<password>@<x.x.x.x IP>:<port no>'
}

try:
resp = requests.get('https://xxx.com')
except:
resp = requests.get('https://xxx.com', proxies=proxies, verify=False)

print(resp.text)

코드 자체 기본 로직은 간단하다. 기본 다이렉트로 서버에 요청을 보내고 차단 이슈가 발생하면 Proxy로 던지게 하면 된다. AWS lambda이기에 할당되는 IP가 한정할 수 없고 모든 IP가 차단될 일이 없기에 기본적으로는 직접 서버로 요청하고 차단 이슈가 발생할 경우 Proxy로 요청하게 구현하면 된다.

정상적으로 요청이 진행되고 있다면 아래 로그와 같이 로그에서 확인 가능할 것이다.

/var/log/squid/access.log

1
2
3
1681976515.035    539 13.x.x.x TCP_TUNNEL/200 51022 CONNECT xx.xx.com:443 userid HIER_DIRECT/223.130.200.148 -
1681976516.478 418 15.x.x.x TCP_TUNNEL/200 116163 CONNECT xx.xx.com:443 userid HIER_DIRECT/223.130.200.148 -
...

마무리

해당 시스템을 구성 후 며칠 동안 사용해 보았는데 잘 동작한다. 성능도 나쁘지 않고 클라이언트 구현시 별도 설정이나 많은 코드가 추가되지 않아서 기존 시스템에서 마이그레이션 하기도 편하다. 다만 차단될 정도의 트래픽을 유발하는 행위는 애초에 만들지 말자!