서버 간의 트래픽을 보호하기 위해 Iptables 방화벽을 설정하는 방법
소개
애플리케이션 설정의 개별 구성 요소를 다른 노드에 배포하는 것은 로드를 줄이고 수평 확장을 시작하는 일반적인 방법입니다. 일반적인 예는 애플리케이션과 별도의 서버에 데이터베이스를 구성하는 것입니다. 이 설정에는 많은 이점이 있지만 네트워크를 통해 연결하면 새로운 보안 문제가 발생합니다.
이 가이드에서는 분산 설정에서 각 서버에 방화벽을 설정하는 방법을 보여줍니다. 다른 트래픽은 거부하면서 구성 요소 간에 의도된 트래픽을 허용하도록 정책을 구성합니다.
DigitalOcean 인프라에서 서버에 대한 추가 외부 계층으로 실행되는 DigitalOcean의 클라우드 방화벽을 구성할 수도 있습니다. 이렇게 하면 서버 자체에 방화벽을 구성할 필요가 없습니다.
이 가이드의 데모에서는 두 개의 Ubuntu 22.04 서버를 사용합니다. 하나는 Nginx와 함께 웹 애플리케이션을 제공하고 다른 하나는 애플리케이션용 MySQL 데이터베이스를 호스팅합니다. 이 설정을 예로 사용하겠지만, 관련된 기술을 추정하여 자신의 서버 요구 사항에 맞출 수 있어야 합니다.
전제 조건
시작하려면 두 개의 새로운 Ubuntu 22.04 서버가 있어야 합니다. 각각에 대한 sudo
권한이 있는 일반 사용자 계정을 추가합니다. 이렇게 하려면 Ubuntu 22.04 초기 서버 설정 가이드를 따르세요.
우리가 확보할 애플리케이션 설정은 이 가이드를 기반으로 합니다. 해당 예제를 따라 하려면 해당 자습서에 표시된 대로 애플리케이션 및 데이터베이스 서버를 설정하십시오. 그렇지 않으면 이 문서를 일반 참조로 사용할 수 있습니다.
1단계 - 방화벽 설정
먼저 각 서버에 대한 기본 방화벽 구성을 구현합니다. 우리가 시행할 정책은 보안 우선 접근 방식을 취합니다. 우리는 SSH 트래픽 이외의 거의 모든 것을 잠그고 특정 애플리케이션을 위해 방화벽에 구멍을 뚫을 것입니다.
이 가이드는 iptables
구문을 따릅니다. iptables
는 nftables
백엔드를 사용하여 Ubuntu 22.04에 자동으로 설치되므로 추가 패키지를 설치할 필요가 없습니다.
nano
또는 선호하는 텍스트 편집기를 사용하여 /etc/iptables/rules.v4
파일을 엽니다.
- sudo nano /etc/iptables/rules.v4
방화벽 템플릿 가이드에서 구성을 붙여넣습니다.
*filter
# Allow all outgoing, but drop incoming and forwarding packets by default
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Custom per-protocol chains
:UDP - [0:0]
:TCP - [0:0]
:ICMP - [0:0]
# Acceptable UDP traffic
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
# Acceptable ICMP traffic
# Boilerplate acceptance policy
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i lo -j ACCEPT
# Drop invalid packets
-A INPUT -m conntrack --ctstate INVALID -j DROP
# Pass traffic to protocol-specific chains
## Only allow new connections (established and related should already be handled)
## For TCP, additionally only allow new SYN packets since that is the only valid
## method for establishing a new TCP connection
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
-A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP
# Reject anything that's fallen through to this point
## Try to be protocol-specific w/ rejection message
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-proto-unreachable
# Commit the changes
COMMIT
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
*security
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
파일을 저장하고 닫습니다. nano
를 사용하는 경우 Ctrl+X
를 눌러 종료한 다음 메시지가 표시되면 Y
를 누른 다음 Enter를 누르십시오.
라이브 환경에서 이를 구현하는 경우 아직 방화벽 규칙을 다시 로드하지 마십시오. 여기에 설명된 규칙 세트를 로드하면 애플리케이션과 데이터베이스 서버 간의 연결이 즉시 끊어집니다. 다시 로드하기 전에 운영 요구 사항을 반영하도록 규칙을 조정해야 합니다.
2단계 - 서비스에서 사용 중인 포트 검색
구성 요소 간의 통신을 허용하려면 사용 중인 네트워크 포트를 알아야 합니다. 구성 파일을 검사하여 올바른 네트워크 포트를 찾을 수 있지만 올바른 포트를 찾는 응용 프로그램에 구애받지 않는 방법은 각 시스템에서 연결을 수신하는 서비스를 확인하는 것입니다.
netstat
도구를 사용하여 이를 확인할 수 있습니다. 애플리케이션이 IPv4를 통해서만 통신하기 때문에 -4
인수를 추가하지만 IPv6도 사용하는 경우 제거할 수 있습니다. 실행 중인 서비스를 찾는 데 필요한 다른 인수는 -p
, -l
, -u
, -n
입니다. , 및 -t
는 -plunt
로 제공할 수 있습니다.
이러한 주장은 다음과 같이 나눌 수 있습니다.
p
: 각 소켓이 속한 프로그램의 PID 및 이름을 표시합니다.l
: 청취 소켓만 표시합니다.u
: UDP 트래픽을 표시합니다.n
: 서비스 이름 대신 숫자 출력을 표시합니다.t
: TCP 트래픽을 표시합니다.
- sudo netstat -4plunt
웹 서버에서 출력은 다음과 같을 수 있습니다.
OutputActive Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1058/sshd
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 4187/nginx
강조 표시된 첫 번째 열은 줄 끝으로 강조 표시된 서비스가 수신 대기 중인 IP 주소와 포트를 보여줍니다. 특수 0.0.0.0
주소는 해당 서비스가 사용 가능한 모든 주소에서 수신 중임을 의미합니다.
데이터베이스 서버에서 출력은 다음과 같을 수 있습니다.
- sudo netstat -4plunt
OutputActive Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1097/sshd
tcp 0 0 192.0.2.30:3306 0.0.0.0:* LISTEN 3112/mysqld
이 열을 정확히 동일하게 읽을 수 있습니다. 이 예에서 192.0.2.30
주소는 데이터베이스 서버의 사설 IP 주소를 나타냅니다. 필수 자습서에서는 보안상의 이유로 MySQL을 개인 인터페이스로 제한했습니다.
이 단계에서 찾은 값을 기록해 둡니다. 방화벽 구성을 조정하는 데 필요한 네트워킹 세부 정보입니다.
웹 서버에서 다음 포트에 액세스할 수 있는지 확인해야 합니다.
- 모든 주소의 포트 80
- 모든 주소의 포트 22(방화벽 규칙에서 이미 고려됨)
데이터베이스 서버는 다음 포트에 액세스할 수 있는지 확인해야 합니다.
- 주소
192.0.2.30
(또는 이와 연결된 인터페이스)의 포트 3306 - 모든 주소의 포트 22(방화벽 규칙에서 이미 고려됨)
3단계 - 웹 서버 방화벽 규칙 조정
이제 필요한 포트 정보를 얻었으므로 웹 서버의 방화벽 규칙 집합을 조정합니다. sudo
권한으로 편집기에서 규칙 파일을 엽니다.
- sudo nano /etc/iptables/rules.v4
웹 서버에서 허용되는 트래픽 목록에 포트 80을 추가해야 합니다. 서버는 사용 가능한 모든 주소에서 수신 대기하므로(웹 서버는 일반적으로 어디에서나 액세스할 수 있을 것으로 예상하므로) 인터페이스 또는 대상 주소로 규칙을 제한하지 않습니다.
웹 방문자는 TCP 프로토콜을 사용하여 연결합니다. 프레임워크에는 TCP 애플리케이션 예외에 대한 TCP
라는 사용자 지정 체인이 이미 있습니다. SSH 포트에 대한 예외 바로 아래에서 해당 체인에 포트 80을 추가할 수 있습니다.
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 80 -j ACCEPT
. . .
웹 서버가 데이터베이스 서버와의 연결을 시작합니다. 나가는 트래픽은 방화벽에서 제한되지 않으며 설정된 연결과 관련된 들어오는 트래픽이 허용되므로 이 연결을 허용하기 위해 이 서버에서 추가 포트를 열 필요가 없습니다.
완료되면 파일을 저장하고 닫습니다. 이제 귀하의 웹 서버에는 다른 모든 것을 차단하면서 모든 합법적인 트래픽을 허용하는 방화벽 정책이 있습니다.
규칙 파일에서 구문 오류를 테스트합니다.
- sudo iptables-restore -t < /etc/iptables/rules.v4
구문 오류가 표시되지 않으면 방화벽을 다시 로드하여 새 규칙 집합을 구현합니다.
- sudo service iptables-persistent reload
4단계 - 데이터베이스 서버 방화벽 규칙 조정
데이터베이스 서버에서 서버의 개인 IP 주소에 포트 3306
에 대한 액세스를 허용해야 합니다. 이 경우 해당 주소는 192.0.2.30
입니다. 특별히 이 주소로 향하는 액세스를 제한하거나 해당 주소가 할당된 인터페이스와 비교하여 액세스를 제한할 수 있습니다.
해당 주소와 연결된 네트워크 인터페이스를 찾으려면 ip -4 addr show scope global
을 실행합니다.
- ip -4 addr show scope global
Output2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 203.0.113.5/24 brd 104.236.113.255 scope global eth0
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 192.0.2.30/24 brd 192.0.2.255 scope global eth1
valid_lft forever preferred_lft forever
강조 표시된 영역은 eth1
인터페이스가 해당 주소와 연결되어 있음을 나타냅니다.
다음으로 데이터베이스 서버에서 방화벽 규칙을 조정합니다. 데이터베이스 서버에서 sudo
권한으로 규칙 파일을 엽니다.
- sudo nano /etc/iptables/rules.v4
다시 한 번 우리의 TCP
체인에 규칙을 추가하여 웹 서버와 데이터베이스 서버 간의 연결에 대한 예외를 형성하게 됩니다.
문제의 실제 주소를 기반으로 액세스를 제한하려면 다음과 같은 규칙을 추가합니다.
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 3306 -d 192.0.2.30 -j ACCEPT
. . .
해당 주소가 있는 인터페이스를 기반으로 예외를 허용하려면 대신 다음과 유사한 규칙을 추가할 수 있습니다.
*filter
. . .
# Acceptable TCP traffic
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 3306 -i eth1 -j ACCEPT
. . .
완료되면 파일을 저장하고 닫습니다.
다음 명령으로 구문 오류를 확인하십시오.
- sudo iptables-restore -t < /etc/iptables/rules.v4
준비가 되면 방화벽 규칙을 다시 로드합니다.
- sudo service iptables-persistent reload
이제 서버 간에 필요한 데이터 흐름을 제한하지 않고 두 서버를 모두 보호해야 합니다.
결론
애플리케이션을 설정할 때 적절한 방화벽을 구현하는 것은 항상 배포 계획의 일부여야 합니다. Nginx 및 MySQL을 실행하는 두 대의 서버를 사용하여 이 구성을 시연했지만 위에서 시연한 기술은 특정 기술 선택에 관계없이 적용할 수 있습니다.
특히 방화벽 및 iptables
에 대해 자세히 알아보려면 다음 가이드를 살펴보세요.
- 서버 보안을 위한 효과적인 방화벽 정책을 선택하는 방법
- Iptables 및 Netfilter 아키텍처에 대한 심층 분석
- Nmap 및 Tcpdump로 방화벽 구성을 테스트하는 방법