# 如何在 Discourse Docker 容器中使用 \`iptables\`

**URL:** https://meta.discourse.org/t/how-to-use-iptables-inside-discourse-docker-container/136305
**Category:** Self-hosting
**Tags:** hosting
**Created:** [2019年十二月17日 11:59 UTC](https://meta.discourse.org/t/how-to-use-iptables-inside-discourse-docker-container/136305 "2019-12-17T11:59:20Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![maltfield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/maltfield/32/160669_2.png) [@maltfield](https://meta.discourse.org/u/maltfield)
#### Post date: [2019年十二月17日 11:59 UTC](https://meta.discourse.org/t/how-to-use-iptables-inside-discourse-docker-container/136305/1 "2019-12-17T11:59:20Z")

</div>

如何在 Discourse Docker 容器中配置 `iptables`？

我特别需要在 Docker 容器 **内部** 进行防火墙控制，因为我希望能够根据用户 ID 添加条件规则。

最初，我完全禁止 Docker 容器访问互联网（如果使用 `web.socketed.template.yml`，这是可行的，因为 Nginx 将通过 Unix 域套接字而非网络进行通信），方法是使用 `dockerd ... --iptables=false`。

```plaintext
[root@osestaging1 templates]# grep ExecStart /usr/lib/systemd/system/docker.service
#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --iptables=false
[root@osestaging1 templates]# 

```

……但我意识到，Docker 容器至少应该能够访问互联网，以便通过 `unattended-upgrades` 更新其基础操作系统（当前为 Debian 10）以获取关键安全更新。因此，我宁愿只授予 root 和 apt 访问互联网的权限，并通过在 Discourse Docker 容器内运行的 `iptables` 拒绝其他所有访问。

---

<div class="post-metadata">

### Author: ![maltfield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/maltfield/32/160669_2.png) [@maltfield](https://meta.discourse.org/u/maltfield)
#### Post date: [2019年十二月17日 12:04 UTC](https://meta.discourse.org/t/how-to-use-iptables-inside-discourse-docker-container/136305/2 "2019-12-17T12:04:17Z")

</div>

我已经设置好了。最复杂的部分是——即使你是 root 用户——在默认的 Discourse Docker 容器内尝试查看或编辑 iptables 规则时，仍然会收到“权限被拒绝”的错误。

```plaintext
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` 脚本（用于包装 Docker 命令）在开箱即用的情况下，运行 Discourse Docker 容器时未启用“NET\_ADMIN”能力。

- [capabilities(7) - Linux manual page](http://man7.org/linux/man-pages/man7/capabilities.7.html)
- [Running containers | Docker Docs](https://docs.docker.com/engine/reference/run/#/runtime-privilege-and-linux-capabilities)
- [https://stackoverflow.com/questions/41706983/installing-iptables-in-docker-container-based-on-alpinelinux](https://stackoverflow.com/questions/41706983/installing-iptables-in-docker-container-based-on-alpinelinux)

为 Discourse Docker 容器添加 NET\_ADMIN 能力最稳健的方法是更新容器的 yaml 文件，通过 `docker_args` yaml 字符串，向 `docker run ... /sbin/boot` 命令添加必要的参数：

```plaintext
docker_args: "--cap-add NET_ADMIN"

```

例如：

```plaintext
[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` 命令中：

```plaintext
[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。

以下命令将创建必要的模板 yaml 文件，该文件将在下一次 `./launcher rebuild app` 时生成 `runit` 脚本：

```plaintext
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 容器实际上根本不需要互联网访问权限，因为 Docker 主机上的 nginx 反向代理通过 Unix 域套接字与 Docker 容器内的 Discourse nginx 进行通信。我允许互联网访问的主要原因是为了让 Docker 容器的基础操作系统能够通过 `unattended-upgrades` 自动更新关键安全补丁。因此，我为用户 `100`（即 `apt` 用户）开放了访问权限。

最后，将上述 `templates/iptables.template.yml` 文件添加到容器的 `app.yaml` 文件中。

```plaintext
[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]# 

```

现在你应该可以执行：

```plaintext
./launcher rebuild app

```

等待大约 10 分钟后，你的 Discourse Docker 容器中的 iptables 将完全正常运行 🙂

---

<div class="post-metadata">

### Author: ![gerhard](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/gerhard/32/119479_2.png) [@gerhard](https://meta.discourse.org/u/gerhard)
#### Post date: [2019年十二月17日 13:23 UTC](https://meta.discourse.org/t/how-to-use-iptables-inside-discourse-docker-container/136305/3 "2019-12-17T13:23:43Z")

</div>

> ⚠ **无论你是谁，无论你做什么，请千万不要在家里、学校或任何地方尝试此操作！**

我非常确定，通过禁止 `discourse` 用户拥有的 `unicorn` 和 `sidekiq` 进程访问互联网，你正在使你的 Discourse 实例瘫痪。不过，我相信你清楚自己在做什么。

---

<div class="post-metadata">

### Author: ![pfaffman](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pfaffman/32/120154_2.png) [@pfaffman](https://meta.discourse.org/u/pfaffman)
#### Post date: [2019年十二月17日 14:32 UTC](https://meta.discourse.org/t/how-to-use-iptables-inside-discourse-docker-container/136305/4 "2019-12-17T14:32:56Z")

</div>

> [@maltfield](#):
>
> 我特别需要 Docker 容器_内部_的防火墙控制，因为我想能够根据用户 ID 添加条件规则。

这解决了什么问题？
