Discourse 도커 컨테이너에서 `iptables`를 사용하는 방법

저는 이 설정을 이렇게 구성했습니다. 가장 복잡한 부분은, 루트(root) 권한을 가지고 있더라도 기본 Discourse Docker 컨테이너 내에서 iptables 규칙을 확인하거나 편집하려고 하면 “권한 거부(permission denied)” 오류가 발생한다는 점입니다.

root@24a1f9f4c038:/# iptables -L
# Warning: iptables-legacy tables present, use iptables-legacy to see them
iptables: Permission denied (you must be root).
root@24a1f9f4c038:/# 

이유는 기본적으로 Discourse의 launcher 스크립트(도커 명령을 래핑하는 역할)가 Discourse Docker 컨테이너를 실행할 때 “NET_ADMIN” 권한(capability) 없이 실행하기 때문입니다.

Discourse Docker 컨테이너에 NET_ADMIN 권한을 추가하는 가장 견고한 방법은 컨테이너의 yaml 파일을 업데이트하여 docker_args yaml 문자열을 통해 docker run ... /sbin/boot 명령에 필요한 인수를 포함하는 것입니다:

docker_args: "--cap-add NET_ADMIN"

예를 들어:

[root@osestaging1 discourse]# head -n15 containers/app.yml
## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed

docker_args: "--cap-add NET_ADMIN"

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
[root@osestaging1 discourse]# 

이것은 $user_args 변수를 통해 launcher가 실행하는 docker run ... /sbin/boot 명령에 추가됩니다:

[root@osestaging1 discourse]# grep -A2 -E '^\s*\$docker_path run' launcher
     $docker_path run --shm-size=512m $links $attach_on_run $restart_policy "${env[@]}" "${labels[@]}" -h "$hostname" \
        -e DOCKER_HOST_IP="$docker_ip" --name $config -t "${ports[@]}" $volumes $mac_address $user_args \
        $run_image $boot_command
[root@osestaging1 discourse]# 

docker pull 이후와 docker run ... /sbin/boot 이전에 Discourse Docker 이미지 내부에 이미 포함된 변경 사항을 적용하는 것은 매우 복잡하기 때문에, 저는 컨테이너가 부팅될 때 runit에 의해 실행되는 간단한 스크립트를 통해 iptables를 설치 및 구성하도록 결정했습니다.

다음 명령은 다음 ./launcher rebuild app 시점에 runit 스크립트를 생성할 필요한 템플릿 yaml 파일을 생성합니다.

cat << EOF > /var/discourse/templates/iptables.template.yml
run:
  - file:
     path: /etc/runit/1.d/01-iptables
     chmod: "+x"
     contents: |
        #!/bin/bash
        ################################################################################
        # File:    /etc/runit/1.d/01-iptables
        # Version: 0.2
        # Purpose: installs & locks-down iptables
        # Author:  Michael Altfield <michael@opensourceecology.org>
        # Created: 2019-11-26
        # Updated: 2019-12-17
        ################################################################################
        sudo apt-get update
        sudo apt-get install -y iptables 
        sudo iptables -A INPUT -i lo -j ACCEPT
        sudo iptables -A INPUT -s 127.0.0.1/32 -j DROP
        sudo iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        sudo iptables -A INPUT -j DROP
        sudo iptables -A OUTPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -j ACCEPT
        sudo iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        sudo iptables -A OUTPUT -m owner --uid-owner 0 -j ACCEPT
        sudo iptables -A OUTPUT -m owner --uid-owner 100 -j ACCEPT
        sudo iptables -A OUTPUT -j DROP
        sudo ip6tables -A INPUT -i lo -j ACCEPT
        sudo ip6tables -A INPUT -s ::1/128 -j DROP
        sudo ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        sudo ip6tables -A INPUT -j DROP
        sudo ip6tables -A OUTPUT -s ::1/128 -d ::1/128 -j ACCEPT
        sudo ip6tables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        sudo ip6tables -A OUTPUT -m owner --uid-owner 0 -j ACCEPT
        sudo ip6tables -A OUTPUT -m owner --uid-owner 100 -j ACCEPT
        sudo ip6tables -A OUTPUT -j DROP

EOF

이것이 매우 기본적인 iptables 설정임을 유의하세요. 제가 사용하는 web.socketed.template.yml 파일의 경우, Docker 호스트의 nginx 리버스 프록시가 유닉스 도메인 소켓을 통해 Docker 컨테이너의 Discourse nginx와 통신하므로 Docker 컨테이너는 사실상 인터넷 접근이 전혀 필요하지 않습니다. 인터넷 접근을 허용하는 주된 이유는 Docker 컨테이너의 기본 운영체제가 unattended-upgrades를 통해 중요한 보안 패치를 받아 스스로 업데이트할 수 있도록 하기 위해서입니다. 따라서 apt 사용자인 사용자 100을 위해 포트를 개방합니다.

마지막으로, 위에서 언급한 templates/iptables.template.yml 파일을 컨테이너의 app.yaml 파일에 추가하세요.

[root@osestaging1 discourse]# head -n15 /var/discourse/containers/discourse_ose.yml
## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed

docker_args: "--cap-add NET_ADMIN"

templates:
  - "templates/iptables.template.yml"
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
[root@osestaging1 discourse]# 

이제 다음을 실행할 수 있습니다.

./launcher rebuild app

그리고 약 10분 정도 기다리면, 여러분의 Discourse Docker 컨테이너에는 iptables가 완전히 기능하는 상태로 설치되어 있을 것입니다 :slight_smile: