Compare commits

..

No commits in common. "master" and "200812" have entirely different histories.

9 changed files with 171 additions and 931 deletions

View file

@ -1,35 +0,0 @@
name: Build Images
on:
push:
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Log into DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ github.actor }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ github.actor }}/ufw-docker-agent
- name: Build and push
uses: docker/build-push-action@v6
with:
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View file

@ -1,16 +0,0 @@
name: Unit Testing ufw-docker
on: [push, pull_request]
jobs:
test:
name: Unit Testing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Checkout submodules
shell: bash
run: |
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
- name: Test ufw-docker
run: ./test.sh

View file

@ -1,29 +1,19 @@
FROM ubuntu:24.04 FROM ubuntu:20.04
ARG docker_version="27.3.1" ARG docker_version="19.03.12"
ARG use_iptables_legacy=false
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y ca-certificates curl gnupg lsb-release \ && apt-get install -y --no-install-recommends apt-transport-https \
&& mkdir -p /etc/apt/keyrings \ ca-certificates curl software-properties-common gnupg dirmngr \
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg]" \ && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
"https://download.docker.com/linux/ubuntu" "$(lsb_release -cs) stable" \ $(lsb_release -cs) stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update \ && apt-get update \
&& apt-get install -y --no-install-recommends locales ufw \ && apt-get install -y --no-install-recommends locales ufw \
&& apt-get install -y --no-install-recommends "docker-ce=$(apt-cache madison docker-ce | grep -m1 -F "${docker_version}" | cut -d'|' -f2 | tr -d '[[:blank:]]')" \ && ( apt-get install -y --no-install-recommends "docker-ce=5:${docker_version}~*" || \
apt-get install -y --no-install-recommends "docker-ce=${docker_version}~*" ) \
&& locale-gen en_US.UTF-8 \ && locale-gen en_US.UTF-8 \
&& if "$use_iptables_legacy"; then \
apt-get -y install arptables ebtables \
&& update-alternatives --install /usr/sbin/arptables arptables /usr/sbin/arptables-legacy 100 \
&& update-alternatives --install /usr/sbin/ebtables ebtables /usr/sbin/ebtables-legacy 100 \
&& update-alternatives --set iptables /usr/sbin/iptables-legacy \
&& update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy \
&& update-alternatives --set arptables /usr/sbin/arptables-legacy \
&& update-alternatives --set ebtables /usr/sbin/ebtables-legacy; \
fi \
&& apt-get clean autoclean \ && apt-get clean autoclean \
&& apt-get autoremove --yes \ && apt-get autoremove --yes \
&& rm -rf /var/lib/{apt,dpkg,cache,log}/ && rm -rf /var/lib/{apt,dpkg,cache,log}/

106
README.md
View file

