Docker + UFW+ IPTABLES 방화벽 적용
인트로
이전에 글을 작성 할 때 Docker daemon에서{ iptables : false }를 통해 도커에 방화벽이 적용되는 예를 작성했습니다. 해당 방법에는 한 가지 문제가 있었는데요, 도커의 iptables를 끊을 경우 네트워크 연결이 되지 않았습니다.
이에 대해 저는 어떻게 해결해야 하나? 고민하던 와중에 UFW의 Routed 기능을 알게 되었고 이를 활성화 함으로써 문제 해결을 했었습니다.
이후 아무 문제 없이 평온하게 운영하고 있던 서버의 컨테이너들이 갑자기 네트워크 연결이 되지 않는 현상이 재 발현 되었습니다.
왜 이러지 ? 저번에 분명 UFW의 Routed 통해서 해결 됐었는데 ??? 라고 의문을 가졌는데요.
아직 까지 어떤 문제로 인해 발생되었는지 정확히는 알지 못합니다.
다만 “도커 서비스 재실행시 설정이 꼬이는 문제”가 발생하는 것이라 추정되는 상태입니다.
그래서 이참에 도커에 방화벽 적용하는 방법으로 좀 더 깔끔하고, 안정적이고, 좀 더 올바른 해결 방법을 모색했고 해당 글들을 찾게 되었습니다.
그런데 문제는 이 글은 IPTABLES의 활용 방법을 모르니 당최 이해하기가 쉽지 않았습니다.
오래 걸리긴 했지만 해결하게 되어 이해하기 쉽도록 설명하면서 작성해봅니다.
본문
들어가기에 앞서 우선 도커가 왜? 어떻게? “UFW방화벽을 우회하여 포트를 개방할 수 있었나”를 이해해 보겠습니다.
해당 글의 주제에서는 IPTABLES의 Filter만 신경 쓰면 되기 때문에 Filter 테이블만 확인 해 보겠습니다
우선 Docker와 UFW를 활성화 하면 IPTABLES는 어떻게 되는지 한 번 확인해 보겠습니다
다음 이미지는 Filter 테이블에 생성된 Chain 입니다.
위 이미지에 UFW와 Docker 관련된 Chain이 보입니다.
두 가지는 딱 봐도 각각 격리된 것으로 느껴집니다.
실제 구성은 어떻게 되어있을까요? 다음 코드를 작성하여 확인해봅니다.
iptables -L
IPTABLES는 등록된 순서가 중요한데요. 자세히 살펴보면 DOCKER-USER는 FORWARD Chain에만 등록된 것을 알 수 있고 순위로는DOCKER-USER가 “1순위”로 이후에 UFW가 등록된 것이 보입니다.
위와 같이 IPTABLES에 적용이 되기 때문에 도커는 FORWARD로의 접속에 대한 방화벽만을 통제하고 있기 때문에 UFW 상에 일반적인 방법으로 방화벽을 등록할 경우 제한이 걸리지 않았던 것이고 routed로 방화벽을 등록해야지 제한이 걸리게 된다는 것을 알 수 있습니다.
{iptables : false}를 할 경우 어떻게 되나?
위 옵션을 사용해서 Docker에 적용할 경우 당연히 도커가 IPTABLES를 사용하지 않기 때문에 UFW의 통제를 받을 것입니다. 방화벽의 보호는 받게 되나, 발생하는 문제는 Docker 에서 IPTABLES를 이용해 사용 가능하던 네트워크 통제, 라우팅 기능을 잃어 버리게 됩니다.
https://d-life93.tistory.com/431?category=1013222
이 때 해결 방법이 제가 작성했던 이전 글의 방법인 다음 두 가지 방법으로 문제 해결 했었습니다.
- 도커의 네트워크 연결(브리지 또는 생성한 네트워크)이 방화벽에 의해 제한되지 않도록 등록
- 라우팅 기능은 UFW의 Routed 기능을 허용함으로써 도커가 라우팅 기능이 사용 가능
문제 해결은 됐지만 원인 미상의 문제가 추가 발생했고, 위 해결 방법이 다소 깔끔하지 못하고 더 올바른 방법을 위해 다른 방법을 적용하고자 했습니다.
적용할 방법
들어가기에 앞서 사설 IP에 대해 알아보고 가야 합니다.
기존에 쓰던 IP는 IPv4로 2의 32승개 만큼의 개수가 존재했습니다.
- 4,294,967,296개(약 43억개)
하지만 각종 전자 제품, 스마트 기기들이 등장하면서 매일 매일 기하급수적인 증가로 인해 고갈 될 상황에 처하게 되어 2의 128승개를 사용하는 IPv6가 등장했습니다.
- 340,282,366,920,938,463,463,374,607,431,768,211,456개(약 ??개)
하지만 여전히 IPv4는 사용되고 있는데요
- IPv4 : 32bit
- IPv6 : 128bit
IPv4를 최대한 효율적으로 사용하기 위해 공인 IP, 사설 IP로 나누게 됩니다.
공인 IP
- 인터넷 진흥원 등의 IP 주소 할당 공인기관에서 할당한 인터넷 상에서 사용할 수 있는 전세계 유일한 IP주소
사설 IP
- 공유기와 같이 IP의 분배가 되는 기기에서 사용되는 주소로 인터넷에서는 사용할 수 없는 IP주소
사설 IP 대역은 어떻게 될까요?
다음과 같은 대역을 사설 IP 대역으로 사용하고 있습니다.
- 10.0.0.0 ~ 10.255.255.255 (10.0.0.0/8)
- 172.16.0.0 ~ 172.31.255.255 (172.16.0.0/12)
- 192.168.0.0 ~ 192.168.255.255 (192.168.0.0/16)
여기까지 IP를 알아보고 다음으로 Iptables의 Chain 별에 기능에 대해 알아보겠습니다.
- INPUT Chain - 외부에서 방화벽 서버로 들어오며 거치는 패킷
- FORWARD Chain - 서버가 목적이 아닌 다른 네트워크로 포워딩이 목적으로 방화벽을 통해 흘러가며 거치는 패킷
- OUTPUT Chain - 서버에서 외부로 나가며 거치는 패킷
여기에서 우리는 ufw-user-input 와 ufw-user-forward를 사용하는데 용도는 위 기능과 똑같이 생각하시면 되겠습니다. 풀어서 표현하자면 다음과 같은 의미로 느껴집니다.
- ufw-user-input - 서버에 SSH 접속용 허용 또는 제한을 위한 IP 또는 PORT를 등록
- ufw-user-forward - 라우터의 입력 포트에서 출력 포트로 패킷을 이동할 IP 또는 PORT를 등록
저는 두 가지 모두 사용 했습니다.
도커와 관련된 접근을 막기 위해서 ufw-user-forward를 사용 했고
도커 외의 접근을 막기 위해서 ufw-user-input를 사용했습니다.
https://github.com/chaifeng/ufw-docker 에 나오는 예제를 그대로 사용했었는데요
일부 잘못된 코드가 있어 에러가 발생 했었습니다.
(제가 뭔가 설치 안 해서 발생했을 수 있겠지만 ㅎㅎ;)
그래서 저는 다음과 같이 필요 없는 것 버리고 아주 아주 약간 수정해서 테스트 해봤습니다.
sudo vim /etc/ufw/after.rules
#
# rules.input-after
#
# Rules that should be run after the ufw command line added rules. Custom
# rules should be added to one of these chains:
# ufw-after-input
# ufw-after-output
# ufw-after-forward
#
# Don't delete these required lines, otherwise there will be errors
*filter
:ufw-after-input - [0:0]
:ufw-after-output - [0:0]
:ufw-after-forward - [0:0]
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
# End required lines
# don't log noisy services by default
-A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input
-A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input
-A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input
# don't log noisy broadcast
-A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input
# docker
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT
기존 UFW 설정 코드에서 이번에 추가된 사항을 뜯어보겠습니다.
- 사용할 Chain 등록? import 하는 느낌입니다.
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
- 최초 docker에 접근하게 되면 ufw-user-forward Chain으로 Jump 하여 ufw-user-forward에 등록된 허용, 제한 목록의 통제를 받습니다.
-A DOCKER-USER -j ufw-user-forward
- 외부에서 docker에 접근 할 때 사용한 IP가( -s ) 아래 설정한 IP 대역에 포함되면 RETURN 즉 COMMIT으로 Jump하여 방화벽 검증을 끝냅니다.
아래 IP 대역은 사설 네트워크 대역이며, 사설 네트워크에서 접속한 것은 모두 넘깁니다.
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
- 프로토콜 타입 udp로 -m 확장 모듈은 (해당 프로토콜 기본값 : udp) 이고 시작 포트가 53이고 도착지 포트가 1024 ~ 65535 포트일 경우 RETURN Jump하여 방화벽 검증을 끝냅니다.
53번 포트는 DNS용 포트입니다
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
- 모든 공용 네트워크에 의해 시작된 연결 요청을 차단하지만 내부 네트워크가 외부 네트워크에 액세스할 수 있도록 허용합니다. 위에 있는 ufw-user-forward에서 허용되지 못했을 경우 여기에서 걸려서 패킷이 드랍됩니다.
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
여기서 잠깐!
udp는 왜 포트 번호 0 ~ 32767번까지만 DROP 시킬까? 궁금해야 합니다.
udp 프로토콜로 통신을 사용하게 될 경우 32768 ~ 60999 번 포트까지 범위에서 랜덤으로 포트를 선택해서 통신을 보내게 되어있습니다.
해당 내용은 다음 위치에 등록되어 있습니다
vim **/proc/sys/net/ipv4/ip_local_port_range**
위에 까지 글을 토대로 간단하게 정리해 보자면 다음과 같습니다.
- 도커로 접근을 하면
- ufw-user-forward 에서 1차로 IP 또는 Port번호를 식별해서 Allow, Deny 조치합니다.
- 사설 네트워크 대역에서 접근한 것이라면 ? Ok! 들어와! 하이패스 시켜줍니다.
- 위에서 부터 1차로 FORWARD 거르고, 2차로 사설 네트워크인 것을 걸렀는데 여기까지 왔네? 도커 컨테이너를 이용하겠다고? 안돼! 입장불가! DROP
위와 같은 플로우로 흐르게 되어 도커에 올바른 방화벽 적용이 가능하게 된 것입니다.
UFW 설정 적용을 위한 재 시작
위와 같이 작성하고 다음 명령을 사용해서 설정을 적용 했었는데요
sudo ufw reload
저는 첫번째는 사용했을 때는 문제 없이 작동되었는데,
이상하게도 두 번째 사용하면 UFW가 종료 및 재시작 하는 도중에 오류가 발생해서 시작을 못했습니
이렇게 될 경우 https://d-life93.tistory.com/463 의 방법을 사용해야 했습니다
위와 같은 문제로 인해 다음 명령으로 적용 테스트 해봤는데, 에러가 발생하지 않았습니다.
아래 명령은 UFW 서비스 자체를 재 시작 하는 명령입니다.
reload랑 restart랑 기능의 차이는 있겠지만 설정한 옵션이 적용된다는 것은 똑같을 것인데... 아이러니 합니다.
systemctl restart ufw
위와 같이 기본 설정을 마친 후 ufw-user-input과 ufw-user-forward 에서 허용할 또는 제한할 IP 와 포트 번호를 등록해 줍니다.
ufw-user-input 등록 예시
ufw-user-input은 DOCKER-USER에서 사용하지 않기 때문에 기본적으로 도커에 대한 허용 및 제한을 관여하지는 않습니다.
# 접속한 주소가 100.0.0.1/16 대역에 포함되면 모든 서비스에 접근 가능 (도커 상관없음)
ufw allow from x.x.x.x/16 to any
# 접속한 주소가 101.0.0.1 이면 접근 제한 (도커 상관없음)
ufw deny from 101.0.0.1
# 접속하는 포트가 80 포트이고 tcp 프로토콜이면 허용 (도커 상관없음)
ufw allow 80/tcp
ufw-user-forward 등록 예시
위와 크게 다를 것은 없습니다. 다만 두 가지를 유의할 사항을 작성하겠습니다.
# forward를 등록하는 경우 다음 예시와 같이 route가 포함돼야 합니다.
ufw route deny 5000
# forward를 등록하는 경우 도커 컨테이너 생성 시 호스트 포트와 컨테이너 포트를 설정 할 텐데, 지정 한 포트 중 '컨테이너 포트'로 등록을 해줘야 합니다.
# 예시 : 포트번호 20000:5000 이라면 5000번 포트를 허용해야 접속이 가능합니다.
ufw route allow 5000
마무리
아무 지식 없이 매번 방화벽에, 네트워크에 치이다가. 정보처리산업기사 시험 준비 하면서 알게 된 지식과 해당 문제를 해결하기 위해 공부하면서 쌓은 지식을 짬뽕 시켜서 해결해 본 경험을 토대로 다른 분들도 이해하고 적용 가능 할 수 있도록 해보고 싶어서 정리해 봤습니다.
위 예시에 설명이 포함되어 있지만 만약 처음 사용해보려 하시는 분이라면 이해가 되실지 모르겠습니다. 이 방법을 사용하기 위해서는 Docker, UFW, IPTABLES의 사용 방법 그리고 네트워크에 대한 이해를 어느 정도 가지고 있어야 한다고 생각합니다.
네트워크와 방화벽 꾀나 재미있고 개발자로 살아가면서 많은 도움이 되는 부분인 것 같습니다.
한 번 공부해보시고 적용해 보시는 것도 좋은 방법이라 생각합니다.
'Programming > Infrastructure' 카테고리의 다른 글
Docker + UFW 적용 문제와 해결 방법 (2) | 2022.03.15 |
---|