23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
보통 ingress(inbound)만 열심히 방어하지 egress(outbound)까지 막아야되는 경우는 잘 없죠.
보안이라는게 참 비용도 많이 들고 요구 사항을 모두 커버하는 네트워크를 구성하기가 참 쉽지가 않습니다.
특히 기존에 서비스를 운영하고 있다면 기존 아키텍처를 호환/유지해야되는데
SW라면 모를까 하필 최하단의 네트워크를 건들기란 참 쉽지 않은 길입니다.
하지만 저는 그 길을 걸어보았고
그 여정에 대한 발자취를 공유하고자 합니다.
그럼 수요 없는 공급인 centralized egress vpc를 향한 여정 출발합니다.
AWS를 사용하면 전공수업 잘 들어둘껄이란 생각을 하게 되는 서비스인 VPC입니다.
처음에 아무런 지식 없이 AWS에서 MVP레벨의 서비스를 구축하게 되면 요런 모습을 가집니다.
저도 처음 AWS를 사용했을때 이렇게 만들었던거 같네요 ㅎㅎ...
그냥 EC2에 Elastic IP(EIP)를 달고 도메인 연결하고
spring mvc, tomcat, mysql, redis 다 때려박아서 서비스를 했던 기억이 있습니다.
당연히 이렇게 된다면 EC2를 재시작하거나 트래픽이 몰리거나 DB에 부하가 생기면 서비스에 장애가 생기겠죠?
그래서 Scaling 가능한 구조로 바꾸기 위해서 AWS에서 제공해주는 Managed Service를 도입하게 됩니다.
가장 처음으로 도입하는게 Elastic Load Balancer(ELB)와 Auto Scaling Group, RDS, ElastiCache이죠.
Auto Scaling Group을 사용해서 웹서버인 EC2를 Scaling하고
ELB 서비스에서 제공하는 ALB(Application Load Balancer)를 사용해서 Scaling되는 EC2를 라우팅해주고
RDS, ElastiCache를 사용해서 DB를 Scaling하는 아주 기초적인 구조입니다.
잘동작하고 돈만 많다면(?) 기존 구조로도 안정적으로 서비스할 수 있겠죠?
하지만 Auto Scaling Group에 있는 EC2와 RDS, ElastiCache 등이 외부에 노출되기 때문에
보안을 위해서 그 다음 스탭으로는 아래와 같이 네트워크를 분리해서 구성합니다.
외부에 서버나 DB를 직접 노출하지 않기 위해서죠.
위처럼 구성한다면 EC2, ElastiCache, RDS 등에 EIP(=Public IP)를 주지 않아도 되고
그러면 외부에서 서버에 접근할 방법이 없기 때문에 안전합니다.
다만 이러면 EC2는 Public IP가 없이 때문에 인터넷이 안되는데
EC2의 인터넷 통신을 위해서 중간에 NAT Gateway(NAT)를 두고 EIP를 할당해서
EC2는 NAT를 통해서 Public IP를 할당 받아서 인터넷 통신을 할 수 있게 됩니다.
그래서 보통 Public Subnet에는 NAT와 ELB만 두고
Private Subnet에는 웹서버 역할을 하는 EC2만 두고
DB Subnet에는 DB만 둬서 외부 공격에 대해 네트워크 레벨로 방어진을 구성합니다.
좀 더 신경쓰면
ALB 앞단에 NLB(Network Load Balancer)를 둬서 정적IP를 사용자에게 제공할 수 있습니다.
이러면 사용자가 접근해야되는 시스템에서 방화벽에 IP를 등록해야된다고 하면 우린 NLB의 EIP를 알려주면 되죠.
대신 기존에 ALB에 연결했던 도메인을 NLB로 옮겨줘야되긴 합니다.
보안이란게 100%는 불가능하지만
지금 구조라면 약간 아쉬운점이 있습니다.
바로 가장 기본적인 DDoS나 SQL Injection, 악성 봇 등에 대한 방어가 없죠!!
ORM만 써도 SQL Injection을 막을 순 있다곤 하지만
그건 정말 기본적인 안전장치이고 네트워크 레벨에서 1차적으로 막아주는게 가장 중요합니다.
그래서 도입하게 되는게 바로 WAF(Web Application Firewall)라는 서비스죠.
서드파티 방화벽을 쓰는 경우도 보긴 했는데 WAF 정도만 달아줘도 대부분의 공격을 잘 막아주긴 합니다.
다만 비싼게 흠이긴한데 DDoS 한번 당해보면 싸다고 느껴지실겁니다.
요금제는 트래픽당 과금 방식이라 대충 얼마 나온다고 예측하기 힘들기 때문에 직접 보고 판단하시는게 좋습니다.
아래 링크에서 요금 예시를 보면 생각보다 얼마 안하네?라고 생각하실텐데
실제 워크로드에 적용해보시면 어마어마하게 나올겁니다 ㅎㅎ...
(특히 B2C 서비스라면 더더욱...)
제가 예전 언급했던 내용이지만
outbound가 열려있다면 해커 입장에서는 1번이라도 뚫어내면 데이터를 다 빼갈 수 있습니다.
제목은 "Gradle 프로젝트 빌드하기"이지만
Log4j 취약점이나 공급망 공격에 대해서 언급한 내용이 있으니 참고하시면 좋을듯합니다.
어쨋든 요점은 해커 입장에서는 악성코드를 1번이라도 심을 기회가 생겨서 심는다면
outbound가 다 열려있는 상태라면 해당 프로그램을 통해서 데이터를 빼갈 수 있습니다.
대표적인게 최근 유행(?)하고 있는게 eBPF죠.
eBPF는 github에 공개된 오픈소스이고 커널 레벨에서 실행되기 때문에
어떤 프로세스가 어떤 파일에 접근하고 네트워크 통신을 하는지 감시할 수 있습니다.
그렇다면 우리가 구축한 아키텍처에서 EC2의 outbound만 볼까요?
예를 들어서 EC2에서 git push/pull하거나 docker push/pull, slack api 호출 등이 outbound에 해당되죠.
보통 security group의 outbound는 기본이 any open(=0.0.0.0/0 all port 허용)이기 때문에
이제까지 문제 없이 사용하고 계셨을겁니다.
(물론 inbound는 22, 443, 80 정도만 열어놓고 사용하셨겠죠?)
딱봐도 불안해보이죠...?
만약 사용자가 업로드한 첨부파일이 악성코드였고 그게 EC2에 설치가 됐다면?
SQL Injection 공격으로 명령어를 삽입해서 RCE(Remote code execution) 공격을 시도했다면?
공격 당한 EC2 디스크에 있는 데이터는 물론이고
해당 EC2에서 접근 가능한 DB까지 싹싹 긁어서 해커의 서버로 퍼다 날랐을겁니다.
그렇다고 outbound를 무작정 막자니 애매한 부분이 있습니다.
security group은 IP기반으로만 허용이 가능해서 특정IP를 차단하기엔 어려움이 있습니다.
예를 들어서 IPv4에서 1개의 IP만 차단하려면
security group은 차단할 IP 1개를 제외한 약 43억 개 IP를 등록해야되거든요.
하지만 애초에 security group은 inbound/outbound 각 60개가 제한량이라 이런 방식은 불가능합니다.
(물론 신청하면 더 늘려주긴 하는데 굳이 추천하는 방법은 아닙니다.)
그래서 내가 정말 사용하고 있는 IP/Port를 정확히 알아내서 outbound로 등록해야되는데
IP를 알아내기도 어렵지만 무슨 Port를 쓰는지도 파악하기 참 쉽지 않습니다.
당연히 http(80), https(443), ssh/ftp(22) 정도는 쓸텐데
smtp(25, 587), dns(53), ntp(123) 요런것들도 넣어줘야합니다.
근데 이제 문제는 IP가 정해져있는 경우가 아닌 DNS 기반으로 호출하는 케이스이죠.
아래처럼 hooks.slack.com 과 같은 Slack API은 그래도 다행히 고정된 IP를 가지고 있습니다.
Github 같은곳은 IP가 너무 많은 케이스도 있습니다.
GIthub meta API: https://api.github.com/meta
하지만 둘 다 리스트가 있다고 해도 언제 바뀔지 모르고
이렇게 고정된 IP를 제공해주는 서비스/회사는 그렇게 많지 않습니다.
(정작 우린 NLB로 정적IP를 제공해주고 있는데...)
그럼 어떻게 제어해야될까요...?
가장 많이 사용하는 방법은 바로 squid proxy를 사용해서 접근제어를 하는겁니다
클라이언트IP 주소나 도메인 기반으로 접근 제어를 할 수 있어서 대부분 outbound 제어하는 용도로 많이 사용합니다.
물론 원래 웹 캐싱하라고 있는데 그런 경우엔 보통 nginx쓰거나 CDN을 쓰다보니 저도 주로 접근 제어 용도로만 사용하네요;;
아무튼 squid proxy를 별도로 구성해서 아래와 같이 적용한다면 outbound도 직접 컨트롤 할 수 있습니다.
EC2가 API 서버든 Batch 서버든 outbound를 squid proxy를 바라보게 하고
squid proxy를 통해서만 외부 통신을 하도록 설정하는겁니다.
물론 squid proxy가 설치된 EC2는 DNS기반으로도 통신을 해야되기 때문에
어쩔 수 없이 security group에서 outbound를 any open 해야되지만
SW적으로 외부 통신에 대한 접근 제어를 할 수 있어서 안심할 수 있습니다.
하지만 이렇게 단일 서버로 구성하면 Scaling이 안될테니
추가적으로 squid proxy 자체도 아래처럼 ELB + Auto Scaling Group으로 구성해야되겠죠?
(보통 NLB을 쓰는 이유는 ALB는 http/https만 지원되기 때문입니다)
그럼 이렇게 하면 이제 끝일까요??
문제는 VPC가 여러개라면 VPC마다 저런걸 구축해줘야합니다.
게다가 외부 통신이 없어도 provisioning을 위해서는
Auto Scaling Group에서 EC2 1개는 유지를 해야되는 불필요한 고정 비용을 내야하죠
(+ NLB도 시간당 과금이 있죠)
게다가 이런 문제도 있습니다.
VPC가 2개일때 이렇게 따로 구축해놨더니
각 NAT마다 EIP가 달라서 불필요하게 방화벽 요청을 2번해야되거나
역시 NAT + EIP 비용을 2배로 내야된다는거죠.
(물론 분리된 네트워크니깐 NAT+EIP 비용은 원래 나가는거지만 대충 시적 허용 같은 느낌으로...)
그래서 머리를 써서 VPC Peering을 통해서 centralized egress를 구축해보기 위해 아래처럼 시도합니다.
즉, 외부 통신인 outbound(egress)를 1개의 VPC에서 모두 처리하는 방식을 써보겠다는거죠.
이러면 VPC Peering 비용이 발생하긴하지만
NAT+EIP+SquidProxy 모두 하나로 운영할 수 있어서 관리 포인트가 줄게되죠!
라는 행복한 꿈을 꾸었습니다...
VPC A를 centralized egress 전용 VPC로 사용하려고 했지만
안타깝게도 VPC A와 B의 IP 주소가 겹치는(오버랩되는) 문제가 발생하게 됩니다.
그나마 다행히 VPC C는 겹치지 않아서 Peering과 Proxy까지 연결해놨네요.
그렇다고 VPC B만 따로 구축하자니 찝찝합니다.
그래서 머리를 쥐어짜내서 아래와 같이 구축합니다.
NLB를 Public Subnet으로 보내고 EIP를 할당해서 인터넷 통신도 되게 하는거죠!
그럼 VPC B처럼 IP가 겹쳐서 VPC Peering을 할 수 없는곳은 인터넷을 통해서 NLB에 접근하고
VPC C처럼 Peering이 가능한 곳은 NLB의 Private/Public IP 맘대로 호출하는겁니다.
뭔가 어설프지만 egress 영역을 한곳에 모아서 이름도 멋있는 centralized egress VPC를 만들었네요!
근데 문제가 있습니다.
만약 위 구조에서 1GB 크기의 docker 이미지를 pull을 받는다면 어떻게 될까요?
빨간 구간인 NLB에서 1GB의 과금(GB당 $0.0225)이
파란 구간인 NAT에서 1GB의 과금(GB당 $0.059)이 중복해서 발생하게 될겁니다.
그러면 중복 과금을 피하기 위해서 squid proxy를 Public Subnet으로 올리는 판단을 한다면 어떻게 될까요?
즉, squid proxy에서 NAT를 통해서 나가는게 아니고 바로 internet gateway로 나가는거죠!
위 그림처럼 될텐데
안타깝게도 squid proxy가 NAT를 타지 않게 되면 외부 통신하는 IP가 계속 바뀌게 됩니다.
왜냐하면 Auto Scaling Group으로 생성되는 EC2는 언제 사라질지 모르거든요.
그리고 EC2가 추가될때마다 EIP가 새로 할당되기 때문에 outbound IP가 계속 변경됩니다.
즉, 고정된 IP를 유지하기 위해서는 squid proxy가 NAT를 타고 나갈 수 있도록
중복 비용이 발생하더라도 Private Subnet에 squid proxy를 두어야합니다.
물론 outbound IP가 계속 변경되어도 문제가 없다고 한다면 지금 구조를 유지하셔도 됩니다.
이러면 오히려 NAT가 없어도 되죠!
물론 외부에서 squid proxy 서버에 접근할 수 있는 문제가 있긴하지만
감안(?)한다면 큰 문제(?) 없습니다.
(분명 보안 때문에 하는거 아닌가요...)
이제 시작이죠.
이제 OS 환경부터 개발환경 코드까지 모두 proxy를 탈 수 있게 해야됩니다.
하지만 AWS라면 AWS 자체 내부통신은 proxy를 타면 안됩니다.
예를 들어서 EC2 스스로 169.254.169.254에 접근해서 메타데이터와 자격증명을 획득하는데
이건 AWS 내부적으로 일어나는 통신이기 때문에 proxy를 타면 안됩니다.
흔히 링크-로컬(Link-Local) 주소라하고 외부 네트워크에서 접근이 불가능한 IP입니다.
그래서 squid proxy를 설치한 EC2의 IP주소가 10.0.0.0이라 했을때 OS 환경 변수를 이렇게 설정해야됩니다.
# ~/.bashrc나 /etc/environment 에 넣어준다
export http_proxy=10.0.0.0:3128
export https_proxy=10.0.0.0:3128
export NO_PROXY=169.254.169.254이러면 끝나느냐...?
안타깝게도 http client 중에서 OS 설정을 무시하거나 따로 proxy 옵션을 켜줘야하는 라이브러리들이 많습니다
대표적으로 python aiohttp 패키지죠
아래처럼 옵션을 따로 줘야합니다. 모든 프로젝트에서 저런걸 다 찾아야겠죠?
그리고 요즘 docker 많이 사용하시죠?
다행히 공식 문서가 있긴한데 중간에 좀 이상한 부분이 있습니다
네 https_proxy 설정에 http로 들어가야됩니다.
뭐 그냥하면 되는거 아닌가?라고 생각하실텐데 저건 docker run 할때구요
docker build할때는 https가 들어가야됩니다.
즉, docker build/run/pull할때 약간 좀 다르게 동작해야된다는거죠.
근데 docker build하려면 이미지를 먼저 pull을 먼저 받아야하는데??
네... 그래서 해괴합니다.
그리고 당연히 dockerfile을 build하실텐데
dockerfile 내에서 npm/pip install을 해야된다면 이런것들도 proxy를 태워야합니다.
즉, 아래처럼 build args로 동적으로 처리해야된다는거죠
# Dockerfile에 추가
ARG HTTP_PROXY
ARG HTTPS_PROXY
# docker build
docker build \
--build-arg HTTP_PROXY=http://10.0.0.0:3128 \
--build-arg HTTPS_PROXY=https://10.0.0.0:3128 ...저는 여기까지 경험하고 squid proxy 도입에 대해서 그만뒀습니다.
코드를 모두 수정해야되는거뿐 아니라
CI/CD관련 코드부터 OS 환경변수까지 모두 수정해야되는데
제가 모든걸 관리하면 모를까 코드수정만 해도 하루이틀만에 할 분량도 아니고
network hop도 복잡해져서 당연히 서비스 응답속도에도 영향이 있을테고
뭔가 안된다고 하면 개발/분석가마다 개발환경까지 다 확인해줘야하는데
말이 안된다고 생각했고
그러다가 떠올린게 바로 금단의 Network Firewall이었습니다...
[to be Continued]
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.