빈즈토크로 웹앱 서버 배포하기
빈즈토크
AWS가 제공하는 PaaS 중 하나인 서비스는
기존에 각각 따로 관리했던 EC2, RDS, ELB, ASG등 서비스를 비롯해 클라우드 워치와 같은 모니터링 서비스와
NGINX 혹은 APACHE 등의 서버를 이용해서 간편하게 웹 어플리케이션을 배포하고 관리할수 있도록 도와주는 서비스라고 생각한다.
여기에 Java, .NET, PHP, Node.js, Python, Ruby, Go, Docker를 지원한다.
즉 내가주로 작업하는 Node 백엔드 서버를 하나하나 일일히 구성하지 않고 서버 및 리버스 프록시 서버, 오토스케일링 그룹등을 원하는대로 구성할 수 있다.
여기에 코드 파이프라인까지 통합할수 있으니 CD까지도 가능하다. (아쉽게도 이건 유저가 직접 설정해야 한다, 다루지 않음)
설정하는건 메뉴얼을 보고 조금 들여다보면 알수 있으니 생략
NGINX
오늘의 또다른 주인공인 NGINX는 다른 용도로 사용할 수 있겠지만 오늘은 역방향 프록시, 빈즈토크 관련된 내용들과 NGINX 자체에 대해 작성하고자 한다.
엔진엑스는 APACHE와 비슷한 웹서버 혹은 역방향 프록시 서버 또는 메일 서버로 사용이 가능한 웹 서버 프로그램이다. Apache와 가장 다른 요소는 Nginx는 비동기 이벤트 구조를 지니고 있기때문에 그렇지 않는 아파치에 비해 가용성이 높다고 한다. (웨비나에서 진짜 이렇게 설명했다.)
또한 웹서버 단독으로 사용하는것 대신 Nginx와 같이 사용하면 로드밸런싱 및 보다 강력한 보안등을 경험할 수 있다.
이런 빈즈토크와 Nginx를 이용해서 웹서버에서 발생한 악의적인 요청을 막는 방법을 작성해보고자 한다.
빈즈토크와 Nginx로 악의적인 요청 막기
웸 서버를 개발하고 테스트를 하는 입장에서 여지껏 사용하는 서버의 성능이 안좋거니 하고 넘어간것이 많다. 그러면 안되는 것이지만, 아직까지 서버를 사용하는 유저들이 없는 상황(테스트중)이니 로그를 자세히 들여다 보지 않고 있었다.
그러던 어느늘부터 빈즈토크에 배포가 잘 안되고 인스턴스가 느려지는 현상이 생겨났다. 스왑메모리를 추가하며 작업을 했지만 사실 느려진 이유는 메모리 부족이 아니라 AMI(아마존 리눅스)에서는 firewalld가 깔려있지 않고 방화벽 설정이 되어 있지 않아 봇들의 만남의 광장이 되버린것이 주된 이유였다. 물론 이것도 작업을 하다 자연스럽게 해결되었다.
이런 문제는 빈즈토크 배포를 하다 우연히 발견한 로그를 보고 확인하게되었다.
Nginx는 access 로그를 지원한다. 어떻게 설정하느냐에 따라 다르겠지만 적어도 Beanstalk 서비스는 로그가 기본적으로 활성화 되어 있었다.
그중 가장 이상했던 로그 일부분은 다음과 같다.
... GET /.env 404 ...
... POST / 404 ...
... GET /config/getuser?....
... GET /owa/auth/x.js ...
이런 로그들은 전부 외부에서 악의적인 요청이 들어왔을때의 로그일부 이다.
즉 서버는 알게모르게 공격을 받고 있었던 것이다.
심지어 GET 쿼리스트링으로 base64 인코딩된 데이터를 받기도 했다. 뭔지 모르지만 코드 주입 공격이지 않았을까?
이런 요청을 막기위해서 Nginx와 빈즈토크 파일을 수정하기 시작했다.
Beanstalk 설정
우선 AWS CodePipline을 이용해 깃헙의 특정 브랜치로 PR을 날리고 머지하면 자동으로 배포가 가능한 CD 환경을 구축해 두었음을 알려둔다. (사실 안그래도 되긴한다. 첫 배포하기 전에 git 명령어가 조금 늘어나는 수준의 차이)
빈즈토크 OS환경을 구성하기 위해서는 다음 그림처럼 .ebextenstion 폴더를 프로젝트 최상위 폴더에서 생성해 준다.
해당 폴더는 빈즈토크 환경을 수정할때 사용한다. aws 문서처럼 빈즈토크 환경을 수정할때 사용하며, 다른 aws 문서처럼 패키지, 파일, 명령, 서비스를 구성해야 할때 사용한다.
여기서 이번에 필요한 파일은 없지만 처음 구성하는 사람들의 고충을 덜고자 사용중인 redirect 파일을 공유한다.
commands:
01setup_swap:
test: "test ! -e /var/swapfile;"
command: |
/bin/dd if=/dev/zero of=/var/swapfile bs=1M count=2048
/bin/chmod 600 /var/swapfile
/sbin/mkswap /var/swapfile
/sbin/swapon /var/swapfile
02_permission:
command: |
chmod +x .platform/hooks/prebuild/init.sh;
ignoreErrors: true
files:
/etc/nginx/conf.d/proxy.conf:
mode: "000644"
owner: root
group: root
content: |
proxy_set_header X-Real-IP $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-For $remote_addr;
upstream nodejs {
#4000 포트로 리버스 프록시
server 127.0.0.1:4000;
keepalive 256;
}
server {
listen 8080 ssl;
listen [::]:8080 ssl;
server_name localhost;
# Beanstalk는 필요한 모듈이 이미 포함되어 있다.
# 로드밸런서에서 들어오는 IP들을 로드밸런서의 것이 아닌 Client의 IP로 바꾼다.
set_real_ip_from 172.31.0.0/16;
real_ip_header X-Forwarded-For;
# Application LB는 TCP를 지원하지 않는다. 여기 작성된 ssl 관련 항목들이 작동 안할지도
ssl_certificate /YOUR/fullchain.pem/PATH;
ssl_certificate_key /YOUR/privkey.pem/PATH;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "어떤+값들이:있지만+혹시나:하여+숨깁니다:검색해+보세요!";
ssl_prefer_server_ciphers on;
# if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
# set $year $1;
# set $month $2;
# set $day $3;
# set $hour $4;
# }
access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
access_log /var/log/nginx/access.log main;
# 이걸이용해서 쿠키를 유지할수 있었다. SSL 설정이 반드시 필요한것인지는 미지수.
add_header Set-Cookie "Path=/; HttpOnly; Secure";
location / {
proxy_pass http://nodejs;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_set_header Host $host;
}
gzip on;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
location /static {
alias /var/app/current/static;
}
}
# /opt/elastic ... 이 방식은 AMI 2버전에서는 지원되지 않는다.
/opt/elasticbeanstalk/hooks/configdeploy/post/99_kill_default_nginx.sh:
mode: "000755"
owner: root
group: root
content: |
#!/bin/bash -xe
rm -f /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf
service nginx stop
service nginx start
container_commands:
removeconfig:
command: "rm -f /tmp/deployment/config/#etc#nginx#conf.d#00_elastic_beanstalk_proxy.conf /etc/nginx/conf.d/00_elastic_beanstalk_proxy.conf"
사실 이번에 봐야할 것은 .platform 폴더다.
Nginx 구성을 위한 .platform 폴더
해당 폴더는 Nginx, Apache와 같은 프록시 서버를 위한 폴더이다.
aws 문서 에서 확인 가능하듯이 hooks, nginx등을 이용해서 nginx구성을 설정하고, 배포단계에서 필요한 훅 스크립트를 실행 시킬수 있다.
일단 Nginx 구성 파일을 수정해야 한다. .platform/nginx/nginx.conf 파일을 열고 다음과 같이 수정했다.
#Elastic Beanstalk Nginx Configuration File
user nginx;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 32781;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf;
set_real_ip_from 172.31.0.0/16;
# x-forwared-for는 'ipv4, ipv4' 와 같은 문자다 가장 뒤에 ip가 신뢰하지 못하는 ip. 그걸 가져오기 위함.
real_ip_recursive on;
real_ip_header X-Forwarded-For;
# 기본으로 10M? 정도로 요청 body 사이즈가 정해져 있다.
client_max_body_size 50M;
# BOT들은 주로 http이고, host 정보를 가지고 있지 않는다. 근데 이건 ELB Health checker도 마찬가지.
# http_post에는 요청하는 대상의 host 정보가 담겨있다. 당신이 url창에 example.com이라 작성하면 http_host는 example.com이 된다.
map $http_host $default_host_match {
원하는.도메인 1;
example.com 1;
default 0;
}
map $http_upgrade $connection_upgrade {
default "upgrade";
}
server {
listen 80 default_server;
access_log /var/log/nginx/access.log main;
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip off;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# .으로 시작하는 요청 및 auth, bao가 들어간 요청은 honeypot.conf 파일에 따라 서비스 제공이 안된다.
location ~ (/\..*|^/bao|auth) {
include /etc/nginx/includes/honeypot.conf;
}
# 403 혹은 410 에러의 경우는 아래 @honeypot 을이용해 처리한다.
error_page 403 410 = @honeypot;
set $set 1;
if ($default_host_match = 0 ) {
set $set "${set}1";
}
# BOT들중 ELB Health Checker를 허용하기 위함. 본인은 외부 결제모듈 서버도 추가해 두었다.
if ($http_user_agent ~ ^ELB-HealthChecker\/) {
# if ($default_host_match = 0) {
set $set "${set}1";
}
# http_host도 없고 ELB 봇도 아닌 봇들은 if문 에의해 410페이지로 간다.
if ($set = 11) {
return 410;
}
set $post_root 0;
# 볻들중 post / 으로 계속 요청하는것도 있다.
if ($request_uri = \/ ) {
set $post_root post;
}
if ($request_method = POST) {
set $post_root "${post_root}_root";
}
if ($request_uri = /health) {
return 410;
}
if ($post_root = post_root) {
return 410;
}
# 위에서 @honeypot으로 보내진 요청은 honeypot.conf에 있는 코드를 실행시킨다.
location @honeypot {
include /etc/nginx/includes/honeypot.conf;
# 한번 블럭처리된 ip를가 다시 @honeypot에 접근시킬수 있도록 함.
allow all;
}
# Include the Elastic Beanstalk generated locations
include conf.d/elasticbeanstalk/*.conf;
}
}
이렇게 하면 Beanstalk에서 오는 요청들에서
- 요청을 보낸 진짜 client의 ip가 $remote_addr에 저장되며
- 악의적인 요청들중 일부는 410/ 403 처리가 되며 3, 그들은 honeypot.conf의 코드를 통해 deny 및 방화벽 블랙리스트에 작성된다.
먼저 해당 방법은GetPageSpeed의 방법을 일부 수정한것임을 밝힌다.
사실 로드밸런서를 사용하지 않는 사람이라면 위 링크의 방법을 사용하면 된다. 하지만 ELB를 사용하면
ip를 이용한 방화벽이 제대로 작동할수 없다. 이때는 Nginx의 denyp {ip}
를 이용해야 한다.
해당링크의 내용을 적용하지만 /usr/local/bin/block-ip.sh
파일은 약간 수정해야 한다.
ipv4, ipv6에 따라 /sbin/ipset, /sbin/conntract을 호출한다. 각각의 줄 다음에
echo "deny ${REMOTE_ADDR}" >> /etc/nginx/conf.d/blacklist.conf
nginx -s reload
를 추가한다. 해당 명령어를 통해 자체 blacklist에 요청을 거부할 ip를 리스트를 등록하고, 다운타임없이 nginx를 리로드 시켜 해당 ip의 요청을 더이상 받지 않도록 한다.
이렇게 하면 일시동안 원하는 봇들의 요청을 막을수 있다.
하지만 서버에 ssh 등으로 접속해서 직접 수정한 경우라면 코드 업데이트가 일어나는 순간 작성해둔 nginx.conf 파일 내용이 복구되어 문제가 생길수 있으며,
그렇지 않다 하더라도 당장 배포를 하면 honeypot.conf 파일이 없어 문제가 된다.
작성자의 경우, platform hook중 pre-build 훅을 통해 원하는 파일을 생성하여 해결했다.
파일 생성을 위한 훅
.platform/hooks/prebuild/init.sh에 다음내용을 작성했다.
#!/bin/bash
# modules
if [[ ! -d "/home/webapp/dl.fedoraproject.org" ]]; then
sudo wget -r --no-parent -A 'epel-release-*.rpm' http://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/;
sudo rpm -Uvh dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-*.rpm;
sudo yum-config-manager --enable epel*;
sudo yum update -y --skip-broken;
if ! command -v "dpkg" &> /dev/null; then
sudo yum install dpkg -y;
fi
if ! command -v "fcgiwrap" &> /dev/null; then
sudo yum -y install fcgiwrap;
sudo systemctl start fcgiwrap@nginx.socket;
sudo systemctl enable fcgiwrap@nginx.socket;
fi
sudo yum -y install conntrack-tools;
if ! command -v "firewalld" &> /dev/null; then
sudo yum -y install firewalld;
fi
# letsEncrypt
if ! command -v "certbot" &> /dev/null; then
sudo yum install -y certbot python2-certbot-nginx;
sudo yum install -y certbot-dns-route53;
.... 원하는 cert 생성방법
fi
# honeypot 파일
if [[ ! -d "/etc/nginx/includes" ]]; then
sudo mkdir /etc/nginx/includes
fi
if [[ ! -f "/etc/nginx/includes/honeypot.conf" ]]; then
sudo touch /etc/nginx/includes/honeypot.conf;
sudo echo $"fastcgi_intercept_errors off;" >> /etc/nginx/includes/honeypot.conf;
sudo echo $"include /etc/nginx/fastcgi_params;" >> /etc/nginx/includes/honeypot.conf;
sudo echo $"fastcgi_param SCRIPT_FILENAME /usr/local/libexec/block-ip.cgi;" >> /etc/nginx/includes/honeypot.conf;
sudo echo $"fastcgi_pass unix:/run/fcgiwrap/fcgiwrap-nginx.sock;" >> /etc/nginx/includes/honeypot.conf;
fi
# cgi 파일
if [[ ! -f "/usr/local/libexec/block-ip.cgi" ]]; then
sudo touch /usr/local/libexec/block-ip.cgi;
sudo echo $"#!/bin/bash" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"echo \"Status: 410 Gone\"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"echo \"Content-type: text/plain\"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"echo" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"echo \"Bye bye, \$REMOTE_ADDR!\"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"sudo /usr/local/bin/block-ip.sh" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"exit 0" >> /usr/local/libexec/block-ip.cgi;
sudo chmod 0755 /usr/local/libexec/block-ip.cgi;
fi
# sh 파일
if [[ ! -f "/usr/local/bin/block-ip.sh" ]]; then
sudo touch /usr/local/bin/block-ip.sh;
sudo echo $"#!/bin/bash" >> /usr/local/bin/block-ip.sh;
sudo echo $"" >> /usr/local/bin/block-ip.sh;
sudo echo $"if [[ -z \${REMOTE_ADDR} ]]; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" if [[ -z \"\$1\" ]]; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" echo \"REMOTE_ADDR not set!\"" >> /usr/local/bin/block-ip.sh;
sudo echo $" exit 1" >> /usr/local/bin/block-ip.sh;
sudo echo $" else" >> /usr/local/bin/block-ip.sh;
sudo echo $" REMOTE_ADDR=\$1" >> /usr/local/bin/block-ip.sh;
sudo echo $" fi" >> /usr/local/bin/block-ip.sh;
sudo echo $"fi" >> /usr/local/bin/block-ip.sh;
sudo echo $"" >> /usr/local/bin/block-ip.sh;
sudo echo $"# Put space separate list of trusted IP addresses, not to lock yourself out if you like to test the honeypot! \:\)" >> /usr/local/bin/block-ip.sh;
sudo echo $"TRUSTED_IPS=(127.0.0.1)" >> /usr/local/bin/block-ip.sh;
sudo echo $"if printf '%s\n' \${TRUSTED_IPS[@]} | grep -q -P \"^\$REMOTE_ADDR\\\$\"; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" echo \"Trusted IP\"" >> /usr/local/bin/block-ip.sh;
sudo echo $" exit 0" >> /usr/local/bin/block-ip.sh;
sudo echo $"fi" >> /usr/local/bin/block-ip.sh;
sudo echo $"" >> /usr/local/bin/block-ip.sh;
sudo echo $"if [[ \"\$REMOTE_ADDR\" != \"\${1#*[0-9].[0-9]}\" ]]; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" /sbin/ipset add honeypot4 \${REMOTE_ADDR}" >> /usr/local/bin/block-ip.sh;
sudo echo $" /sbin/conntrack -D -s \${REMOTE_ADDR}" >> /usr/local/bin/block-ip.sh;
sudo echo #" echo "deny ${REMOTE_ADDR};" >> /etc/nginx/conf.d/blacklist.conf;" >>/usr/local/bin/block-ip.sh;
sudo echo #" nginx -s reload" >>/usr/local/bin/block-ip.sh;
sudo echo $"elif [[ \"\$REMOTE_ADDR\" != \"\${1#*:[0-9a-fA-F]}\" ]]; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" /sbin/ipset add honeypot6 \${REMOTE_ADDR}" >> /usr/local/bin/block-ip.sh;
sudo echo $" /sbin/conntrack -D -s \${REMOTE_ADDR}" >> /usr/local/bin/block-ip.sh;
sudo echo #" echo "deny ${REMOTE_ADDR};" >> /etc/nginx/conf.d/blacklist.conf;" >>/usr/local/bin/block-ip.sh;
sudo echo #" nginx -s reload" >>/usr/local/bin/block-ip.sh;
sudo echo $"else" >> /usr/local/bin/block-ip.sh;
sudo echo $" echo \"Unrecognized Ip format '\$1'\"" >> /usr/local/bin/block-ip.sh;
sudo echo $"fi" >> /usr/local/bin/block-ip.sh;
fi
if [[ ! -f "/etc/nginx/conf.d/blacklist.conf" ]]; then
sudo touch /etc/nginx/conf.d/blacklist.conf
fi
# nginx 권한
if [[ ! -f "/etc/sudoers.d/nginx-block-ip" ]]; then
sudo echo $"Defaults!/usr/local/bin/block-ip.sh env_keep=REMOTE_ADDR" >> /etc/sudoers.d/nginx-block-ip;
sudo echo $"nginx ALL=(ALL) NOPASSWD: /usr/local/bin/block-ip.sh" >> /etc/sudoers.d/nginx-block-ip;
fi
if pgrep -x "firewalld" > /dev/null;
then
firewall-cmd --reload
else
systemctl start firewalld
fi
fi
pgrep 명령어를 이용해서 프로그램이 실행중이지 않으면 실행시키고 -f 명령어를 이용해 이미 작성한 sh, cgi 파일에 더이상 변경이 없도록 하고, -d 명령어를 이용해 존재하지 않는 폴더를 생성하고 command -v 명령어를 이용해 프로그램 설치여부를 판단한다. (일부 프로그램 미지원)
그리고 git을 이용한다면 git 명령어를 이용해 +x 권한을 줘야한다.
각 파일의 권한을 확인하기 위해서는
git ls-files -s
위 명령어를 통해 platform hook 파일들의 권한을 확인할 수 있다. 만약 훅 파일 권한이 100644로되어 있다면 빈즈토크에서 permission이 없다고 에러를 리턴할 수도 있다.
혹시 모르니 아래 명령어를 이용해 +x,100755 권한을 파일에 부여하자. 빈즈토크에게 실행권한을 주는것이다.
git update-index --chmod=+x .platform/hooks/prebuild/*.sh
변경내역을 commit 하기 직전에 해당 명령어를 로컬 터미널에서 입력해주고 커밋 및 푸시를 진행하면 된다. 중간에 git add .등을 이용해서 파일을 추가하면 권한이 다시 100644로 돌아간다.
이제 배포를 진행하고 blacklist.conf 파일에 차곡차곡 쌓여가는 방화벽 및 nginx ip 블랙리스트를 구경하면 된다.
21년 12월 17일 추가 위에 코드에서 추가해야할 코드를 적는다.
우선 block-ip.sh 부분에서
echo #"....
요부분들이 있을텐데, 해당부분이 주석처리가 되어 엔진엑스가 deny할 블랙리스트를 업데이트 시키지 못한다.
echo #"
에서 #
을 모두 지워주자.
또한 막상배포를 하면 502에러가 나올것이다. 방화벽에서 접속을 거부하고 있는 상태다.
위 prebuild 훅인 init.sh 의 내용을 아래 코드로 바꿔주자.
#!/bin/bash
# modules
if [[ ! -d "/home/webapp/dl.fedoraproject.org" ]]; then
sudo wget -r --no-parent -A 'epel-release-*.rpm' http://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/;
sudo rpm -Uvh dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-*.rpm;
sudo yum-config-manager --enable epel*;
sudo yum update -y --skip-broken;
if ! command -v "dpkg" &> /dev/null; then
sudo yum install dpkg -y;
fi
if ! command -v "fcgiwrap" &> /dev/null; then
sudo yum -y install fcgiwrap;
sudo systemctl start fcgiwrap@nginx.socket;
sudo systemctl enable fcgiwrap@nginx.socket;
fi
sudo yum -y install conntrack-tools;
if ! command -v "firewalld" &> /dev/null; then
sudo yum -y install firewalld;
firewall-cmd --permanent --new-ipset=honeypot4 --type=hash:ip --option=maxelem=1000000 --option=family=inet --option=hashsize=4096
firewall-cmd --permanent --new-ipset=honeypot6 --type=hash:ip --option=maxelem=1000000 --option=family=inet6 --option=hashsize=4096
firewall-cmd --permanent --zone=drop --add-source=ipset:honeypot4
firewall-cmd --permanent --zone=drop --add-source=ipset:honeypot6
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload
fi
# letsEncrypt
if ! command -v "certbot" &> /dev/null; then
sudo yum install -y certbot python2-certbot-nginx;
sudo yum install -y certbot-dns-route53;
로컬 ssl이 필요한경우, lets encrypt 관련 cert 발급 멸령어를 찾아서 작성한다.
fi
# honeypot 파일
if [[ ! -d "/etc/nginx/includes" ]]; then
sudo mkdir /etc/nginx/includes
fi
if [[ ! -f "/etc/nginx/includes/honeypot.conf" ]]; then
sudo touch /etc/nginx/includes/honeypot.conf;
sudo echo $"fastcgi_intercept_errors off;" >> /etc/nginx/includes/honeypot.conf;
sudo echo $"include /etc/nginx/fastcgi_params;" >> /etc/nginx/includes/honeypot.conf;
sudo echo $"fastcgi_param SCRIPT_FILENAME /usr/local/libexec/block-ip.cgi;" >> /etc/nginx/includes/honeypot.conf;
sudo echo $"fastcgi_pass unix:/run/fcgiwrap/fcgiwrap-nginx.sock;" >> /etc/nginx/includes/honeypot.conf;
fi
# cgi 파일
if [[ ! -f "/usr/local/libexec/block-ip.cgi" ]]; then
sudo touch /usr/local/libexec/block-ip.cgi;
sudo echo $"\#!/bin/bash" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"echo \"Status: 410 Gone\"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"echo \"Content-type: text/plain\"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"echo" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"echo \"Bye bye, \$REMOTE_ADDR!\"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"sudo /usr/local/bin/block-ip.sh" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"" >> /usr/local/libexec/block-ip.cgi;
sudo echo $"exit 0" >> /usr/local/libexec/block-ip.cgi;
fi
sudo chmod 755 /usr/local/libexec/block-ip.cgi;
# sh 파일
if [[ ! -f "/usr/local/bin/block-ip.sh" ]]; then
sudo touch /usr/local/bin/block-ip.sh;
sudo echo $"\#!/bin/bash" >> /usr/local/bin/block-ip.sh;
sudo echo $"" >> /usr/local/bin/block-ip.sh;
sudo echo $"if [[ -z \${REMOTE_ADDR} ]]; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" if [[ -z \"\$1\" ]]; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" echo \"REMOTE_ADDR not set!\"" >> /usr/local/bin/block-ip.sh;
sudo echo $" exit 1" >> /usr/local/bin/block-ip.sh;
sudo echo $" else" >> /usr/local/bin/block-ip.sh;
sudo echo $" REMOTE_ADDR=\$1" >> /usr/local/bin/block-ip.sh;
sudo echo $" fi" >> /usr/local/bin/block-ip.sh;
sudo echo $"fi" >> /usr/local/bin/block-ip.sh;
sudo echo $"" >> /usr/local/bin/block-ip.sh;
sudo echo $"# Put space separate list of trusted IP addresses, not to lock yourself out if you like to test the honeypot! \:\)" >> /usr/local/bin/block-ip.sh;
sudo echo $"TRUSTED_IPS=(127.0.0.1)" >> /usr/local/bin/block-ip.sh;
sudo echo $"if printf '%s\n' \${TRUSTED_IPS[@]} | grep -q -P \"^\$REMOTE_ADDR\\\$\"; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" echo \"Trusted IP\"" >> /usr/local/bin/block-ip.sh;
sudo echo $" exit 0" >> /usr/local/bin/block-ip.sh;
sudo echo $"fi" >> /usr/local/bin/block-ip.sh;
sudo echo $"" >> /usr/local/bin/block-ip.sh;
sudo echo $"if [[ \"\$REMOTE_ADDR\" != \"\${1\#*[0-9].[0-9]}\" ]]; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" /sbin/ipset add honeypot4 \${REMOTE_ADDR}" >> /usr/local/bin/block-ip.sh;
sudo echo $" /sbin/conntrack -D -s \${REMOTE_ADDR}" >> /usr/local/bin/block-ip.sh;
sudo echo " echo \"deny \${REMOTE_ADDR};\" >> /etc/nginx/conf.d/blacklist.conf;" >>/usr/local/bin/block-ip.sh;
sudo echo " nginx -s reload" >>/usr/local/bin/block-ip.sh;
sudo echo $"elif [[ \"\$REMOTE_ADDR\" != \"\${1#*:[0-9a-fA-F]}\" ]]; then" >> /usr/local/bin/block-ip.sh;
sudo echo $" /sbin/ipset add honeypot6 \${REMOTE_ADDR}" >> /usr/local/bin/block-ip.sh;
sudo echo $" /sbin/conntrack -D -s \${REMOTE_ADDR}" >> /usr/local/bin/block-ip.sh;
sudo echo " echo \"deny \${REMOTE_ADDR};\" >> /etc/nginx/conf.d/blacklist.conf;" >>/usr/local/bin/block-ip.sh;
sudo echo " nginx -s reload" >>/usr/local/bin/block-ip.sh;
sudo echo $"else" >> /usr/local/bin/block-ip.sh;
sudo echo $" echo \"Unrecognized Ip format '\$1'\"" >> /usr/local/bin/block-ip.sh;
sudo echo $"fi" >> /usr/local/bin/block-ip.sh;
fi
if [[ ! -f "/etc/nginx/conf.d/blacklist.conf" ]]; then
sudo touch /etc/nginx/conf.d/blacklist.conf
fi
# nginx 권한
if [[ ! -f "/etc/sudoers.d/nginx-block-ip" ]]; then
sudo echo $"Defaults!/usr/local/bin/block-ip.sh env_keep=REMOTE_ADDR" >> /etc/sudoers.d/nginx-block-ip;
sudo echo $"nginx ALL=(ALL) NOPASSWD: /usr/local/bin/block-ip.sh" >> /etc/sudoers.d/nginx-block-ip;
fi
if pgrep -x "firewalld" > /dev/null;
then
firewall-cmd --reload
else
systemctl start firewalld
fi
fi
그리고 내친김에 80포트요청을 443(https)로 바꾸는 설정파일도 공유한다.
아래 파일을 .platform/nginx/nginx.conf에 작성해두고 배포하면 .ebextenstions 폴더에서 프록시 설정을 할 필요가 없다.(개인적) 그리고 인스턴스에 SSL 인증서를 가지고 있는경우, 이를 사용하여 로컬 SSL도 가능하며, 80포트의 요청을 443 https로 리디렉션시키고, 이와중 ELB 헬스체커의 요청은 200을 리턴하게끔 한다.
#Elastic Beanstalk Nginx Configuration File
user nginx;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 32781;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
proxy_set_header X-Real-IP $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-For $remote_addr;
set_real_ip_from 172.31.0.0/16;
real_ip_recursive on;
real_ip_header X-Forwarded-For;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf;
# 사실 이 블럭도 필요 없을지도 모른다. beanstalk의 nginx/conf.d/elasticbeanstalk/00... 파일과 내용이 겹침
upstream nodejs {
server 127.0.0.1:4000;
keepalive 256;
}
access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
access_log /var/log/nginx/access.log main;
add_header Set-Cookie "Path=/; HttpOnly; Secure";
# 여기는 엔진엑스의 바디 크기 제한을 50MB까지 늘리는 코드
client_max_body_size 50M;
# 여기는 봇들을 걸러내기 위함. 봇들은 호스트가 없거나 ip주소다.
map $http_host $default_host_match {
원하는.도메인 1;
example.com 1;
default 0;
}
map $http_upgrade $connection_upgrade {
default "upgrade";
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /YOUR/CERT.PEM/LOCATION.pem;
ssl_certificate_key /YOUR/KET.PEM/LOCATION.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "어떤+값들이:있지만+혹시나:하여+숨깁니다:검색해+보세요!";
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log main;
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip on;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
set $year $1;
set $month $2;
set $day $3;
set $hour $4;
}
location ~ (/\..*|^/bao|auth) {
include /etc/nginx/includes/honeypot.conf;
}
error_page 403 410 = @honeypot;
set $set 1;
if ($default_host_match = 0 ) {
set $set "${set}1";
}
if ($http_user_agent ~ ^(ELB-HealthChecker\/|IamportNotification)) {
# if ($default_host_match = 0) {
set $set "${set}1";
}
if ($set = 11) {
return 410;
}
set $post_root 0;
if ($request_uri = \/ ) {
set $post_root post;
}
if ($request_method = POST) {
set $post_root "${post_root}_root";
}
if ($request_uri = /health) {
return 410;
}
if ($post_root = post_root) {
return 410;
}
location @honeypot {
include /etc/nginx/includes/honeypot.conf;
allow all;
}
include conf.d/elasticbeanstalk/00_application.conf;
}
server {
listen 80;
listen [::]:80;
server_name example.com;
access_log /var/log/nginx/access.log main;
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip on;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
set $year $1;
set $month $2;
set $day $3;
set $hour $4;
}
location ~ (/\..*|^/bao|auth) {
include /etc/nginx/includes/honeypot.conf;
}
error_page 403 410 = @honeypot;
set $set 1;
if ($http_user_agent = ELB-HealthChecker/2.0) {
return 200;
}
if ($default_host_match = 0 ) {
set $set "${set}1";
}
if ($http_user_agent ~ ^(ELB-HealthChecker\/|IamportNotification)) {
# if ($default_host_match = 0) {
set $set "${set}1";
}
if ($set = 11) {
return 410;
}
set $post_root 0;
if ($request_uri = \/ ) {
set $post_root post;
}
if ($request_method = POST) {
set $post_root "${post_root}_root";
}
if ($request_uri = /health) {
return 410;
}
if ($post_root = post_root) {
return 410;
}
location @honeypot {
include /etc/nginx/includes/honeypot.conf;
allow all;
}
# 요청을 443 포트로 리디렉션
return 301 https://$host$requrest_uri;
# Include the Elastic Beanstalk generated locations
include conf.d/elasticbeanstalk/*.conf;
}
}
댓글남기기