대부분의 경우에는 Docker를 사용할 때 -p host:container 등과 같은 방법으로 컨테이너에서 도커의 기본 브릿지를 통하여 포트 포워딩을 하여 사용합니다. 하지만 이는 잘못하면 서버에 엄청나게 큰 보안 구멍을 낼 수 있는 사용입니다. 물론 적절한 상위 방화벽 설정으로 이러한 포트 유출이 방지되고 있는 경우가 있습니다만 여전히 잠재적으로 원치 않는 영역까지 포트가 유출되고 있는 상황이고 볼 수 있습니다.

어떤 상황에서 컨테이너의 포트 유출이 발생하는가?

기본적으로 Docker 컨테이너에서 호스트로의 포트포워딩을 할 때 iptables 규칙이 제대로 설정되지 않았다면 Docker-User 체인을 통해 컨테이너의 포트가 타 체인의 규칙을 무시하고 트래픽을 전송할 수 있는 환경이 만들어집니다. 물론 Docker에서는 iptables의 Docker-User 체인을 통한 트래픽 설정을 권장합니다. 하지만 대부분의 경우 iptables는 사용자의 부주의로 위험한 환경이 만들어지기 쉬우며 이론 상의 한계를 가집니다.

  • 사용자의 부주의로 인한 잘못된 DOCKER-USER 체인 규칙 설정
  • 모든 컨테이너의 포트가 한 곳에 연결되어 깔끔하지 않음

실제로 저는 현재 DOCKER-USER 체인에 아무 규칙도 설정하지 않았습니다. 그렇기 때문에 포트를 유출시키기에 굉장히 좋은 환경입니다.

Chain DOCKER-USER (1 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             anywhere

대신 Docker의 네트워크 기능을 잘 활용해보자

그럼에도 불구하고 이러한 포트 유출을 막으면서도 훨씬 더 깔끔하게 Docker 생태계를 호스트 위에서 동작하도록 하는 방법이 있습니다. 새로운 방법을 찾은데에는 여러가지 이유가 있습니다. 첫 번째로 iptables는 너무 많은 것을 신경쓰게 했습니다. 저는 본래 이미 iptables를 사용하지 않고 nftables를 사용하고 있었지만 Docker의 네트워크 레이어는 nftables를 지원하지 않습니다. 그래서 동시에 사용하면서 굉장히 많은 부수적인 유지보수가 필요해졌고 결국에 nftables 사용을 포기했었습니다.

그러나 아무리 그래도 iptables가 어려운 것은 사실입니다. 전혀 직관적이지 않아요. 그 누구도 그 많은 iptables 옵션 값에 단순히 포트와 네트워크 플로우를 위해서 시간을 쏟고 싶지 않을 것이라고 확신합니다. 그래서 저는 대신 여러 External Network를 만들고 Docker-Compose를 더더욱 활용하기 시작했습니다.

External Network를 활용하자

먼저 제가 생각한 것은 External Network입니다. 기본적인 Docker 브릿지를 사용하지 않고 각 도메인의 컨테이너를 각 도메인의 이름으로된 External Network에 연결시키고 ports 대신 expose 키를 활용하여 각 컨테이너의 간의 연결을 성립시켰습니다.

docker network create -d bridge <network_name>

그리고 Compose 파일에서 네트워크를 등록시켜주고 원하는 포트를 해당 네트워크에 노출(expose)합니다. 간단히 Nginx 이미지로 예시를 만들어보았습니다.

version: "3.8"

services:
  proxy:
    container_name: proxy
    image: nginx:latest
    expose:
      - 80
    networks:
      - <network_name>

networks:
  <network_name>:
    external: true

위와 같은 경우에는 내부 브릿지 네트워크로만 포트가 노출되므로 더 이상 보안에 예상치못한 구멍이 생길 일이 없어집니다. 또 타 컨테이너에서 container_name으로 등록된 proxy라는 값이 내부적으로 hosts에 등록되어 쉽게 연결을 가능케 합니다. 그래서 생성된 내부 네트워크를 탐색하면 다음과 같이 컨테이너가 안전하게 하나의 도메인 전용 내부 네트워크에 종속된 것을 볼 수 있습니다.

그 외로도 호스트를 최대한 적게 건드리게 되므로 격리의 수준을 높이고 호스트를 더더욱 안정적으로 실행할 수 있는 환경을 만들어주는 셈이 됩니다. 최종적으로 현재 구조에서는 단순히 이제 포트를 외부로 노출해야 하는 컨테이너만 ports 속성을 만들어주면 됩니다. 그것이 아니라면 expose를 대신 활용하면 되는 것입니다.

안전하게 "호스트에만" 포트를 노출시켜보자

또 이 ports 속성을 더더욱 안전하게 사용할 수 있는 방법에는 직접 그 포트가 어느 도메인으로 전파될 수 있을지 설정하여 호스트에서 포트를 접근해야 하는 경우 더더욱 안전하게 포트를 노출시킬 수 있습니다.

단순히 기존의 ports 속성에 몇 단어만 추가하면 됩니다.

ports:
  - 127.0.0.1:80:80

위와 같이 설정하는 경우에는 0.0.0.0으로 호스트가 되지 않고 127.0.0.1으로만 전파가 되어 안전하게 호스트에서 컨테이너의 포트로 접근을 할 수 있게 됩니다.


여러모로 저는 리버스 프록시를 설정하는 과정이었는데 컨테이너의 포트가 호스트를 넘어 외부까지 전파되는 것을 보고 상당히 당황했었던 기억이 있네요. 그래도 그 때의 경험이 현재까지 위험한 습관을 최소화하고 있다고 여기며 다행이라고 생각하고 있습니다.