@ -2,7 +2,7 @@ To Fix The Docker and UFW Security Flaw Without Disabling Iptables
================== ==================
[![Build Status](https://travis-ci.org/chaifeng/ufw-docker.svg)](https://travis-ci.org/chaifeng/ufw-docker) [![Build Status](https://travis-ci.org/chaifeng/ufw-docker.svg)](https://travis-ci.org/chaifeng/ufw-docker)
[![chaifeng/ufw-docker-agent](https://img.shields.io/docker/pulls/chaifeng/ufw-docker-agent)](https://hub.docker.com/r/chaifeng/ufw-docker-agent) ![chaifeng/ufw-docker-agent](https://img.shields.io/docker/pulls/chaifeng/ufw-docker-agent)
- [English](#tldr) - [English](#tldr)
- [中文](#太长不想读) - [中文](#太长不想读)
@ -18,7 +18,7 @@ UFW is a popular iptables front end on Ubuntu that makes it easy to manage firew
The issue is: The issue is:
1. UFW is enabled on a server that provides external services, and all incoming connections that are not allowed are blocked by default. 1. UFW is enabled on a server that provides external services, and all incoming connections that are not allowed are blocked by default.
2. Run a Docker container on the server and use the `-p` option to publish ports for that container on all IP addresses. 2. Run a Docker container on the server and use the `-p` option to publish ports for that container on all IP addresses.
For example: `docker run -d --name httpd -p 0.0.0.0:8080:80 httpd:alpine`, this command will run an httpd service and publish port 80 of the container to port 8080 of the server. For example: `docker run -d --name httpd -p 0.0.0.0:8080:80 httpd:alpine`, this command will run an httpd service and publish port 80 of the container to port 8080 of the server.
3. UFW will not block all external requests to visit port 8080. Even the command `ufw deny 8080` will not prevent external access to this port. 3. UFW will not block all external requests to visit port 8080. Even the command `ufw deny 8080` will not prevent external access to this port.
4. This problem is actually quite serious, which means that a port that was originally intended to provide services internally is exposed to the public network. 4. This problem is actually quite serious, which means that a port that was originally intended to provide services internally is exposed to the public network.
@ -43,7 +43,7 @@ Almost all of these solutions are similar. It requires to disable docker's iptab
The solutions that we can find on internet are very similar and not elegant, I hope a new solution can: The solutions that we can find on internet are very similar and not elegant, I hope a new solution can:
- Don't need to disable Docker's iptables and let Docker to manage it's network. - Don't need to disable Docker's iptables and let Docker to manage it's network.
We don't need to manually maintain iptables rules for any new Docker networks, and avoid potential side effects after disabling iptables in Docker. We don't need to manually maintain iptables rules for any new Docker networks, and avoid potential side effects after disabling iptables in Docker.
- The public network cannot access ports that published by Docker. Even if the port is published on all IP addresses using an option like `-p 8080:80`. Containers and internal networks can visit each other normally. - The public network cannot access ports that published by Docker. Even if the port is published on all IP addresses using an option like `-p 8080:80`. Containers and internal networks can visit each other normally.
Although it is possible to have Docker publish a container's port to the server's private IP address, the port will not be accessed on the public network. But, this server may have multiple private IP addresses, and these private IP addresses may also change. Although it is possible to have Docker publish a container's port to the server's private IP address, the port will not be accessed on the public network. But, this server may have multiple private IP addresses, and these private IP addresses may also change.
@ -193,22 +193,6 @@ Con:
Doesn't support older versions of Ubuntu, and the command is a bit more complicated. But you can use my script. Doesn't support older versions of Ubuntu, and the command is a bit more complicated. But you can use my script.
### IPv6
[Enable IPv6 support](https://forums.docker.com/t/docker-user-chain-for-ip6tables/133961/3) in Docker by specifying ULA range (RFC 4193) in `/etc/docker/daemon.json`
```json
{
"experimental": true,
"ipv6": true,
"ip6tables": true,
"fixed-cidr-v6": "fd00:dead:beef::/48"
}
```
Restart Docker
```shell
systemctl restart docker
```
### Conclusion ### Conclusion
@ -226,7 +210,7 @@ Download `ufw-docker` script
sudo wget -O /usr/local/bin/ufw-docker \ sudo wget -O /usr/local/bin/ufw-docker \
https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
sudo chmod +x /usr/local/bin/ufw-docker chmod +x /usr/local/bin/ufw-docker
Then using the following command to modify the `after.rules` file of `ufw` Then using the following command to modify the `after.rules` file of `ufw`
@ -236,36 +220,6 @@ This command does the following things:
- Back up the file `/etc/ufw/after.rules` - Back up the file `/etc/ufw/after.rules`
- Append the rules of UFW and Docker at the end of the file - Append the rules of UFW and Docker at the end of the file
#### IPv6 support
`ufw-docker` also supports IPv6 networks and will update `/etc/ufw/after6.rules` when necessary.
### Using the `--docker-subnets` option
You can use the `--docker-subnets` option to customize which subnets will be allowed to communicate with Docker containers.
This option applies to both IPv4 and IPv6 networks.
* If the option is **not provided**, only standard private LAN subnets will be used
(IPv4: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`; IPv6: `fd00::/8`)
* If `--docker-subnets` is given **without any arguments**, all Docker network subnets will be detected and used automatically.
**Note:** If you add or remove Docker networks, you need to run `ufw-docker install --docker-subnets` again to update the firewall rules according to the latest network configuration.
* If one or more subnets are specified, only these subnets will be used (you can list multiple subnets, separated by spaces; each should be in CIDR format).
The subnets can include networks not managed by Docker itself.
#### Examples
# Use default private LAN subnets (IPv4: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16; IPv6: fd00::/8)
ufw-docker install
# Auto-detect and use all Docker network subnets (both IPv4 and IPv6)
ufw-docker install --docker-subnets
# Only allow these specified subnets to communicate with Docker containers
ufw-docker install --docker-subnets 192.168.207.0/24 10.207.0.0/16 fd00:cf::/64
You can use the same options with `ufw-docker check` to preview the changes before applying them.
#### Install for Docker Swarm mode #### Install for Docker Swarm mode
We can only use this script on manager nodes to manage firewall rules when using in Swarm mode. We can only use this script on manager nodes to manage firewall rules when using in Swarm mode.
@ -305,10 +259,6 @@ Expose the `443` port of the container `httpd` and the protocol is `tcp`
ufw-docker allow httpd 443/tcp ufw-docker allow httpd 443/tcp
Expose the `443` port of the container `httpd` and the protocol is `tcp` and the network is `foobar-external-network` when the container `httpd` is attached to multiple networks
ufw-docker allow httpd 443/tcp foobar-external-network
Expose all published ports of the container `httpd` Expose all published ports of the container `httpd`
ufw-docker allow httpd ufw-docker allow httpd
@ -335,7 +285,7 @@ Remove rules from all nodes related to the service `web`
### Try it out ### Try it out
We use [Vagrant](https://www.vagrantup.com/) to set up a local testing environment. We use [Vagrant](https://www.vagrantup.com/) to set up a local testing environment.
Run the following command to create 1 master node and 2 worker nodes Run the following command to create 1 master node and 2 worker nodes
@ -452,7 +402,7 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
COMMIT COMMIT
# END UFW AND DOCKER # END UFW AND DOCKER
然后重启 UFW`sudo systemctl restart ufw`。现在外部就已经无法访问 Docker 发布出来的任何端口了,但是容器内部以及私有网络地址上可以正常互相访问,而且容器也可以正常访问外部的网络。**可能由于某些未知原因,重启 UFW 之后规则也无法生效,请重启服务器。** 然后重启 UFW`sudo systemctl restart ufw`。现在外部就已经无法访问 Docker 发布出来的任何端口了,但是容器内部以及私有网络地址上可以正常互相访问,而且容器也可以正常访问外部的网络。**可能由于某些未知原因,重启 UFW 之后规则也无法生效,请重启服务器。**
如果希望允许外部网络访问 Docker 容器提供的服务,比如有一个容器的服务端口是 `80`。那就可以用以下命令来允许外部网络访问这个服务: 如果希望允许外部网络访问 Docker 容器提供的服务,比如有一个容器的服务端口是 `80`。那就可以用以下命令来允许外部网络访问这个服务:
@ -548,19 +498,6 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
不支持老版本的 Ubuntu而且命令的使用上可能也会比较复杂。 不支持老版本的 Ubuntu而且命令的使用上可能也会比较复杂。
### 支持 IPv6
要让 Docker Engine [启用 IPv6 的支持](https://forums.docker.com/t/docker-user-chain-for-ip6tables/133961/3). 你需要在 `/etc/docker/daemon.json` 文件中启用相关设置,并分配一个 ULA唯一本地地址RFC 4193地址段作为 IPv6 网络范围。
```json
{
"experimental": true,
"ipv6": true,
"ip6tables": true,
"fixed-cidr-v6": "fd00:dead:beef::/48"
}
```
#### 结论 #### 结论
如果我们正在使用老版本的 Ubuntu我们可以使用 `ufw-user-input`。但是要小心避免把不该暴露的服务暴露出去。 如果我们正在使用老版本的 Ubuntu我们可以使用 `ufw-user-input`。但是要小心避免把不该暴露的服务暴露出去。
@ -587,33 +524,6 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
- 备份文件 `/etc/ufw/after.rules` - 备份文件 `/etc/ufw/after.rules`
- 把 UFW 和 Docker 的相关规则添加到文件 `after.rules` 的末尾 - 把 UFW 和 Docker 的相关规则添加到文件 `after.rules` 的末尾
#### IPv6 支持
`ufw-docker` 也支持 IPv6 网络,并会在需要时自动更新 `/etc/ufw/after6.rules` 文件。
### 使用 `--docker-subnets` 选项
你可以使用 `--docker-subnets` 选项,自定义允许与 Docker 容器通信的子网。
该选项同时适用于 IPv4 和 IPv6 网络。
* 如果**未指定**该选项,只会使用标准的私有局域网子网
IPv4`10.0.0.0/8``172.16.0.0/12``192.168.0.0/16`IPv6`fd00::/8`
* 如果指定了 `--docker-subnets` 但**未带参数**,会自动检测并使用所有 Docker 网络的子网。
**注意:** 如果你对 Docker 网络进行了新增或删除操作,需要重新运行 `ufw-docker install --docker-subnets`,以便根据最新的网络配置更新防火墙规则。
* 如果指定了一个或多个子网,则只会使用这些子网(你可以用空格分隔多个子网,每个子网应为 CIDR 格式)。
这些子网可以包括非 Docker 管理的网络。
#### 示例
# 使用默认的私有局域网子网IPv410.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16IPv6fd00::/8
ufw-docker install
# 自动检测并使用所有 Docker 网络的子网(同时支持 IPv4 和 IPv6
ufw-docker install --docker-subnets
# 仅允许这些指定的子网与 Docker 容器通信
ufw-docker install --docker-subnets 192.168.207.0/24 10.207.0.0/16 fd00:cf::/64
#### 为 Docker Swarm 环境安装 #### 为 Docker Swarm 环境安装
仅仅可以在管理节点上使用 `ufw-docker` 这个脚本来管理防火墙规则。 仅仅可以在管理节点上使用 `ufw-docker` 这个脚本来管理防火墙规则。
@ -653,10 +563,6 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
ufw-docker allow httpd 443/tcp ufw-docker allow httpd 443/tcp
如果容器 `httpd` 绑定到多个网络上,暴露其 `443` 端口,协议为 `tcp`,网络为 `foobar-external-network`
ufw-docker allow httpd 443/tcp foobar-external-network
把容器 `httpd` 的所有映射端口都暴露出来 把容器 `httpd` 的所有映射端口都暴露出来
ufw-docker allow httpd ufw-docker allow httpd

256
Vagrantfile vendored
View file

@ -3,59 +3,25 @@
# -*- mode: ruby -*- # -*- mode: ruby -*-
# vi: set ft=ruby : # vi: set ft=ruby :
ENV['VAGRANT_NO_PARALLEL']="true"
Vagrant.configure('2') do |config| Vagrant.configure('2') do |config|
ubuntu_version = File.readlines("Dockerfile").filter { |line|
line.start_with?("FROM ")
}.first.match(/\d\d\.\d\d/)[0]
docker_version = File.readlines("Dockerfile").filter { |line| config.vm.box = "chaifeng/ubuntu-20.04-docker-19.03.11"
line.start_with?("ARG docker_version=") #config.vm.box = "chaifeng/ubuntu-16.04-docker-18.03"
}.first.match(/"([\d\.]+)"/)[1]
config.vm.box = "chaifeng/ubuntu-#{ubuntu_version}-docker-#{docker_version}"
config.vm.provider 'virtualbox' do |vb| config.vm.provider 'virtualbox' do |vb|
vb.memory = '1024' vb.memory = '1024'
vb.default_nic_type = "virtio" vb.default_nic_type = "virtio"
end end
config.vm.provider 'parallels' do |prl|
prl.memory = '1024'
prl.check_guest_tools = false
end
ip_prefix="192.168.56" ip_prefix="192.168.56"
ip6_prefix="fd00:a:b"
worker_count=1
def env_true?(env_name) config.vm.provision 'docker-daemon-config', type: 'shell', inline: <<-SHELL
value = ENV[env_name] || 'false'
true_values = %w[true yes on 1]
down = value.strip.downcase
return 'true' if true_values.include?(down)
'false'
end
def env_true_str?(env_name)
env_true?(env_name).to_s
end
config.vm.provision 'setup', preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL
byobu-ctrl-a screen
SHELL
config.vm.provision 'docker-daemon-config', type: 'shell', inline: <<-SHELL
set -eu set -eu
[[ -f /etc/profile.d/editor.sh ]] || echo 'export EDITOR=vim' > /etc/profile.d/editor.sh if [[ ! -f /etc/docker/daemon.json ]]; then
if [[ "$(hostname)" = @(master|node?) && ! -f /etc/docker/daemon.json ]]; then
echo '{' >> /etc/docker/daemon.json echo '{' >> /etc/docker/daemon.json
echo ' "insecure-registries": ["localhost:5000", "#{ip_prefix}.130:5000"]' >> /etc/docker/daemon.json echo ' "insecure-registries": ["localhost:5000", "#{ip_prefix}.130:5000"]' >> /etc/docker/daemon.json
[[ -n "#{ENV['DOCKER_REGISTRY_MIRROR']}" ]] && [[ -n "#{ENV['DOCKER_REGISTRY_MIRROR']}" ]] &&
echo ' , "registry-mirrors": ["#{ENV['DOCKER_REGISTRY_MIRROR']}"]' >> /etc/docker/daemon.json echo ' , "registry-mirrors": ["#{ENV['DOCKER_REGISTRY_MIRROR']}"]' >> /etc/docker/daemon.json
#{env_true_str?('ENABLE_DOCKER_IPV6')} &&
echo ' ,"ip6tables": true, "ipv6": true, "fixed-cidr-v6": "#{ip6_prefix}:deaf::/64"' >> /etc/docker/daemon.json
echo '}' >> /etc/docker/daemon.json echo '}' >> /etc/docker/daemon.json
if type systemctl &>/dev/null; then if type systemctl &>/dev/null; then
systemctl restart docker systemctl restart docker
@ -65,49 +31,41 @@ Vagrant.configure('2') do |config|
fi fi
SHELL SHELL
config.vm.provision 'ufw-docker', preserve_order: true, type: 'shell', inline: <<-SHELL config.vm.provision 'ufw-docker', type: 'shell', inline: <<-SHELL
set -xeuo pipefail set -euo pipefail
export DEBUG=true export DEBUG=true
lsb_release -is | grep -Fi ubuntu lsb_release -is | grep -Fi ubuntu
/vagrant/ufw-docker check || {
ufw allow OpenSSH
ufw allow from #{ip_prefix}.128/28 to any
declare -a subnets=(--docker-subnets 192.168.56.128/28 10.0.0.0/8 172.16.0.0/12) yes | ufw enable || true
#{env_true_str?('ENABLE_DOCKER_IPV6')} && ufw status | grep '^Status: active'
subnets+=(fd00:a:b:deaf::/64 fd05:8f23:c937:1::/64 fd05:8f23:c937:2::/64 fd05:8f23:c937::/64)
if [[ "$(hostname)" = @(master|node?) ]]; then
/vagrant/ufw-docker check "${subnets[@]-}" >/dev/null 2>&1 || {
ufw allow OpenSSH
ufw allow from #{ip_prefix}.128/28 to any
#{env_true_str?('ENABLE_DOCKER_IPV6')} &&
ufw allow from #{ip6_prefix}:0:cafe::/80 to any
yes | ufw enable || true /vagrant/ufw-docker install
ufw status | grep '^Status: active'
/vagrant/ufw-docker install "${subnets[@]-}" sed -i -e 's,192\.168\.0\.0/16,#{ip_prefix}.128/28,' /etc/ufw/after.rules
systemctl restart ufw
systemctl restart ufw
}
[[ -L /usr/local/bin/ufw-docker ]] || ln -s /vagrant/ufw-docker /usr/local/bin/ [[ -L /usr/local/bin/ufw-docker ]] || ln -s /vagrant/ufw-docker /usr/local/bin/
fi
iptables -I DOCKER-USER 4 -p udp -j LOG --log-prefix '[UFW DOCKER] '
}
SHELL SHELL
private_registry="#{ip_prefix}.130:5000" private_registry="#{ip_prefix}.130:5000"
config.vm.define "master" do |master| config.vm.define "master" do |master|
master_ip_address = "#{ip_prefix}.130"
master.vm.hostname = "master" master.vm.hostname = "master"
master.vm.network "private_network", ip: "#{master_ip_address}" master.vm.network "private_network", ip: "#{ip_prefix}.130"
if env_true?('ENABLE_DOCKER_IPV6')
master.vm.network "private_network", ip: "#{ip6_prefix}:0:cafe::130", type: "static", netmast: 64, auto_config: true
end
master.vm.provision "unit-testing", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL master.vm.provision "unit-testing", type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
[[ -n "#{ENV['DISABLE_UNIT_TESTING']}" ]] || /vagrant/test.sh
/vagrant/test.sh
SHELL SHELL
master.vm.provision "docker-registry", preserve_order: true, type: 'docker' do |d| master.vm.provision "docker-registry", type: 'docker' do |d|
d.run "registry", d.run "registry",
image: "registry:2", image: "registry:2",
args: "-p 5000:5000", args: "-p 5000:5000",
@ -117,35 +75,31 @@ Vagrant.configure('2') do |config|
ufw_docker_agent_image = "#{private_registry}/chaifeng/ufw-docker-agent:test" ufw_docker_agent_image = "#{private_registry}/chaifeng/ufw-docker-agent:test"
master.vm.provision "docker-build-ufw-docker-agent", preserve_order: true, type: 'shell', inline: <<-SHELL master.vm.provision "docker-build-ufw-docker-agent", type: 'shell', inline: <<-SHELL
set -xeuo pipefail set -euo pipefail
suffix="$(iptables --version | grep -o '\\(nf_tables\\|legacy\\)')" docker build -t #{ufw_docker_agent_image} /vagrant
if [[ "$suffix" = legacy ]]; then use_iptables_legacy=true; else use_iptables_legacy=false; fi docker push #{ufw_docker_agent_image}
docker build --build-arg use_iptables_legacy="${use_iptables_legacy:-false}" -t "#{ufw_docker_agent_image}-${suffix}" /vagrant
docker push "#{ufw_docker_agent_image}-${suffix}"
echo "export UFW_DOCKER_AGENT_IMAGE=#{ufw_docker_agent_image}-${suffix}" > /etc/profile.d/ufw-docker.sh echo "export UFW_DOCKER_AGENT_IMAGE=#{ufw_docker_agent_image}" > /etc/profile.d/ufw-docker.sh
echo "export DEBUG=true" >> /etc/profile.d/ufw-docker.sh echo "export DEBUG=true" >> /etc/profile.d/ufw-docker.sh
echo "Defaults env_keep += UFW_DOCKER_AGENT_IMAGE" > /etc/sudoers.d/98_ufw-docker echo "Defaults env_keep += UFW_DOCKER_AGENT_IMAGE" > /etc/sudoers.d/98_ufw-docker
echo "Defaults env_keep += DEBUG" >> /etc/sudoers.d/98_ufw-docker echo "Defaults env_keep += DEBUG" >> /etc/sudoers.d/98_ufw-docker
SHELL SHELL
master.vm.provision "swarm-init", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL master.vm.provision "swarm-init", type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
docker info | fgrep 'Swarm: active' && exit 0 docker info | fgrep 'Swarm: active' && exit 0
docker swarm init --advertise-addr "#{master_ip_address}" docker swarm init --advertise-addr eth1
docker swarm join-token worker --quiet > /vagrant/.vagrant/docker-join-token docker swarm join-token worker --quiet > /vagrant/.vagrant/docker-join-token
SHELL SHELL
master.vm.provision "build-webapp", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL master.vm.provision "build-webapp", type: 'shell', inline: <<-SHELL
set -xeuo pipefail set -euo pipefail
docker build -t #{private_registry}/chaifeng/hostname-webapp - <<\\DOCKERFILE docker build -t #{private_registry}/chaifeng/hostname-webapp - <<\\DOCKERFILE
FROM httpd:alpine FROM httpd:alpine
RUN printf "Listen %s\\n" 7000 8080 >> /usr/local/apache2/conf/httpd.conf
RUN { echo '#!/bin/sh'; \\ RUN { echo '#!/bin/sh'; \\
echo 'set -e; (echo -n "${name:-Hi} "; hostname;) > /usr/local/apache2/htdocs/index.html'; \\ echo 'set -e; (echo -n "${name:-Hi} "; hostname;) > /usr/local/apache2/htdocs/index.html'; \\
echo 'exec "$@"'; \\ echo 'exec "$@"'; \\
@ -157,8 +111,8 @@ DOCKERFILE
docker push #{private_registry}/chaifeng/hostname-webapp docker push #{private_registry}/chaifeng/hostname-webapp
SHELL SHELL
master.vm.provision "local-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL master.vm.provision "local-webapp", type: 'shell', inline: <<-SHELL
set -xeuo pipefail set -euo pipefail
for name in public:18080 local:8000; do for name in public:18080 local:8000; do
webapp="${name%:*}_webapp" webapp="${name%:*}_webapp"
port="${name#*:}" port="${name#*:}"
@ -171,70 +125,26 @@ DOCKERFILE
ufw-docker allow public_webapp ufw-docker allow public_webapp
SHELL SHELL
master.vm.provision "multiple-network", preserve_order: true, type: 'shell', inline: <<-SHELL master.vm.provision "swarm-webapp", type: 'shell', inline: <<-SHELL
set -xeuo pipefail set -euo pipefail
declare -a docker_opts=()
if ! docker network ls | grep -F foo-internal; then
! #{env_true_str?('ENABLE_DOCKER_IPV6')} || docker_opts=(--ipv6 --subnet fd05:8f23:c937:1::/64)
docker network create --internal "${docker_opts[@]}" foo-internal
fi
if ! docker network ls | grep -F bar-external; then
! #{env_true_str?('ENABLE_DOCKER_IPV6')} || docker_opts=(--ipv6 --subnet fd05:8f23:c937:2::/64)
docker network create "${docker_opts[@]}" bar-external
fi
for app in internal-multinet-app:7000 public-multinet-app:17070; do
if ! docker inspect "${app%:*}" &>/dev/null; then
docker run -d --restart unless-stopped --name "${app%:*}" \
-p "${app#*:}":80 --env name="${app}" \
--network foo-internal \
192.168.56.130:5000/chaifeng/hostname-webapp
docker network connect bar-external "${app%:*}"
fi
done
ufw-docker allow public-multinet-app 80 bar-external
ufw-docker allow internal-multinet-app 80 foo-internal
SHELL
master.vm.provision "swarm-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL
set -xeuo pipefail
declare -a docker_opts=()
if #{env_true_str?('ENABLE_DOCKER_IPV6')}; then
docker inspect ip6net >/dev/null ||
docker network create --driver overlay --ipv6 ip6net
docker_opts+=(--network ip6net)
fi
for name in public:29090 local:9000; do for name in public:29090 local:9000; do
webapp="${name%:*}_service" webapp="${name%:*}_service"
port="${name#*:}" port="${name#*:}"
if docker service inspect "$webapp" &>/dev/null; then docker service rm "$webapp"; fi if docker service inspect "$webapp" &>/dev/null; then docker service rm "$webapp"; fi
docker service create --name "$webapp" "${docker_opts[@]}" \ docker service create --name "$webapp" \
--publish "${port}:80" --env name="$webapp" --replicas #{worker_count} #{private_registry}/chaifeng/hostname-webapp --publish "${port}:80" --env name="$webapp" --replicas 3 #{private_registry}/chaifeng/hostname-webapp
done done
ufw-docker service allow public_service 80/tcp ufw-docker service allow public_service 80/tcp
docker service inspect "public_multiport" ||
docker service create --name "public_multiport" "${docker_opts[@]}" \
--publish "40080:80" --publish "47000:7000" --publish "48080:8080" \
--env name="public_multiport" --replicas #{worker_count + 1} #{private_registry}/chaifeng/hostname-webapp
ufw-docker service allow public_multiport 80/tcp
ufw-docker service allow public_multiport 8080/tcp
SHELL SHELL
end end
1.upto worker_count do |ip| 1.upto 2 do |ip|
config.vm.define "node#{ip}" do | node | config.vm.define "node#{ip}" do | node |
node.vm.hostname = "node#{ip}" node.vm.hostname = "node#{ip}"
node.vm.network "private_network", ip: "#{ip_prefix}.#{ 130 + ip }" node.vm.network "private_network", ip: "#{ip_prefix}.#{ 130 + ip }"
if env_true?('ENABLE_DOCKER_IPV6')
node.vm.network "private_network", ip: "#{ip6_prefix}:0:cafe::#{ 130 + ip }", type: "static", netmast: 64, auto_config: true
end
node.vm.provision "node#{ip}-swarm-join", preserve_order: true, type: 'shell', inline: <<-SHELL node.vm.provision "swarm-join", type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
docker info | fgrep 'Swarm: active' && exit 0 docker info | fgrep 'Swarm: active' && exit 0
@ -244,92 +154,24 @@ DOCKERFILE
end end
end end
config.vm.define "node-internal" do |node|
node.vm.hostname = "node-internal"
node.vm.network "private_network", ip: "#{ip_prefix}.142"
if env_true?('ENABLE_DOCKER_IPV6')
node.vm.network "private_network", ip: "#{ip6_prefix}:0:cafe::142", type: "static", netmast: 64, auto_config: true
end
end
config.vm.define "external" do |external| config.vm.define "external" do |external|
external.vm.hostname = "external" external.vm.hostname = "external"
external.vm.network "private_network", ip: "#{ip_prefix}.127" external.vm.network "private_network", ip: "#{ip_prefix}.127"
if env_true?('ENABLE_DOCKER_IPV6')
external.vm.network "private_network", ip: "#{ip6_prefix}:0:eeee::127", type: "static", netmast: 64, auto_config: true
end
external.vm.provision "testing", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL external.vm.provision "testing", type: 'shell', inline: <<-SHELL
set -xuo pipefail set -euo pipefail
error_count=0 set -x
function test-webapp() { server="http://#{ip_prefix}.130"
local actual="" function test-webapp() { timeout 3 curl --silent "$@"; }
test-webapp "$server:18080"
! test-webapp "$server:8000"
if [[ "$#" -eq 2 ]]; then test-webapp "$server:29090"
local expect_fail='!' ! test-webapp "$server:9000"
url="$2"
else
url="$1"
fi
timeout 3 curl --silent "$url" || actual='!'
if [[ "${expect_fail:-}" = "${actual}" ]]; then
echo "OK: '$url' is ${expect_fail:+NOT }accessible${expect_fail:+ (should NOT be)}."
else
echo "FAIL: '$url' is ${expect_fail:+}${expect_fail:-NOT }accessible${expect_fail:-}."
(( ++ error_count ))
return 1
fi
} 2>/dev/null
function run_tests() {
local server="$1"
test-webapp "$server:18080"
test-webapp ! "$server:8000"
test-webapp "$server:17070" # multiple networks app
test-webapp ! "$server:7000" # internal multiple networks app
# Docker Swarm
test-webapp ! "$server:9000"
test-webapp ! "$server:47000"
}
function run_tests_ipv4() {
local server="$1"
# Docker Swarm
test-webapp "$server:29090"
test-webapp "$server:40080"
test-webapp "$server:48080"
}
function run_tests_ipv6() {
local server="$1"
echo TODO: It seems that Docker Swarm does not support IPv6 well >&2
test-webapp ! "$server:29090" # it is accessible via IPv4
test-webapp ! "$server:40080" # it is accessible via IPv4
test-webapp ! "$server:48080" # it is accessible via IPv4
}
run_tests "http://#{ip_prefix}.130"
run_tests_ipv4 "http://#{ip_prefix}.130"
if #{env_true_str?('ENABLE_DOCKER_IPV6')}; then
run_tests "http://[#{ip6_prefix}:0:cafe::130]"
run_tests_ipv6 "http://[#{ip6_prefix}:0:cafe::130]"
fi
{
echo "=====================" echo "====================="
if [[ "$error_count" -eq 0 ]]; then echo " TEST DONE " echo " TEST DONE "
else echo " TESTS FAIL: ${error_count}"
fi
echo "=====================" echo "====================="
exit "${error_count}"
} 2>/dev/null
SHELL SHELL
end end
end end

@ -1 +1 @@
Subproject commit 27885eb79c11e4652dede994c886ae5f9e30994f Subproject commit ff948334df72f25410d03cbff72b5eaa5e9de409

View file

@ -284,7 +284,6 @@ test-ufw-docker--service-delete-matches-assert() {
docker service update --update-parallelism=0 \ docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \ --env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
--env-add "ufw_public_abcd1234=webapp/deny" \ --env-add "ufw_public_abcd1234=webapp/deny" \
--env-add "DEBUG=false" \
--image "${ufw_docker_agent_image}" \ --image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}" "${ufw_docker_agent}"
} }

View file

@ -12,20 +12,12 @@ source "$working_dir"/bach/bach.sh
@mocktrue ufw status @mocktrue ufw status
@mocktrue grep -Fq "Status: active" @mocktrue grep -Fq "Status: active"
@mock iptables --version @ignore remove_blank_lines
@mocktrue grep -F '(legacy)'
@mocktrue docker -v
@mock docker -v === @stdout Docker version 0.0.0, build dummy
@mockpipe remove_blank_lines
@ignore echo @ignore echo
@ignore err @ignore err
DEFAULT_PROTO=tcp DEFAULT_PROTO=tcp
GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+"
UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:090502-legacy
} }
function ufw-docker() { function ufw-docker() {
@ -38,41 +30,6 @@ function load-ufw-docker-function() {
@load_function "$working_dir/../ufw-docker" "$1" @load_function "$working_dir/../ufw-docker" "$1"
} }
test-ufw-docker-init-legacy() {
@mocktrue grep -F '(legacy)'
@source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help
}
test-ufw-docker-init-legacy-assert() {
iptables --version
test -n chaifeng/ufw-docker-agent:090502-legacy
trap on-exit EXIT INT TERM QUIT ABRT ERR
@dryrun cat
}
test-ufw-docker-init-nf_tables() {
@mockfalse grep -F '(legacy)'
@source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help
}
test-ufw-docker-init-nf_tables-assert() {
iptables --version
test -n chaifeng/ufw-docker-agent:090502-nf_tables
trap on-exit EXIT INT TERM QUIT ABRT ERR
@dryrun cat
}
test-ufw-docker-init() {
UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:100917
@source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help
}
test-ufw-docker-init-assert() {
test -n chaifeng/ufw-docker-agent:100917
trap on-exit EXIT INT TERM QUIT ABRT ERR
@dryrun cat
}
test-ufw-docker-help() { test-ufw-docker-help() {
ufw-docker help ufw-docker help
} }
@ -91,23 +48,11 @@ test-ufw-docker-without-parameters-assert() {
test-ufw-is-disabled() { test-ufw-is-disabled() {
@mockfalse grep -Fq "Status: active" @mockfalse grep -Fq "Status: active"
@mock iptables --version === @stdout 'iptables v1.8.4 (legacy)'
ufw-docker ufw-docker
} }
test-ufw-is-disabled-assert() { test-ufw-is-disabled-assert() {
die "UFW is disabled or you are not root user, or mismatched iptables legacy/nf_tables, current iptables v1.8.4 (legacy)" die "UFW is disabled or you are not root user."
ufw-docker--help
}
test-docker-is-installed() {
@mockfalse docker -v
ufw-docker
}
test-docker-is-installed-assert() {
die "Docker executable not found."
ufw-docker--help ufw-docker--help
} }
@ -128,14 +73,6 @@ test-ufw-docker-install-assert() {
} }
test-ufw-docker-install--docker-subnets() {
ufw-docker install --docker-subnets
}
test-ufw-docker-install--docker-subnets-assert() {
ufw-docker--install --docker-subnets
}
test-ufw-docker-check() { test-ufw-docker-check() {
ufw-docker check ufw-docker check
} }
@ -144,14 +81,6 @@ test-ufw-docker-check-assert() {
} }
test-ufw-docker-check--docker-subnets() {
ufw-docker check --docker-subnets
}
test-ufw-docker-check--docker-subnets-assert() {
ufw-docker--check --docker-subnets
}
test-ufw-docker-service() { test-ufw-docker-service() {
ufw-docker service allow httpd ufw-docker service allow httpd
} }
@ -214,7 +143,7 @@ test-ufw-docker-list-httpd() {
ufw-docker list httpd ufw-docker list httpd
} }
test-ufw-docker-list-httpd-assert() { test-ufw-docker-list-httpd-assert() {
ufw-docker--list 'httpd-container-name\(/v6\)\?' "" tcp "" ufw-docker--list httpd-container-name "" tcp
} }
@ -223,7 +152,7 @@ test-ufw-docker-allow-httpd() {
ufw-docker allow httpd ufw-docker allow httpd
} }
test-ufw-docker-allow-httpd-assert() { test-ufw-docker-allow-httpd-assert() {
ufw-docker--allow httpd-container-name "" tcp "" ufw-docker--allow httpd-container-name "" tcp
} }
@ -232,7 +161,7 @@ test-ufw-docker-allow-httpd-80() {
ufw-docker allow httpd 80 ufw-docker allow httpd 80
} }
test-ufw-docker-allow-httpd-80-assert() { test-ufw-docker-allow-httpd-80-assert() {
ufw-docker--allow httpd-container-name 80 tcp "" ufw-docker--allow httpd-container-name 80 tcp
} }
@ -241,7 +170,7 @@ test-ufw-docker-allow-httpd-80tcp() {
ufw-docker allow httpd 80/tcp ufw-docker allow httpd 80/tcp
} }
test-ufw-docker-allow-httpd-80tcp-assert() { test-ufw-docker-allow-httpd-80tcp-assert() {
ufw-docker--allow httpd-container-name 80 tcp "" ufw-docker--allow httpd-container-name 80 tcp
} }
@ -250,7 +179,7 @@ test-ufw-docker-allow-httpd-80udp() {
ufw-docker allow httpd 80/udp ufw-docker allow httpd 80/udp
} }
test-ufw-docker-allow-httpd-80udp-assert() { test-ufw-docker-allow-httpd-80udp-assert() {
ufw-docker--allow httpd-container-name 80 udp "" ufw-docker--allow httpd-container-name 80 udp
} }
@ -262,12 +191,21 @@ test-ASSERT-FAIL-ufw-docker-allow-httpd-INVALID-port() {
} }
test-ufw-docker-list-httpd() {
@mock ufw-docker--instance-name httpd === @stdout httpd-container-name
ufw-docker list httpd
}
test-ufw-docker-list-httpd-assert() {
ufw-docker--list httpd-container-name "" tcp
}
test-ufw-docker-delete-allow-httpd() { test-ufw-docker-delete-allow-httpd() {
@mock ufw-docker--instance-name httpd === @stdout httpd-container-name @mock ufw-docker--instance-name httpd === @stdout httpd-container-name
ufw-docker delete allow httpd ufw-docker delete allow httpd
} }
test-ufw-docker-delete-allow-httpd-assert() { test-ufw-docker-delete-allow-httpd-assert() {
ufw-docker--delete 'httpd-container-name\(/v6\)\?' "" tcp "" ufw-docker--delete httpd-container-name "" tcp
} }
@ -284,31 +222,7 @@ function setup-ufw-docker--allow() {
load-ufw-docker-function ufw-docker--allow load-ufw-docker-function ufw-docker--allow
@mocktrue docker inspect instance-name @mocktrue docker inspect instance-name
@mock docker inspect --format '{{range $name, $net := .NetworkSettings.Networks}}{{if $net.IPAddress}}{{$name}} {{$net.IPAddress}}{{"\n"}}{{end}}{{if $net.GlobalIPv6Address}}{{$name}} {{$net.GlobalIPv6Address}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout "default 172.18.0.3" @mock docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' instance-name === @stdout 172.18.0.3
@mock docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout 5000/tcp 8080/tcp 5353/udp
}
function setup-IPv6-ufw-docker--allow() {
load-ufw-docker-function ufw-docker--allow
@mocktrue docker inspect instance-name
@mock docker inspect --format '{{range $name, $net := .NetworkSettings.Networks}}{{if $net.IPAddress}}{{$name}} {{$net.IPAddress}}{{"\n"}}{{end}}{{if $net.GlobalIPv6Address}}{{$name}} {{$net.GlobalIPv6Address}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout "default 172.18.0.3" "default fd00:cf::42"
@mock docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout 5000/tcp 8080/tcp 5353/udp
}
function setup-ufw-docker--allow--multinetwork() {
load-ufw-docker-function ufw-docker--allow
@mocktrue docker inspect instance-name
@mock docker inspect --format '{{range $name, $net := .NetworkSettings.Networks}}{{if $net.IPAddress}}{{$name}} {{$net.IPAddress}}{{"\n"}}{{end}}{{if $net.GlobalIPv6Address}}{{$name}} {{$net.GlobalIPv6Address}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout "default 172.18.0.3" "awesomenet 172.19.0.7"
@mock docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout 5000/tcp 8080/tcp 5353/udp
}
function setup-IPv6-ufw-docker--allow--multinetwork() {
load-ufw-docker-function ufw-docker--allow
@mocktrue docker inspect instance-name
@mock docker inspect --format '{{range $name, $net := .NetworkSettings.Networks}}{{if $net.IPAddress}}{{$name}} {{$net.IPAddress}}{{"\n"}}{{end}}{{if $net.GlobalIPv6Address}}{{$name}} {{$net.GlobalIPv6Address}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout "default 172.18.0.3" "default fd00:cf::42" "awesomenet 172.19.0.7" "awesomenet fd00:cf::207"
@mock docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout 5000/tcp 8080/tcp 5353/udp @mock docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout 5000/tcp 8080/tcp 5353/udp
} }
@ -355,7 +269,7 @@ test-ufw-docker--allow-instance-and-match-the-port() {
ufw-docker--allow instance-name 5000 tcp ufw-docker--allow instance-name 5000 tcp
} }
test-ufw-docker--allow-instance-and-match-the-port-assert() { test-ufw-docker--allow-instance-and-match-the-port-assert() {
ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp
} }
@ -365,9 +279,9 @@ test-ufw-docker--allow-instance-all-published-port() {
ufw-docker--allow instance-name "" "" ufw-docker--allow instance-name "" ""
} }
test-ufw-docker--allow-instance-all-published-port-assert() { test-ufw-docker--allow-instance-all-published-port-assert() {
ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp
ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp
ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default ufw-docker--add-rule instance-name 172.18.0.3 5353 udp
} }
@ -377,116 +291,14 @@ test-ufw-docker--allow-instance-all-published-tcp-port() {
ufw-docker--allow instance-name "" tcp ufw-docker--allow instance-name "" tcp
} }
test-ufw-docker--allow-instance-all-published-tcp-port-assert() { test-ufw-docker--allow-instance-all-published-tcp-port-assert() {
ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp
ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp
ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default # FIXME ufw-docker--add-rule instance-name 172.18.0.3 5353 udp # FIXME
}
test-ufw-docker--allow-instance-all-published-port-multinetwork() {
setup-ufw-docker--allow--multinetwork
ufw-docker--allow instance-name "" ""
}
test-ufw-docker--allow-instance-all-published-port-multinetwork-assert() {
ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default
ufw-docker--add-rule instance-name 172.19.0.7 5000 tcp awesomenet
ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default
ufw-docker--add-rule instance-name 172.19.0.7 8080 tcp awesomenet
ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default
ufw-docker--add-rule instance-name 172.19.0.7 5353 udp awesomenet
}
test-ufw-docker--allow-instance-all-published-port-multinetwork-select-network() {
setup-ufw-docker--allow--multinetwork
ufw-docker--allow instance-name "" "" awesomenet
}
test-ufw-docker--allow-instance-all-published-port-multinetwork-select-network-assert() {
ufw-docker--add-rule instance-name 172.19.0.7 5000 tcp awesomenet
ufw-docker--add-rule instance-name 172.19.0.7 8080 tcp awesomenet
ufw-docker--add-rule instance-name 172.19.0.7 5353 udp awesomenet
}
test-IPv6-ufw-docker--allow-instance-and-match-the-port() {
setup-IPv6-ufw-docker--allow
ufw-docker--allow instance-name 5000 tcp
}
test-IPv6-ufw-docker--allow-instance-and-match-the-port-assert() {
ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 5000 tcp default
}
test-IPv6-ufw-docker--allow-instance-all-published-port() {
setup-IPv6-ufw-docker--allow
ufw-docker--allow instance-name "" ""
}
test-IPv6-ufw-docker--allow-instance-all-published-port-assert() {
ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 5000 tcp default
ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 8080 tcp default
ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 5353 udp default
}
test-IPv6-ufw-docker--allow-instance-all-published-tcp-port() {
setup-IPv6-ufw-docker--allow
ufw-docker--allow instance-name "" tcp
}
test-IPv6-ufw-docker--allow-instance-all-published-tcp-port-assert() {
ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 5000 tcp default
ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 8080 tcp default
ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default # FIXME
ufw-docker--add-rule instance-name/v6 fd00:cf::42 5353 udp default # FIXME
}
test-IPv6-ufw-docker--allow-instance-all-published-port-multinetwork() {
setup-IPv6-ufw-docker--allow--multinetwork
ufw-docker--allow instance-name "" ""
}
test-IPv6-ufw-docker--allow-instance-all-published-port-multinetwork-assert() {
ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 5000 tcp default
ufw-docker--add-rule instance-name 172.19.0.7 5000 tcp awesomenet
ufw-docker--add-rule instance-name/v6 fd00:cf::207 5000 tcp awesomenet
ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 8080 tcp default
ufw-docker--add-rule instance-name 172.19.0.7 8080 tcp awesomenet
ufw-docker--add-rule instance-name/v6 fd00:cf::207 8080 tcp awesomenet
ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default
ufw-docker--add-rule instance-name/v6 fd00:cf::42 5353 udp default
ufw-docker--add-rule instance-name 172.19.0.7 5353 udp awesomenet
ufw-docker--add-rule instance-name/v6 fd00:cf::207 5353 udp awesomenet
}
test-IPv6-ufw-docker--allow-instance-all-published-port-multinetwork-select-network() {
setup-IPv6-ufw-docker--allow--multinetwork
ufw-docker--allow instance-name "" "" awesomenet
}
test-IPv6-ufw-docker--allow-instance-all-published-port-multinetwork-select-network-assert() {
ufw-docker--add-rule instance-name 172.19.0.7 5000 tcp awesomenet
ufw-docker--add-rule instance-name/v6 fd00:cf::207 5000 tcp awesomenet
ufw-docker--add-rule instance-name 172.19.0.7 8080 tcp awesomenet
ufw-docker--add-rule instance-name/v6 fd00:cf::207 8080 tcp awesomenet
ufw-docker--add-rule instance-name 172.19.0.7 5353 udp awesomenet
ufw-docker--add-rule instance-name/v6 fd00:cf::207 5353 udp awesomenet
} }
test-ufw-docker--add-rule-a-non-existing-rule() { test-ufw-docker--add-rule-a-non-existing-rule() {
@mockfalse ufw-docker--list webapp 5000 tcp "" @mockfalse ufw-docker--list webapp 5000 tcp
load-ufw-docker-function ufw-docker--add-rule load-ufw-docker-function ufw-docker--add-rule
ufw-docker--add-rule webapp 172.18.0.4 5000 tcp ufw-docker--add-rule webapp 172.18.0.4 5000 tcp
@ -495,54 +307,29 @@ test-ufw-docker--add-rule-a-non-existing-rule-assert() {
ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp" ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp"
} }
test-ufw-docker--add-rule-a-non-existing-rule-with-network() {
@mockfalse ufw-docker--list webapp 5000 tcp default
load-ufw-docker-function ufw-docker--add-rule
ufw-docker--add-rule webapp 172.18.0.4 5000 tcp default
}
test-ufw-docker--add-rule-a-non-existing-rule-with-network-assert() {
ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default"
}
test-ufw-docker--add-rule-modify-an-existing-rule() { test-ufw-docker--add-rule-modify-an-existing-rule() {
@mocktrue ufw-docker--list webapp 5000 tcp default @mocktrue ufw-docker--list webapp 5000 tcp
@mock ufw --dry-run route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default" === @echo @mocktrue ufw --dry-run route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp"
@mockfalse grep "^Skipping" @mockfalse grep "^Skipping"
load-ufw-docker-function ufw-docker--add-rule load-ufw-docker-function ufw-docker--add-rule
ufw-docker--add-rule webapp 172.18.0.4 5000 tcp default ufw-docker--add-rule webapp 172.18.0.4 5000 tcp
} }
test-ufw-docker--add-rule-modify-an-existing-rule-assert() { test-ufw-docker--add-rule-modify-an-existing-rule-assert() {
ufw-docker--delete webapp 5000 tcp default ufw-docker--delete webapp 5000 tcp
ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default" ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp"
}
test-IPv6-ufw-docker--add-rule-modify-an-existing-rule() {
@mocktrue ufw-docker--list webapp/v6 5000 tcp default
@mock ufw --dry-run route allow proto tcp from any to fd00:cf::42 port 5000 comment "allow webapp/v6 5000/tcp default" === @echo
@mockfalse grep "^Skipping"
load-ufw-docker-function ufw-docker--add-rule
ufw-docker--add-rule webapp/v6 fd00:cf::42 5000 tcp default
}
test-IPv6-ufw-docker--add-rule-modify-an-existing-rule-assert() {
ufw-docker--delete webapp/v6 5000 tcp default
ufw route allow proto tcp from any to fd00:cf::42 port 5000 comment "allow webapp/v6 5000/tcp default"
} }
test-ufw-docker--add-rule-skip-an-existing-rule() { test-ufw-docker--add-rule-skip-an-existing-rule() {
@mocktrue ufw-docker--list webapp 5000 tcp "" @mocktrue ufw-docker--list webapp 5000 tcp
@mocktrue ufw --dry-run route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp" @mocktrue ufw --dry-run route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp"
@mocktrue grep "^Skipping" @mocktrue grep "^Skipping"
load-ufw-docker-function ufw-docker--add-rule load-ufw-docker-function ufw-docker--add-rule
ufw-docker--add-rule webapp 172.18.0.4 5000 tcp "" ufw-docker--add-rule webapp 172.18.0.4 5000 tcp
} }
test-ufw-docker--add-rule-skip-an-existing-rule-assert() { test-ufw-docker--add-rule-skip-an-existing-rule-assert() {
@do-nothing @do-nothing
@ -550,16 +337,17 @@ test-ufw-docker--add-rule-skip-an-existing-rule-assert() {
test-ufw-docker--add-rule-modify-an-existing-rule-without-port() { test-ufw-docker--add-rule-modify-an-existing-rule-without-port() {
@mocktrue ufw-docker--list webapp "" tcp "" @mocktrue ufw-docker--list webapp "" tcp
@mock ufw --dry-run route allow proto tcp from any to 172.18.0.4 comment "allow webapp" === @echo
@mocktrue ufw --dry-run route allow proto tcp from any to 172.18.0.4 comment "allow webapp"
@mockfalse grep "^Skipping" @mockfalse grep "^Skipping"
load-ufw-docker-function ufw-docker--add-rule load-ufw-docker-function ufw-docker--add-rule
ufw-docker--add-rule webapp 172.18.0.4 "" tcp "" ufw-docker--add-rule webapp 172.18.0.4 "" tcp
} }
test-ufw-docker--add-rule-modify-an-existing-rule-without-port-assert() { test-ufw-docker--add-rule-modify-an-existing-rule-without-port-assert() {
ufw-docker--delete webapp "" tcp "" ufw-docker--delete webapp "" tcp
ufw route allow proto tcp from any to 172.18.0.4 comment "allow webapp" ufw route allow proto tcp from any to 172.18.0.4 comment "allow webapp"
} }
@ -577,7 +365,7 @@ test-ufw-docker--instance-name-found-a-name() {
} }
test-ufw-docker--instance-name-found-a-name-assert() { test-ufw-docker--instance-name-found-a-name-assert() {
docker inspect --format="{{.Name}}" foo docker inspect --format="{{.Name}}" foo
@dryrun echo -n foo echo -n foo
} }
@ -596,14 +384,11 @@ test-ufw-docker--instance-name-found-an-id-assert() {
test-ufw-docker--list-name() { test-ufw-docker--list-name() {
@mocktrue ufw status numbered @mocktrue ufw status numbered
@mockfalse grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$"
@mockfalse grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\$"
load-ufw-docker-function ufw-docker--list load-ufw-docker-function ufw-docker--list
ufw-docker--list foo ufw-docker--list foo
} }
test-ufw-docker--list-name-assert() { test-ufw-docker--list-name-assert() {
grep "# allow foo\$" grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\$"
} }
test-ufw-docker--list-name-udp() { test-ufw-docker--list-name-udp() {
@ -612,7 +397,7 @@ test-ufw-docker--list-name-udp() {
ufw-docker--list foo "" udp ufw-docker--list foo "" udp
} }
test-ufw-docker--list-name-udp-assert() { test-ufw-docker--list-name-udp-assert() {
grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$" grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\$"
} }
@ -622,7 +407,7 @@ test-ufw-docker--list-name-80() {
ufw-docker--list foo 80 ufw-docker--list foo 80
} }
test-ufw-docker--list-name-80-assert() { test-ufw-docker--list-name-80-assert() {
grep "# allow foo\\( 80\\/tcp\\)\\( [[:graph:]]*\\)\$" grep "# allow foo\\( 80\\/tcp\\)\\?\$"
} }
@ -632,29 +417,7 @@ test-ufw-docker--list-name-80-udp() {
ufw-docker--list foo 80 udp ufw-docker--list foo 80 udp
} }
test-ufw-docker--list-name-80-udp-assert() { test-ufw-docker--list-name-80-udp-assert() {
grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$" grep "# allow foo\\( 80\\/udp\\)\\?\$"
}
test-ufw-docker--list-grep-without-network() {
@mocktrue ufw status numbered
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 80 udp
}
test-ufw-docker--list-grep-without-network-assert() {
grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$"
}
test-ufw-docker--list-grep-without-network-and-port() {
@mocktrue ufw status numbered
@mockfalse grep "# allow foo\\( 80\\/tcp\\)\\( [[:graph:]]*\\)\$"
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 80
}
test-ufw-docker--list-grep-without-network-and-port-assert() {
grep "# allow foo\\( 80\\/tcp\\)\$"
} }
@ -671,7 +434,7 @@ test-ufw-docker--list-number-assert() {
test-ufw-docker--delete-empty-result() { test-ufw-docker--delete-empty-result() {
@mock ufw-docker--list-number webapp 80 tcp === @stdout "" @mock ufw-docker--list-number webapp 80 tcp === @stdout ""
@mockpipe sort -rn @mock sort -rn
load-ufw-docker-function ufw-docker--delete load-ufw-docker-function ufw-docker--delete
ufw-docker--delete webapp 80 tcp ufw-docker--delete webapp 80 tcp
@ -683,7 +446,7 @@ test-ufw-docker--delete-empty-result-assert() {
test-ufw-docker--delete-all() { test-ufw-docker--delete-all() {
@mock ufw-docker--list-number webapp 80 tcp === @stdout 5 8 9 @mock ufw-docker--list-number webapp 80 tcp === @stdout 5 8 9
@mockpipe sort -rn @mock sort -rn
load-ufw-docker-function ufw-docker--delete load-ufw-docker-function ufw-docker--delete
ufw-docker--delete webapp 80 tcp ufw-docker--delete webapp 80 tcp

View file

@ -5,50 +5,28 @@ set -euo pipefail
LANG=en_US.UTF-8 LANG=en_US.UTF-8
LANGUAGE=en_US: LANGUAGE=en_US:
LC_ALL=en_US.UTF-8 LC_ALL=en_US.UTF-8
PATH="/bin:/usr/bin:/sbin:/usr/sbin:/snap/bin/" PATH="/bin:/usr/bin:/sbin:/usr/sbin"
GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+"
DEFAULT_PROTO=tcp DEFAULT_PROTO=tcp
ufw_docker_agent=ufw-docker-agent ufw_docker_agent=ufw-docker-agent
ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:250702-nf_tables}" ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:200812}"
if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then
if iptables --version | grep -F '(legacy)' &>/dev/null; then
ufw_docker_agent_image="${ufw_docker_agent_image%-*}-legacy"
else
ufw_docker_agent_image="${ufw_docker_agent_image%-*}-nf_tables"
fi
fi
test -n "$ufw_docker_agent_image"
function ufw-docker--status() { function ufw-docker--status() {
ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME\(/v6\)\?" ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME"
} }
function ufw-docker--list() { function ufw-docker--list() {
local INSTANCE_NAME="$1" local INSTANCE_NAME="$1"
local INSTANCE_PORT="${2:-}" local INSTANCE_PORT="${2:-}"
local PROTO="${3:-${DEFAULT_PROTO}}" local PROTO="${3:-${DEFAULT_PROTO}}"
local NETWORK="${4:-}"
local params_count="$#"
if [[ -z "$INSTANCE_PORT" ]]; then if [[ -z "$INSTANCE_PORT" ]]; then
INSTANCE_PORT="[[:digit:]]\\+" INSTANCE_PORT="[[:digit:]]\\+"
PROTO="\\(tcp\\|udp\\)" PROTO="\\(tcp\\|udp\\)"
fi fi
ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\?\$"
if [[ -z "$NETWORK" ]]; then
NETWORK="[[:graph:]]*"
fi
local ufw_output
ufw_output="$(ufw status numbered)"
grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\( ${NETWORK}\\)\$" <<< "$ufw_output" ||
grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" <<< "$ufw_output" ||
grep "# allow ${INSTANCE_NAME}\$" <<< "$ufw_output"
} }
function ufw-docker--list-number() { function ufw-docker--list-number() {
@ -66,15 +44,13 @@ function ufw-docker--allow() {
local INSTANCE_NAME="$1" local INSTANCE_NAME="$1"
local INSTANCE_PORT="$2" local INSTANCE_PORT="$2"
local PROTO="$3" local PROTO="$3"
local NETWORK="${4:-}"
local NETWORK_ADDRESSES PORT_PROTO_LIST PROT_PROTO IP SUFFIX
docker inspect "$INSTANCE_NAME" &>/dev/null || docker inspect "$INSTANCE_NAME" &>/dev/null ||
die "Docker instance \"$INSTANCE_NAME\" doesn't exist." die "Docker instance \"$INSTANCE_NAME\" doesn't exist."
mapfile -t NETWORK_ADDRESSES < <(docker inspect --format '{{range $name, $net := .NetworkSettings.Networks}}{{if $net.IPAddress}}{{$name}} {{$net.IPAddress}}{{"\n"}}{{end}}{{if $net.GlobalIPv6Address}}{{$name}} {{$net.GlobalIPv6Address}}{{"\n"}}{{end}}{{end}}' "$INSTANCE_NAME" 2>/dev/null | remove_blank_lines) mapfile -t INSTANCE_IP_ADDRESSES < <(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' "$INSTANCE_NAME" 2>/dev/null | remove_blank_lines)
[[ -z "${NETWORK_ADDRESSES:-}" ]] && die "Could not find a running instance \"$INSTANCE_NAME\"." [[ -z "${INSTANCE_IP_ADDRESSES:-}" ]] && die "Could not find a running instance \"$INSTANCE_NAME\"."
mapfile -t PORT_PROTO_LIST < <(docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' "$INSTANCE_NAME" | remove_blank_lines) mapfile -t PORT_PROTO_LIST < <(docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' "$INSTANCE_NAME" | remove_blank_lines)
@ -83,26 +59,19 @@ function ufw-docker--allow() {
return 1 return 1
fi fi
local count=0 RETVAL=1
for PORT_PROTO in "${PORT_PROTO_LIST[@]}"; do for PORT_PROTO in "${PORT_PROTO_LIST[@]}"; do
if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then
for item in "${NETWORK_ADDRESSES[@]}"; do for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do
INSTANCE_NETWORK="${item% *}" ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}"
IP="${item#* }" RETVAL="$?"
if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then
continue
fi
if [[ "$IP" = *:* ]]; then SUFFIX="/v6"; else SUFFIX=""; fi
ufw-docker--add-rule "${INSTANCE_NAME}${SUFFIX}" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}"
(( ++count ))
done done
fi fi
done done
if [[ "$count" -eq 0 ]]; then if [[ "$RETVAL" -ne 0 ]]; then
err "Fail to add rule(s), cannot find the published port ${INSTANCE_PORT}/${PROTO} of instance \"${INSTANCE_NAME}\" or cannot update outdated rule(s)." err "Fail to add rule(s), cannot find the published port ${INSTANCE_PORT}/${PROTO} of instance \"${INSTANCE_NAME}\" or cannot update outdated rule(s)."
return 1
fi fi
return 0 return "$RETVAL"
} }
function ufw-docker--add-service-rule() { function ufw-docker--add-service-rule() {
@ -123,11 +92,10 @@ function ufw-docker--add-rule() {
local INSTANCE_IP_ADDRESS="$2" local INSTANCE_IP_ADDRESS="$2"
local PORT="$3" local PORT="$3"
local PROTO="$4" local PROTO="$4"
local NETWORK="${5:-}"
declare comment declare comment
echo "allow ${INSTANCE_NAME} ${PORT}/${PROTO} ${NETWORK}" echo "allow ${INSTANCE_NAME} ${PORT}/${PROTO}"
typeset -a UFW_OPTS typeset -a UFW_OPTS
UFW_OPTS=(route allow proto "${PROTO}" UFW_OPTS=(route allow proto "${PROTO}"
from any to "$INSTANCE_IP_ADDRESS") from any to "$INSTANCE_IP_ADDRESS")
@ -136,15 +104,12 @@ function ufw-docker--add-rule() {
UFW_OPTS+=(port "${PORT}") UFW_OPTS+=(port "${PORT}")
comment="$comment ${PORT}/${PROTO}" comment="$comment ${PORT}/${PROTO}"
} }
[[ -n "$NETWORK" ]] && {
comment="$comment ${NETWORK}"
}
UFW_OPTS+=(comment "$comment") UFW_OPTS+=(comment "$comment")
if ufw-docker--list "$INSTANCE_NAME" "$PORT" "$PROTO" "$NETWORK" &>/dev/null; then if ufw-docker--list "$INSTANCE_NAME" "$PORT" "$PROTO" &>/dev/null; then
ufw --dry-run "${UFW_OPTS[@]}" | grep "^Skipping" && return 0 ufw --dry-run "${UFW_OPTS[@]}" | grep "^Skipping" && return 0
err "Remove outdated rule." err "Remove outdated rule."
ufw-docker--delete "$INSTANCE_NAME" "$PORT" "$PROTO" "$NETWORK" ufw-docker--delete "$INSTANCE_NAME" "$PORT" "$PROTO"
fi fi
echo ufw "${UFW_OPTS[@]}" echo ufw "${UFW_OPTS[@]}"
ufw "${UFW_OPTS[@]}" ufw "${UFW_OPTS[@]}"
@ -286,7 +251,6 @@ function ufw-docker--service-delete() {
docker service update --update-parallelism=0 \ docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \ --env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
--env-add "${service_env}" \ --env-add "${service_env}" \
--env-add DEBUG="${DEBUG-}" \
--image "${ufw_docker_agent_image}" \ --image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}" "${ufw_docker_agent}"
} }
@ -296,78 +260,36 @@ function ufw-docker--raw-command() {
} }
after_rules="/etc/ufw/after.rules" after_rules="/etc/ufw/after.rules"
after6_rules="/etc/ufw/after6.rules"
function ufw-docker--check() { function ufw-docker--check() {
err "\\n########## iptables -n -L DOCKER-USER ##########" err "\\n########## iptables -n -L DOCKER-USER ##########"
iptables -n -L DOCKER-USER iptables -n -L DOCKER-USER
err "\\n\\n########## diff $after_rules ##########" err "\\n\\n########## diff $after_rules ##########"
ufw-docker--check-install "$@" && err "\\nCheck IPv4 firewall rules done." ufw-docker--check-install && err "\\nCheck done."
if command -v ip6tables >/dev/null 2>&1; then
err "\\n########## ip6tables -n -L DOCKER-USER ##########"
ip6tables -n -L DOCKER-USER
err "\\n\\n########## diff $after6_rules ##########"
ufw-docker--check-install_ipv6 "$@" && err "\\nCheck IPv6 firewall rules done."
fi
} }
declare -a files_to_be_deleted declare -a files_to_be_deleted
function rm-on-exit() { function rm-on-exit() {
[[ $# -gt 0 ]] && files_to_be_deleted+=("$@") [[ $# -gt 0 ]] && files_to_be_deleted+=("$@")
} }
function on-exit() { function on-exit() {
for file in "${files_to_be_deleted[@]:-}"; do for file in "${files_to_be_deleted[@]:-}"; do
[[ -f "$file" ]] && rm -r "$file" [[ -f "$file" ]] && rm -r "$file"
done done
files_to_be_deleted=() files_to_be_deleted=()
} }
trap on-exit EXIT INT TERM QUIT ABRT ERR trap on-exit EXIT INT TERM QUIT ABRT ERR
function ufw-docker--list-docker-subnets() {
local ipversion="$1"
shift || true
if [[ -z "${1-}" ]]; then
docker network ls --format '{{.ID}}' |
while read -r net; do
docker network inspect "$net" --format '{{range .IPAM.Config}}{{.Subnet}}{{"\n"}}{{end}}'
done
else
printf "%s\n" "$@"
fi |
while read -r cidr; do
if [[ "${ipversion}" = "IPv4" && "$cidr" = *.* ]] || [[ "${ipversion}" = "IPv6" && "$cidr" = *:* ]]
then echo "$cidr"
fi
done |
sort
}
function ufw-docker--check-install() { function ufw-docker--check-install() {
declare -a cidr_list after_rules_tmp="${after_rules_tmp:-$(mktemp)}"
declare cidr rm-on-exit "$after_rules_tmp"
if [[ -z "${1-}" ]]; then
cidr_list=(10.0.0.0/8 172.16.0.0/12 192.168.0.0/16)
elif [[ "${1-}" = '--docker-subnets' ]]; then
shift || true
mapfile -t cidr_list < <(ufw-docker--list-docker-subnets IPv4 "$@")
fi
if [[ -z "${cidr_list:-}" ]]; then
err "ERROR: Could not find any IPv4 subnets used by docker engine\n"
exit 1
fi
after_rules_tmp="${after_rules_tmp:-$(mktemp)}" sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp"
rm-on-exit "$after_rules_tmp" >> "${after_rules_tmp}" cat <<-\EOF
sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp"
{
cat <<-\EOF
# BEGIN UFW AND DOCKER # BEGIN UFW AND DOCKER
*filter *filter
:ufw-user-forward - [0:0] :ufw-user-forward - [0:0]
@ -375,26 +297,18 @@ function ufw-docker--check-install() {
:DOCKER-USER - [0:0] :DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j ufw-user-forward
EOF -A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
for cidr in "${cidr_list[@]}"; do -A DOCKER-USER -j RETURN -s 192.168.0.0/16
echo "-A DOCKER-USER -j RETURN -s ${cidr}"
done
cat <<-\EOF
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
EOF -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
for cidr in "${cidr_list[@]}"; do -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
echo "-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d ${cidr}" -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
done -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
for cidr in "${cidr_list[@]}"; do -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
echo "-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d ${cidr}"
done
cat <<-\EOF
-A DOCKER-USER -j RETURN -A DOCKER-USER -j RETURN
@ -404,159 +318,55 @@ function ufw-docker--check-install() {
COMMIT COMMIT
# END UFW AND DOCKER # END UFW AND DOCKER
EOF EOF
} >> "${after_rules_tmp}"
diff -u --color=auto "$after_rules" "$after_rules_tmp"
}
function ufw-docker--check-install_ipv6() { diff -u --color=auto "$after_rules" "$after_rules_tmp"
declare -a cidr6_list
declare cidr
if [[ -z "${1-}" ]]; then
cidr6_list=(fd00::/8)
elif [[ "${1-}" = '--docker-subnets' ]]; then
shift || true
mapfile -t cidr6_list < <(ufw-docker--list-docker-subnets IPv6 "$@")
fi
if [[ -z "${cidr6_list:-}" ]]; then
err "INFO: Could not find any IPv6 subnets used by docker engine, will disable IPv6 support.\n"
return 0
fi
after6_rules_tmp="${after6_rules_tmp:-$(mktemp)}"
rm-on-exit "$after6_rules_tmp"
sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after6_rules" > "$after6_rules_tmp"
{
cat <<-\EOF
# BEGIN UFW AND DOCKER
*filter
:ufw6-user-forward - [0:0]
:ufw6-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw6-user-forward
EOF
for cidr in "${cidr6_list[@]}"; do
echo "-A DOCKER-USER -j RETURN -s ${cidr}"
done
cat <<-\EOF
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
EOF
for cidr in "${cidr6_list[@]}"; do
echo "-A DOCKER-USER -j ufw6-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d ${cidr}"
echo "-A DOCKER-USER -j ufw6-docker-logging-deny -p udp -m udp --dport 0:32767 -d ${cidr}"
done
cat <<-\EOF
-A DOCKER-USER -j RETURN
-A ufw6-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw6-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER
EOF
} >> "${after6_rules_tmp}"
diff -u --color=auto "$after6_rules" "$after6_rules_tmp"
} }
function ufw-docker--install() { function ufw-docker--install() {
local changed=false if ! ufw-docker--check-install; then
if ! ufw-docker--check-install "$@"; then local after_rules_bak
changed=true after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~"
local after_rules_bak err "\\nBacking up $after_rules to $after_rules_bak"
after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~" cp "$after_rules" "$after_rules_bak"
err "\\nBacking up $after_rules to $after_rules_bak" cat "$after_rules_tmp" > "$after_rules"
cp "$after_rules" "$after_rules_bak" err "Please restart UFW service manually by using the following command:"
cat "$after_rules_tmp" > "$after_rules" if type systemctl &>/dev/null; then
err " sudo systemctl restart ufw"
else
err " sudo service ufw restart"
fi fi
fi
if ! ufw-docker--check-install_ipv6 "$@"; then
changed=true
local after6_rules_bak
after6_rules_bak="${after6_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~"
err "\\nBacking up $after6_rules to $after6_rules_bak"
cp "$after6_rules" "$after6_rules_bak"
cat "$after6_rules_tmp" > "$after6_rules"
fi
if "$changed"; then
err "Please restart UFW service manually by using the following command:"
if type systemctl &>/dev/null; then
err " sudo systemctl restart ufw"
else
err " sudo service ufw restart"
fi
fi
}
function ufw-docker--install--help() {
cat <<HELP
ufw-docker $1 --docker-subnets [SUBNET1 SUBNET2 …]
Specify which subnets should be used when configuring firewall rules for Docker containers and any allowed networks that communicate with containers.
- If this option is not provided, only standard private LAN subnets are used (RFC1918 for IPv4 and fd00::/8 for IPv6).
- If --docker-subnets is given without any arguments, all Docker network subnets will be automatically detected and used.
- If one or more subnets are specified, these subnets will be used for firewall rules, and they can include any networks that need to communicate with containers—not just the subnets configured by the Docker engine.
You can specify multiple subnets separated by spaces (each in CIDR format).
Examples:
- ufw-docker $1
Use only standard private subnets (default behavior).
- IPv4 subnets: 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
- IPv6 subnet: fd00::/8
- ufw-docker $1 --docker-subnets
Auto-detect and use all Docker network subnets.
- ufw-docker $1 --docker-subnets 10.207.0.0/16 192.168.207.0/24 fd00:cf::/64
Use only the specified subnets, including those outside of Dockers own configuration, for all networks that should be allowed to communicate with containers.
HELP
} }
function ufw-docker--help() { function ufw-docker--help() {
cat <<-EOF >&2 cat <<-EOF >&2
Usage: Usage:
ufw-docker <list|allow> [docker-instance-id-or-name [port[/tcp|/udp]] [network]] ufw-docker <list|allow> [docker-instance-id-or-name [port[/tcp|/udp]]]
ufw-docker delete allow [docker-instance-id-or-name [port[/tcp|/udp]] [network]] ufw-docker delete allow [docker-instance-id-or-name [port[/tcp|/udp]]]
ufw-docker service allow <swarm-service-id-or-name <port</tcp|/udp>>> ufw-docker service allow <swarm-service-id-or-name <port</tcp|/udp>>>
ufw-docker service delete allow <swarm-service-id-or-name> ufw-docker service delete allow <swarm-service-id-or-name>
ufw-docker <install|check> [--docker-subnets [SUBNET0 SUBNET1 ...]]
ufw-docker <status|install|check|help> ufw-docker <status|install|check|help>
Examples: Examples:
ufw-docker help ufw-docker help
ufw-docker check --help
ufw-docker install --help
ufw-docker check # Check the installation of firewall rules ufw-docker check # Check the installation of firewall rules
ufw-docker check --docker-subnets # Auto-detect and use all Docker network subnets ufw-docker install # Install firewall rules
ufw-docker check --docker-subnets 192.168.207.0/24 10.207.0.0/16 fd00:cf::/64
ufw-docker install # Install firewall rules
ufw-docker install --docker-subnets # Auto-detect and use all Docker network subnets
ufw-docker install --docker-subnets 192.168.207.0/24 10.207.0.0/16 fd00:cf::/64
ufw-docker status ufw-docker status
ufw-docker list httpd ufw-docker list httpd
ufw-docker allow httpd ufw-docker allow httpd
ufw-docker allow httpd 80 ufw-docker allow httpd 80
ufw-docker allow httpd 80/tcp ufw-docker allow httpd 80/tcp
ufw-docker allow httpd 80/tcp default
ufw-docker delete allow httpd ufw-docker delete allow httpd
ufw-docker delete allow httpd 80/tcp ufw-docker delete allow httpd 80/tcp
ufw-docker delete allow httpd 80/tcp default
ufw-docker service allow httpd 80/tcp ufw-docker service allow httpd 80/tcp
@ -580,11 +390,7 @@ function die() {
# __main__ # __main__
if ! ufw status 2>/dev/null | grep -Fq "Status: active" ; then if ! ufw status 2>/dev/null | grep -Fq "Status: active" ; then
die "UFW is disabled or you are not root user, or mismatched iptables legacy/nf_tables, current $(iptables --version)" die "UFW is disabled or you are not root user."
fi
if ! docker -v &> /dev/null; then
die "Docker executable not found."
fi fi
ufw_action="${1:-help}" ufw_action="${1:-help}"
@ -612,31 +418,16 @@ case "$ufw_action" in
if [[ "$INSTANCE_PORT" = */udp ]]; then if [[ "$INSTANCE_PORT" = */udp ]]; then
PROTO=udp PROTO=udp
fi fi
shift || true
NETWORK="${1:-}"
INSTANCE_PORT="${INSTANCE_PORT%/*}" INSTANCE_PORT="${INSTANCE_PORT%/*}"
;;&
delete|list) "ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO"
"ufw-docker--$ufw_action" "$INSTANCE_NAME\\(/v6\\)\\?" "$INSTANCE_PORT" "$PROTO" "$NETWORK"
;;
allow)
"ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO" "$NETWORK"
;; ;;
service|raw-command|add-service-rule) service|raw-command|add-service-rule)
shift || true shift || true
"ufw-docker--$ufw_action" "$@" "ufw-docker--$ufw_action" "$@"
;; ;;
install|check) status|install|check)
shift || true
if [[ "${1-}" = @(help|-h|--help) ]]; then
ufw-docker--install--help "$ufw_action"
exit
fi
"ufw-docker--$ufw_action" "$@"
;;
status)
ufw-docker--"$ufw_action" ufw-docker--"$ufw_action"
;; ;;
*) *)