Compare commits

...

31 commits

Author SHA1 Message Date
Chai Feng
c0e54ed8b2
Add suffix to tags
Some checks are pending
Build Images / docker (amd64, map[name:legacy use_iptables_legacy:true]) (push) Waiting to run
Build Images / docker (amd64, map[name:nf_tables use_iptables_legacy:false]) (push) Waiting to run
Build Images / docker (arm64/v8, map[name:legacy use_iptables_legacy:true]) (push) Waiting to run
Build Images / docker (arm64/v8, map[name:nf_tables use_iptables_legacy:false]) (push) Waiting to run
Unit Testing ufw-docker / Unit Testing (push) Waiting to run
2025-07-08 17:18:51 +08:00
Chai Feng
05f55b76cc
Fix Docker Meta
Some checks are pending
Build Images / docker (amd64, map[name:legacy use_iptables_legacy:true]) (push) Waiting to run
Build Images / docker (amd64, map[name:nf_tables use_iptables_legacy:false]) (push) Waiting to run
Build Images / docker (arm64/v8, map[name:legacy use_iptables_legacy:true]) (push) Waiting to run
Build Images / docker (arm64/v8, map[name:nf_tables use_iptables_legacy:false]) (push) Waiting to run
Unit Testing ufw-docker / Unit Testing (push) Waiting to run
2025-07-08 16:53:57 +08:00
Chai Feng
f2d7e11800
Release version 250708 2025-07-08 16:21:31 +08:00
Chai Feng
b06afc13ef
Support multiple ports in ufw-docker service allow 2025-07-08 16:17:50 +08:00
Chai Feng
359d20d87c
Fix suffix, forgot to use a dash
Some checks failed
Build Images / docker (amd64, map[name:legacy use_iptables_legacy:true]) (push) Has been cancelled
Build Images / docker (amd64, map[name:nf_tables use_iptables_legacy:false]) (push) Has been cancelled
Build Images / docker (arm64/v8, map[name:legacy use_iptables_legacy:true]) (push) Has been cancelled
Build Images / docker (arm64/v8, map[name:nf_tables use_iptables_legacy:false]) (push) Has been cancelled
Unit Testing ufw-docker / Unit Testing (push) Has been cancelled
2025-07-04 09:31:39 +08:00
Chai Feng
60a1760505
Add matrix build for different archs 2025-07-04 09:25:47 +08:00
Chai Feng
7886c73f5b
Extract a function ufw-docker--list-service-ports 2025-07-03 10:27:47 +08:00
Chai Feng
9659e5d27d
Release version 250702-nf_tables
Some checks failed
Build Images / docker (push) Has been cancelled
Unit Testing ufw-docker / Unit Testing (push) Has been cancelled
2025-07-02 19:55:23 +08:00
Chai Feng
168fc59905
Add IPv6 network support and --docker-subnets option for install/check
- Add support for IPv6 networks in firewall rule generation and validation.
- Add --docker-subnets [SUBNET1 SUBNET2 ...] option to `ufw-docker install` and `ufw-docker check`.
- When used without arguments, automatically detects and applies all Docker network subnets.
- When given subnet arguments, applies firewall rules only to specified subnets (supports multiple subnets, including non-Docker-managed networks).
- If not specified, falls back to default RFC1918 IPv4 and fd00::/8 IPv6 subnets.
- Improve help output with detailed examples and usage guidance.
2025-07-02 18:54:07 +08:00
Chai Feng
e9a9f13095
Update README 2025-06-28 10:36:13 +08:00
Chai Feng
31380eb7ec
Merge branch 'master' into v6nat 2025-06-28 09:51:36 +08:00
Chai Feng
276324acf0
Update Dockerfile, add support for iptables legacy mode via a build arg 2025-06-28 09:19:14 +08:00
Chai Feng
1a51b59cf8
Update build docker image actions, fix username
Some checks failed
Build Images / docker (push) Has been cancelled
Unit Testing ufw-docker / Unit Testing (push) Has been cancelled
2025-01-13 11:31:05 +08:00
Chai Feng
5908cde296
Update github actions that build docker iamges 2025-01-13 11:24:08 +08:00
Chai Feng
8f9335326f
Build multi-arch docker images 2025-01-13 11:01:15 +08:00
Chai Feng
c9547cb4ec
Refactor Vagrantfile with getting docker version automatically
Some checks failed
Unit Testing ufw-docker / Unit Testing (push) Has been cancelled
2024-11-11 18:08:27 +08:00
Chai Feng
9474084f3f
Update Dockerfile with improved version matching 2024-11-11 18:07:06 +08:00
Chai Feng
1fa425bf17
Add node-internal 2024-11-08 10:25:22 +08:00
Chai Feng
3d6896cdd1
Update Bach to the latest unreleased version 2024-09-29 10:25:06 +08:00
Vladislav Fursov
a9fe32d1fa
Add IPv6 support 2023-03-20 02:54:43 +04:00
anuragpeshne
6cdd4dfd2f adds test for docker exist 2023-02-21 19:20:42 +08:00
anuragpeshne
17e6047590 Adds check for docker executable and adds snap to path 2023-02-21 19:20:42 +08:00
Chai Feng
a273ac9d51
221002-nf_tables 2022-10-02 17:03:51 +08:00
Chai Feng
cdad5e2a02
221002-legacy 2022-10-02 17:01:24 +08:00
Chai Feng
9d890ee3ee
Add integration tests related to PR #71 2022-09-26 22:00:05 +08:00
Radosław Kłos
a1d3517aeb Add integration tests for multiport app 2022-09-26 21:43:22 +08:00
Radosław Kłos
d1e6c13156 Add unit tests for alternative greps in ufw-docker--list 2022-09-26 21:43:22 +08:00
Radosław Kłos
682d8b363f Fix existing unit tests 2022-09-26 21:43:22 +08:00
Radosław Kłos
a689c4eb6e Fix (almost) always truthy regexp in ufw-docker--list 2022-09-26 21:43:22 +08:00
Chai Feng
e99858510d
Update Bach Testing Framework 2022-09-21 08:43:58 +08:00
Chai Feng
712b0e8075
Change to iptables (nf_tables), using Ubuntu 22.04 2022-09-20 21:51:39 +08:00
9 changed files with 1314 additions and 217 deletions

47
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: Build Images
on:
push:
jobs:
docker:
runs-on: ubuntu-latest
strategy:
matrix:
arch: [amd64, arm64/v8]
variant:
- name: nf_tables
use_iptables_legacy: "false"
- name: legacy
use_iptables_legacy: "true"
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
tags: |
type=ref,event=tag,suffix=-${{ matrix.variant.name }}
- name: Build and push
uses: docker/build-push-action@v6
with:
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/${{ matrix.arch }}
build-args: |
use_iptables_legacy=${{ matrix.variant.use_iptables_legacy }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View file

@ -1,6 +1,7 @@
FROM ubuntu:20.04 FROM ubuntu:24.04
ARG docker_version="20.10.17" ARG docker_version="28.3.1"
ARG use_iptables_legacy=false
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \ RUN apt-get update \
@ -12,9 +13,17 @@ RUN apt-get update \
| tee /etc/apt/sources.list.d/docker.list > /dev/null \ | 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=5:${docker_version}~*" || \ && 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=${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}/

View file

@ -193,6 +193,22 @@ 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
@ -220,6 +236,36 @@ 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.
@ -502,6 +548,19 @@ 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`。但是要小心避免把不该暴露的服务暴露出去。
@ -528,6 +587,33 @@ 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` 这个脚本来管理防火墙规则。
@ -595,6 +681,8 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
ufw-docker service delete allow web ufw-docker service delete allow web
ufw-docker service delete allow web 80/tcp
### 试试 ### 试试
我们使用 [Vagrant](https://www.vagrantup.com/) 来创建一个本地的测试环境。 我们使用 [Vagrant](https://www.vagrantup.com/) 来创建一个本地的测试环境。

202
Vagrantfile vendored
View file

@ -6,9 +6,15 @@
ENV['VAGRANT_NO_PARALLEL']="true" 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]
#config.vm.box = "chaifeng/ubuntu-22.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" docker_version = File.readlines("Dockerfile").filter { |line|
config.vm.box = "chaifeng/ubuntu-20.04-docker-#{(`uname -m`.strip == "arm64")?"19.03.13-arm64":"19.03.13"}" line.start_with?("ARG docker_version=")
}.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'
@ -21,14 +27,35 @@ Vagrant.configure('2') do |config|
end end
ip_prefix="192.168.56" ip_prefix="192.168.56"
ip6_prefix="fd00:a:b"
worker_count=1
def env_true?(env_name)
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 config.vm.provision 'docker-daemon-config', type: 'shell', inline: <<-SHELL
set -eu set -eu
if [[ ! -f /etc/docker/daemon.json ]]; then [[ -f /etc/profile.d/editor.sh ]] || echo 'export EDITOR=vim' > /etc/profile.d/editor.sh
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
@ -38,27 +65,30 @@ Vagrant.configure('2') do |config|
fi fi
SHELL SHELL
config.vm.provision 'ufw-docker', type: 'shell', inline: <<-SHELL config.vm.provision 'ufw-docker', preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -xeuo pipefail
export DEBUG=true export DEBUG=true
lsb_release -is | grep -Fi ubuntu lsb_release -is | grep -Fi ubuntu
/vagrant/ufw-docker check || {
declare -a subnets=(--docker-subnets 192.168.56.128/28 10.0.0.0/8 172.16.0.0/12)
#{env_true_str?('ENABLE_DOCKER_IPV6')} &&
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 OpenSSH
ufw allow from #{ip_prefix}.128/28 to any 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 yes | ufw enable || true
ufw status | grep '^Status: active' ufw status | grep '^Status: active'
/vagrant/ufw-docker install /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/
iptables -I DOCKER-USER 4 -p udp -j LOG --log-prefix '[UFW DOCKER] '
} }
[[ -L /usr/local/bin/ufw-docker ]] || ln -s /vagrant/ufw-docker /usr/local/bin/
fi
SHELL SHELL
private_registry="#{ip_prefix}.130:5000" private_registry="#{ip_prefix}.130:5000"
@ -67,9 +97,13 @@ Vagrant.configure('2') do |config|
master_ip_address = "#{ip_prefix}.130" 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: "#{master_ip_address}"
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', inline: <<-SHELL master.vm.provision "unit-testing", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL
set -euo pipefail set -euo pipefail
[[ -n "#{ENV['DISABLE_UNIT_TESTING']}" ]] ||
/vagrant/test.sh /vagrant/test.sh
SHELL SHELL
@ -84,18 +118,20 @@ 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", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -xeuo pipefail
docker build -t #{ufw_docker_agent_image}-legacy /vagrant suffix="$(iptables --version | grep -o '\\(nf_tables\\|legacy\\)')"
docker push #{ufw_docker_agent_image}-legacy if [[ "$suffix" = legacy ]]; then use_iptables_legacy=true; else use_iptables_legacy=false; fi
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}-nf_tables" > /etc/profile.d/ufw-docker.sh echo "export UFW_DOCKER_AGENT_IMAGE=#{ufw_docker_agent_image}-${suffix}" > /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', inline: <<-SHELL master.vm.provision "swarm-init", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL
set -euo pipefail set -euo pipefail
docker info | fgrep 'Swarm: active' && exit 0 docker info | fgrep 'Swarm: active' && exit 0
@ -103,11 +139,13 @@ Vagrant.configure('2') do |config|
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', inline: <<-SHELL master.vm.provision "build-webapp", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL
set -euo pipefail set -xeuo 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 "$@"'; \\
@ -120,7 +158,7 @@ DOCKERFILE
SHELL SHELL
master.vm.provision "local-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL master.vm.provision "local-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -xeuo 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#*:}"
@ -134,12 +172,16 @@ DOCKERFILE
SHELL SHELL
master.vm.provision "multiple-network", preserve_order: true, type: 'shell', inline: <<-SHELL master.vm.provision "multiple-network", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -xeuo pipefail
declare -a docker_opts=()
if ! docker network ls | grep -F foo-internal; then if ! docker network ls | grep -F foo-internal; then
docker network create --internal foo-internal ! #{env_true_str?('ENABLE_DOCKER_IPV6')} || docker_opts=(--ipv6 --subnet fd05:8f23:c937:1::/64)
docker network create --internal "${docker_opts[@]}" foo-internal
fi fi
if ! docker network ls | grep -F bar-external; then if ! docker network ls | grep -F bar-external; then
docker network create bar-external ! #{env_true_str?('ENABLE_DOCKER_IPV6')} || docker_opts=(--ipv6 --subnet fd05:8f23:c937:2::/64)
docker network create "${docker_opts[@]}" bar-external
fi fi
for app in internal-multinet-app:7000 public-multinet-app:17070; do for app in internal-multinet-app:7000 public-multinet-app:17070; do
@ -157,25 +199,42 @@ DOCKERFILE
SHELL SHELL
master.vm.provision "swarm-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL master.vm.provision "swarm-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail 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 service create --name "$webapp" "${docker_opts[@]}" \
--publish "${port}:80" --env name="$webapp" --replicas 3 #{private_registry}/chaifeng/hostname-webapp --publish "${port}:80" --env name="$webapp" --replicas #{worker_count} #{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 2 do |ip| 1.upto worker_count 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 "swarm-join", preserve_order: true, type: 'shell', inline: <<-SHELL node.vm.provision "node#{ip}-swarm-join", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
docker info | fgrep 'Swarm: active' && exit 0 docker info | fgrep 'Swarm: active' && exit 0
@ -185,27 +244,92 @@ 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', inline: <<-SHELL external.vm.provision "testing", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL
set -euo pipefail set -xuo pipefail
set -x error_count=0
server="http://#{ip_prefix}.130" function test-webapp() {
function test-webapp() { timeout 3 curl --silent "$@"; } local actual=""
if [[ "$#" -eq 2 ]]; then
local expect_fail='!'
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:18080"
! test-webapp "$server:8000" test-webapp ! "$server:8000"
test-webapp "$server:17070" # multiple networks app test-webapp "$server:17070" # multiple networks app
! test-webapp "$server:7000" # internal 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:29090"
! test-webapp "$server:9000"
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 "====================="
echo " TEST DONE " if [[ "$error_count" -eq 0 ]]; then echo " TEST DONE "
else echo " TESTS FAIL: ${error_count}"
fi
echo "=====================" echo "====================="
exit "${error_count}"
} 2>/dev/null
SHELL SHELL
end end
end end

View file

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
[[ -n "${DEBUG:-}" ]] && set -x [[ "${DEBUG:-}" = true ]] && set -x
[[ 0 -eq "$#" ]] && set -- start [[ 0 -eq "$#" ]] && set -- start
ufw_docker_agent=ufw-docker-agent ufw_docker_agent=ufw-docker-agent
@ -10,8 +10,12 @@ function ufw-allow-or-deny-service() {
declare id="$1" declare id="$1"
declare port="$2" declare port="$2"
if [[ "$port" = deny ]]; then if [[ "$port" = deny || "$port" = */deny ]]; then
run-ufw-docker delete allow "$id" port="${port%deny}"
port="${port%/}"
declare -a opts=("$id")
[[ -z "$port" ]] || opts+=("$port")
run-ufw-docker delete allow "${opts[@]}"
else else
run-ufw-docker add-service-rule "$id" "$port" run-ufw-docker add-service-rule "$id" "$port"
fi fi
@ -22,8 +26,14 @@ function update-ufw-rules() {
-e 's/^declare -x ufw_public_//' \ -e 's/^declare -x ufw_public_//' \
-e 's/="/ /' \ -e 's/="/ /' \
-e 's/"$//' | -e 's/"$//' |
while read -r id port; do while read -r id ruleset; do
ufw-allow-or-deny-service "${id}" "${port#*/}" declare -a rules=( $(tr ',' '\n' <<< "$ruleset") )
for rule in "${rules[@]}"; do
[[ "$rule" = */deny ]] && ufw-allow-or-deny-service "${id}" "${rule#*/}"
done
for rule in "${rules[@]}"; do
[[ "$rule" = */deny ]] || ufw-allow-or-deny-service "${id}" "${rule#*/}"
done
done done
} }

@ -1 +1 @@
Subproject commit 447edb60db232d3dbc2267f37c49bd7a070cc83d Subproject commit 871528a805b21e3c4432e4c23cacc3c4f4ddfc39

View file

@ -6,9 +6,8 @@ source "$working_dir"/bach/bach.sh
@setup { @setup {
set -euo pipefail set -euo pipefail
ufw_docker_agent=ufw-docker-agent ufw_docker_agent=ufw-docker-agent
ufw_docker_agent_image=chaifeng/ufw-docker-agent:181005 ufw_docker_agent_image=chaifeng/ufw-docker-agent:090502
} }
@setup-test { @setup-test {
@ -19,9 +18,17 @@ source "$working_dir"/bach/bach.sh
@ignore echo @ignore echo
@ignore err @ignore err
DEFAULT_PROTO=tcp UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:090502
GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" builtin source <(@sed -n -e '/^# UFW-DOCKER GLOBAL VARIABLES START #$/,/^# UFW-DOCKER GLOBAL VARIABLES END #$/{' -e '/^PATH=/d' -e 'p' -e '}' "$working_dir/../ufw-docker")
DEBUG=false DEBUG=false
unset RANDOM
RANDOM=42
@allow-real sed -e '/^ufw_public_/!d' -e 's/^ufw_public_//' -e 's/=/ /'
@allow-real tr ',' '\n'
@allow-real grep -E '^[0-9]+(/(tcp|udp))?$'
} }
function die() { function die() {
@ -110,6 +117,26 @@ test-ufw-docker--service-delete-allow-webapp-assert() {
} }
test-ufw-docker--service-delete-allow-webapp-8080-tcp() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service delete allow webapp 8080/tcp
}
test-ufw-docker--service-delete-allow-webapp-8080-tcp-assert() {
ufw-docker--service-delete webapp 8080/tcp
}
test-ufw-docker--service-delete-allow-webapp-8080() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service delete allow webapp 8080
}
test-ufw-docker--service-delete-allow-webapp-8080-assert() {
ufw-docker--service-delete webapp 8080
}
test-ufw-docker--get-service-id() { test-ufw-docker--get-service-id() {
load-ufw-docker-function ufw-docker--get-service-id load-ufw-docker-function ufw-docker--get-service-id
ufw-docker--get-service-id database ufw-docker--get-service-id database
@ -129,8 +156,6 @@ test-ufw-docker--get-service-name-assert() {
test-ufw-docker--service-allow-invalid-port-syntax() { test-ufw-docker--service-allow-invalid-port-syntax() {
@mockfalse grep -E '^[0-9]+(/(tcp|udp))?$'
load-ufw-docker-function ufw-docker--service-allow load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow webapp invalid-port ufw-docker--service-allow webapp invalid-port
} }
@ -141,7 +166,6 @@ test-ufw-docker--service-allow-invalid-port-syntax-assert() {
test-ufw-docker--service-allow-an-non-existed-service() { test-ufw-docker--service-allow-an-non-existed-service() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id web404 === @stdout "" @mock ufw-docker--get-service-id web404 === @stdout ""
load-ufw-docker-function ufw-docker--service-allow load-ufw-docker-function ufw-docker--service-allow
@ -154,11 +178,9 @@ test-ufw-docker--service-allow-an-non-existed-service-assert() {
test-ufw-docker--service-allow-a-service-without-ports-published() { test-ufw-docker--service-allow-a-service-without-ports-published() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id private-web === @stdout abcd1234 @mock ufw-docker--get-service-id private-web === @stdout abcd1234
@mock ufw-docker--get-service-name private-web === @stdout private-web @mock ufw-docker--get-service-name private-web === @stdout private-web
@mock docker service inspect private-web \ @mock ufw-docker--list-service-ports private-web === @stdout ""
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}' === @stdout ""
load-ufw-docker-function ufw-docker--service-allow load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow private-web 80/tcp ufw-docker--service-allow private-web 80/tcp
@ -170,12 +192,9 @@ test-ufw-docker--service-allow-a-service-without-ports-published-assert() {
test-ufw-docker--service-allow-a-service-while-agent-not-running() { test-ufw-docker--service-allow-a-service-while-agent-not-running() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id webapp === @stdout abcd1234 @mock ufw-docker--get-service-id webapp === @stdout abcd1234
@mock ufw-docker--get-service-name webapp === @stdout webapp @mock ufw-docker--get-service-name webapp === @stdout webapp
@mock docker service inspect webapp \ @mock ufw-docker--list-service-ports webapp === @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}' \
=== @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
@mockfalse docker service inspect ufw-docker-agent @mockfalse docker service inspect ufw-docker-agent
load-ufw-docker-function ufw-docker--service-allow load-ufw-docker-function ufw-docker--service-allow
@ -185,20 +204,17 @@ test-ufw-docker--service-allow-a-service-while-agent-not-running-assert() {
docker service create --name ufw-docker-agent --mode global \ docker service create --name ufw-docker-agent --mode global \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \ --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/ufw,target=/etc/ufw,readonly=true \ --mount type=bind,source=/etc/ufw,target=/etc/ufw,readonly=true \
--env ufw_docker_agent_image="chaifeng/ufw-docker-agent:181005" \ --env ufw_docker_agent_image="chaifeng/ufw-docker-agent:090502" \
--env DEBUG="false" \ --env DEBUG="false" \
--env "ufw_public_abcd1234=webapp/80/tcp" \ --env "ufw_public_abcd1234=webapp/80/tcp" \
"chaifeng/ufw-docker-agent:181005" "chaifeng/ufw-docker-agent:090502"
} }
test-ufw-docker--service-allow-a-service-add-new-env() { test-ufw-docker--service-allow-a-service-add-new-env() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id webapp === @stdout abcd1234 @mock ufw-docker--get-service-id webapp === @stdout abcd1234
@mock ufw-docker--get-service-name webapp === @stdout webapp @mock ufw-docker--get-service-name webapp === @stdout webapp
@mock docker service inspect webapp \ @mock ufw-docker--list-service-ports webapp === @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}' \
=== @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
@mocktrue docker service inspect ufw-docker-agent @mocktrue docker service inspect ufw-docker-agent
@mock ufw-docker--get-env-list === @stdout "abcd1234 webapp/80/tcp" @mock ufw-docker--get-env-list === @stdout "abcd1234 webapp/80/tcp"
@ -207,21 +223,18 @@ test-ufw-docker--service-allow-a-service-add-new-env() {
} }
test-ufw-docker--service-allow-a-service-add-new-env-assert() { test-ufw-docker--service-allow-a-service-add-new-env-assert() {
docker service update --update-parallelism=0 \ docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:181005" \ --env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:090502" \
--env-add DEBUG="false" \ --env-add DEBUG="false" \
--env-add "ufw_public_abcd1234=webapp/80/tcp" \ --env-add "ufw_public_abcd1234=webapp/80/tcp" \
--image "chaifeng/ufw-docker-agent:181005" \ --image "chaifeng/ufw-docker-agent:090502" \
ufw-docker-agent ufw-docker-agent
} }
test-ufw-docker--service-allow-a-service-update-a-env() { test-ufw-docker--service-allow-a-service-update-a-env() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id webapp === @stdout abcd1234 @mock ufw-docker--get-service-id webapp === @stdout abcd1234
@mock ufw-docker--get-service-name webapp === @stdout webapp @mock ufw-docker--get-service-name webapp === @stdout webapp
@mock docker service inspect webapp \ @mock ufw-docker--list-service-ports webapp === @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}' \
=== @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
@mocktrue docker service inspect ufw-docker-agent @mocktrue docker service inspect ufw-docker-agent
@mock ufw-docker--get-env-list === @stdout "a_different_id webapp/80/tcp" @mock ufw-docker--get-env-list === @stdout "a_different_id webapp/80/tcp"
@ -230,11 +243,60 @@ test-ufw-docker--service-allow-a-service-update-a-env() {
} }
test-ufw-docker--service-allow-a-service-update-a-env-assert() { test-ufw-docker--service-allow-a-service-update-a-env-assert() {
docker service update --update-parallelism=0 \ docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:181005" \ --env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:090502" \
--env-add DEBUG="false" \ --env-add DEBUG="false" \
--env-add "ufw_public_abcd1234=webapp/80/tcp" \
--env-rm "ufw_public_a_different_id" \ --env-rm "ufw_public_a_different_id" \
--image "chaifeng/ufw-docker-agent:181005" \ --env-add "ufw_public_abcd1234=webapp/80/tcp" \
--image "chaifeng/ufw-docker-agent:090502" \
ufw-docker-agent
}
test-ufw-docker--service-allow-a-service-add-value-to-an-env() {
@mock ufw-docker--get-service-id webapp === @stdout abcd1234
@mock ufw-docker--get-service-name webapp === @stdout webapp
@mock ufw-docker--list-service-ports webapp === @stdout "5353 53/udp" "8080 80/tcp" "18080 8080/tcp"
@mocktrue docker service inspect ufw-docker-agent
@mock ufw-docker--get-env-list === @stdout "a_different_id webapp/8080/tcp" "abcd1234 webapp/5353/udp"
load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow webapp 80/tcp
ufw-docker--service-allow webapp 8080/tcp
}
test-ufw-docker--service-allow-a-service-add-value-to-an-env-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:090502" \
--env-add DEBUG="false" \
--env-rm "ufw_public_a_different_id" \
--env-add "ufw_public_abcd1234=webapp/8080/tcp,webapp/5353/udp" \
--image "chaifeng/ufw-docker-agent:090502" \
ufw-docker-agent
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:090502" \
--env-add DEBUG="false" \
--env-rm "ufw_public_a_different_id" \
--env-add "ufw_public_abcd1234=webapp/18080/tcp,webapp/5353/udp" \
--image "chaifeng/ufw-docker-agent:090502" \
ufw-docker-agent
}
test-ufw-docker--service-allow-a-service-denied-port() {
@mock ufw-docker--get-service-id webapp === @stdout abcd1234
@mock ufw-docker--get-service-name webapp === @stdout webapp
@mock ufw-docker--list-service-ports webapp === @stdout "5353 53/udp" "8080 80/tcp" "18080 8080/tcp"
@mocktrue docker service inspect ufw-docker-agent
@mock ufw-docker--get-env-list === @stdout "a_different_id webapp/8080/tcp" "abcd1234 webapp/8080/tcp/deny" "abcd1234 webapp/5353/udp"
load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow webapp 80/tcp
}
test-ufw-docker--service-allow-a-service-denied-port-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:090502" \
--env-add DEBUG="false" \
--env-rm "ufw_public_a_different_id" \
--env-add "ufw_public_abcd1234=webapp/8080/tcp,webapp/5353/udp" \
--image "chaifeng/ufw-docker-agent:090502" \
ufw-docker-agent ufw-docker-agent
} }
@ -245,25 +307,48 @@ test-ufw-docker--get-env-list() {
=== @stdout \ === @stdout \
"ufw_docker_agent_image=192.168.56.130:5000/chaifeng/ufw-docker-agent:test" \ "ufw_docker_agent_image=192.168.56.130:5000/chaifeng/ufw-docker-agent:test" \
"DEBUG=true" \ "DEBUG=true" \
"ufw_public_zv6esvmwnmmgnlauqn7m77jo4=webapp/9090/tcp" \ "ufw_public_id111111=webapp/9090/tcp" \
"ufw_public_id222222=foo/2222/udp" \
"OTHER_ENV=blabla" "OTHER_ENV=blabla"
@mock sed -e '/^ufw_public_/!d' \ @allow-real sed "s/^/id111111 /g"
-e 's/^ufw_public_//' \ @allow-real sed "s/^/id222222 /g"
-e 's/=/ /' === @real sed -e '/^ufw_public_/!d' \
-e 's/^ufw_public_//' \
-e 's/=/ /'
load-ufw-docker-function ufw-docker--get-env-list load-ufw-docker-function ufw-docker--get-env-list
ufw-docker--get-env-list ufw-docker--get-env-list
} }
test-ufw-docker--get-env-list-assert() { test-ufw-docker--get-env-list-assert() {
@stdout "zv6esvmwnmmgnlauqn7m77jo4 webapp/9090/tcp" @stdout "id111111 webapp/9090/tcp"
@stdout "id222222 foo/2222/udp"
}
test-ufw-docker--get-env-list-multiple() {
@mock docker service inspect ufw-docker-agent \
--format '{{range $k,$v := .Spec.TaskTemplate.ContainerSpec.Env}}{{ $v }}{{"\n"}}{{end}}' \
=== @stdout \
"ufw_docker_agent_image=192.168.56.130:5000/chaifeng/ufw-docker-agent:test" \
"DEBUG=true" \
"ufw_public_id111111=webapp/9090/tcp,webapp/8888/tcp,webapp/5555/udp" \
"ufw_public_id222222=foo/2222/udp,foo/3333/tcp" \
"OTHER_ENV=blabla"
@allow-real sed "s/^/id111111 /g"
@allow-real sed "s/^/id222222 /g"
load-ufw-docker-function ufw-docker--get-env-list
ufw-docker--get-env-list
}
test-ufw-docker--get-env-list-multiple-assert() {
@stdout "id111111 webapp/9090/tcp"
@stdout "id111111 webapp/8888/tcp"
@stdout "id111111 webapp/5555/udp"
@stdout "id222222 foo/2222/udp"
@stdout "id222222 foo/3333/tcp"
} }
test-ufw-docker--service-delete-no-matches() { test-ufw-docker--service-delete-no-matches() {
@mock ufw-docker--get-env-list === @stdout "ffff111 foo/80/tcp" "eeee2222 bar/53/udp" @mockfalse ufw-docker--get-service-id webapp
load-ufw-docker-function ufw-docker--service-delete load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp ufw-docker--service-delete webapp
@ -273,9 +358,15 @@ test-ufw-docker--service-delete-no-matches-assert() {
@fail @fail
} }
function mock-abcd1234-webapp() {
@mock ufw-docker--get-service-name webapp === @stdout webapp
@mock ufw-docker--get-service-id webapp === @stdout "abcd1234"
@mock ufw-docker--list-service-ports webapp === @stdout "22 2222/tcp" "80 8080/tcp" "53 5353/udp"
}
test-ufw-docker--service-delete-matches() { test-ufw-docker--service-delete-matches() {
@mock ufw-docker--get-env-list === @stdout "ffff111 foo/80/tcp" "eeee2222 bar/53/udp" "abcd1234 webapp/5000/tcp" mock-abcd1234-webapp
@mock ufw-docker--get-env-list === @stdout "xxx 888/tcp" "abcd1234 webapp/22/tcp"
load-ufw-docker-function ufw-docker--service-delete load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp ufw-docker--service-delete webapp
@ -284,6 +375,188 @@ 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}"
} }
test-ufw-docker--service-delete-matches2() {
mock-abcd1234-webapp
@mock ufw-docker--get-env-list === @stdout "xxx 888/tcp" "abcd1234 webapp/22/tcp" "abcd1234 webapp/53/udp" "abcd1234 webapp/80/tcp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp
}
test-ufw-docker--service-delete-matches2-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
--env-add "ufw_public_abcd1234=webapp/deny" \
--env-add "DEBUG=false" \
--image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}"
}
test-ufw-docker--service-delete-matches-with-a-port() {
mock-abcd1234-webapp
@mock ufw-docker--get-env-list === @stdout "xxx 888/tcp" "abcd1234 webapp/80/tcp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp 8080
}
test-ufw-docker--service-delete-matches-with-a-port-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
--env-add "ufw_public_abcd1234=webapp/80/tcp/deny" \
--env-add "DEBUG=false" \
--image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}"
}
test-ufw-docker--service-delete-matches-with-a-port2() {
mock-abcd1234-webapp
@mock ufw-docker--get-env-list === @stdout "xxx 888/tcp" "abcd1234 webapp/80/tcp" "abcd1234 webapp/53/udp" "abcd1234 webapp/53/tcp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp 8080
}
test-ufw-docker--service-delete-matches-with-a-port2-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
--env-add "ufw_public_abcd1234=webapp/80/tcp/deny,webapp/53/udp,webapp/53/tcp" \
--env-add "DEBUG=false" \
--image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}"
}
test-ufw-docker--service-delete-matches-with-a-port-but-no-previous-rule() {
mock-abcd1234-webapp
@mock ufw-docker--get-env-list === @stdout "xxx 888/tcp" "abcd1234 webapp/53/tcp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp 2222
}
test-ufw-docker--service-delete-matches-with-a-port-but-no-previous-rule-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
--env-add "ufw_public_abcd1234=webapp/22/tcp/deny,webapp/53/tcp" \
--env-add "DEBUG=false" \
--image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}"
}
test-ufw-docker--service-delete-matches-with-a-port-proto-pair() {
mock-abcd1234-webapp
@mock ufw-docker--get-env-list === @stdout "xxx 888/tcp" "abcd1234 webapp/80/tcp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp 8080/tcp
}
test-ufw-docker--service-delete-matches-with-a-port-proto-pair-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
--env-add "ufw_public_abcd1234=webapp/80/tcp/deny" \
--env-add "DEBUG=false" \
--image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}"
}
test-ufw-docker--service-delete-matches-with-a-port-proto-pair2() {
mock-abcd1234-webapp
@mock ufw-docker--get-env-list === @stdout "xxx 888/tcp" "abcd1234 webapp/80/tcp" "abcd1234 webapp/53/udp" "abcd1234 webapp/53/tcp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp 5353/udp
}
test-ufw-docker--service-delete-matches-with-a-port-proto-pair2-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
--env-add "ufw_public_abcd1234=webapp/53/udp/deny,webapp/80/tcp,webapp/53/tcp" \
--env-add "DEBUG=false" \
--image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}"
}
test-ufw-docker--service-delete-matches-with-a-not-matched-port() {
mock-abcd1234-webapp
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp 3333
}
test-ufw-docker--service-delete-matches-with-a-not-matched-port-assert() {
@do-nothing
@fail
}
test-ufw-docker--service-delete-matches-with-a-not-matched-protocal() {
@mock ufw-docker--get-service-id webapp === @stdout "abcd1234"
@mock ufw-docker--get-service-name webapp === @stdout webapp
@mock ufw-docker--list-service-ports webapp === @stdout "22 2222/tcp" "80 8080/tcp" "53 5353/udp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp 8080/udp
}
test-ufw-docker--service-delete-matches-with-a-not-matched-protocal-assert() {
@do-nothing
@fail
}
test-ufw-docker--list-service-ports() {
load-ufw-docker-function ufw-docker--list-service-ports
ufw-docker--list-service-ports foo
}
test-ufw-docker--list-service-ports-assert() {
docker service inspect foo --format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}'
}
function setup-mock-for-testing-docker-entrypoint() {
@mock date '+%Y%m%d%H%M%S' === @stdout 200902140731
declare -gx ufw_public_id111111=alpha/80/tcp
declare -gx ufw_public_id222222=beta/deny
declare -gx ufw_public_id333333=gamma/8080/tcp/deny,gamma/5353/udp
@allow-real sed -e '/^declare -x ufw_public_/!d' -e 's/^declare -x ufw_public_//' -e 's/="/ /' -e 's/"$//'
@allow-real tr ',' '\n'
}
test-dockerentrypoint() {
setup-mock-for-testing-docker-entrypoint
declare -x ufw_public_id333333=gamma/8080/tcp/deny,gamma/5353/udp
@run "$working_dir"/../docker-entrypoint.sh update-ufw-rules
}
test-dockerentrypoint-assert() {
declare -a docker_opts=(run --rm -t --name ufw-docker-agent-42-200902140731
--cap-add NET_ADMIN --network host --env DEBUG=false
-v /var/run/docker.sock:/var/run/docker.sock
-v /etc/ufw:/etc/ufw
chaifeng/ufw-docker-agent:090502
)
docker "${docker_opts[@]}" add-service-rule id111111 80/tcp
docker "${docker_opts[@]}" delete allow id222222
docker "${docker_opts[@]}" delete allow id333333 8080/tcp
docker "${docker_opts[@]}" add-service-rule id333333 5353/udp
}
test-dockerentrypoint-deny-first() {
setup-mock-for-testing-docker-entrypoint
declare -x ufw_public_id333333=gamma/5353/udp,gamma/8080/tcp/deny
@run "$working_dir"/../docker-entrypoint.sh update-ufw-rules
}
test-dockerentrypoint-deny-first-assert() {
declare -a docker_opts=(run --rm -t --name ufw-docker-agent-42-200902140731
--cap-add NET_ADMIN --network host --env DEBUG=false
-v /var/run/docker.sock:/var/run/docker.sock
-v /etc/ufw:/etc/ufw
chaifeng/ufw-docker-agent:090502
)
docker "${docker_opts[@]}" add-service-rule id111111 80/tcp
docker "${docker_opts[@]}" delete allow id222222
docker "${docker_opts[@]}" delete allow id333333 8080/tcp
docker "${docker_opts[@]}" add-service-rule id333333 5353/udp
}

View file

@ -15,13 +15,14 @@ source "$working_dir"/bach/bach.sh
@mock iptables --version @mock iptables --version
@mocktrue grep -F '(legacy)' @mocktrue grep -F '(legacy)'
@ignore remove_blank_lines @mocktrue docker -v
@ignore echo @mock docker -v === @stdout Docker version 0.0.0, build dummy
@mockpipe remove_blank_lines
#@ignore echo
@ignore err @ignore err
DEFAULT_PROTO=tcp builtin source <(@sed -n -e '/^# UFW-DOCKER GLOBAL VARIABLES START #$/,/^# UFW-DOCKER GLOBAL VARIABLES END #$/{' -e '/^PATH=/d' -e 'p' -e '}' "$working_dir/../ufw-docker")
GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+"
UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:090502-legacy UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:090502-legacy
} }
@ -31,7 +32,6 @@ function ufw-docker() {
function load-ufw-docker-function() { function load-ufw-docker-function() {
set -euo pipefail set -euo pipefail
@load_function "$working_dir/../ufw-docker" "$1" @load_function "$working_dir/../ufw-docker" "$1"
} }
@ -98,6 +98,17 @@ test-ufw-is-disabled-assert() {
} }
test-docker-is-installed() {
@mockfalse docker -v
ufw-docker
}
test-docker-is-installed-assert() {
die "Docker executable not found."
ufw-docker--help
}
test-ufw-docker-status() { test-ufw-docker-status() {
ufw-docker status ufw-docker status
} }
@ -114,6 +125,14 @@ 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
} }
@ -122,6 +141,14 @@ 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
} }
@ -232,15 +259,6 @@ 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
@ -263,8 +281,15 @@ 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 .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' instance-name === @stdout 172.18.0.3 @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 $k, $v := .NetworkSettings.Networks}}{{printf "%s\n" $k}}{{end}}' instance-name === @stdout default @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 @mock docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout 5000/tcp 8080/tcp 5353/udp
} }
@ -272,8 +297,15 @@ function setup-ufw-docker--allow--multinetwork() {
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 .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' instance-name === @stdout 172.18.0.3 172.19.0.7 @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 $k, $v := .NetworkSettings.Networks}}{{printf "%s\n" $k}}{{end}}' instance-name === @stdout default awesomenet @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
} }
@ -373,8 +405,86 @@ test-ufw-docker--allow-instance-all-published-port-multinetwork-select-network-a
ufw-docker--add-rule instance-name 172.19.0.7 5353 udp 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 ""
@ignore echo
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
@ -385,6 +495,7 @@ test-ufw-docker--add-rule-a-non-existing-rule-assert() {
test-ufw-docker--add-rule-a-non-existing-rule-with-network() { test-ufw-docker--add-rule-a-non-existing-rule-with-network() {
@mockfalse ufw-docker--list webapp 5000 tcp default @mockfalse ufw-docker--list webapp 5000 tcp default
@ignore echo
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 default
@ -396,8 +507,9 @@ test-ufw-docker--add-rule-a-non-existing-rule-with-network-assert() {
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 default
@mocktrue ufw --dry-run route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default" @mock ufw --dry-run route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default" === @echo
@mockfalse grep "^Skipping" @mockfalse grep "^Skipping"
@ignore echo
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 default
@ -409,10 +521,27 @@ test-ufw-docker--add-rule-modify-an-existing-rule-assert() {
} }
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"
@ignore echo
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"
@ignore echo
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 ""
@ -424,9 +553,9 @@ 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"
@ignore echo
load-ufw-docker-function ufw-docker--add-rule load-ufw-docker-function ufw-docker--add-rule
@ -442,7 +571,7 @@ test-ufw-docker--add-rule-modify-an-existing-rule-without-port-assert() {
test-ufw-docker--instance-name-found-a-name() { test-ufw-docker--instance-name-found-a-name() {
@mock docker inspect --format="{{.Name}}" foo @mock docker inspect --format="{{.Name}}" foo
@mock sed -e 's,^/,,' @mock sed -e 's,^/,,'
@mockfalse grep "^$GREP_REGEXP_INSTANCE_NAME\$" @mockfalse grep "^$GREP_REGEXP_NAME\$"
@mock echo -n foo @mock echo -n foo
@ -451,59 +580,158 @@ 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
echo -n foo @dryrun echo -n foo
} }
test-ufw-docker--instance-name-found-an-id() { test-ufw-docker--instance-name-found-an-id() {
@mock docker inspect --format="{{.Name}}" fooid @mock docker inspect --format="{{.Name}}" fooid
@mock sed -e 's,^/,,' @mock sed -e 's,^/,,'
@mockfalse grep "^$GREP_REGEXP_INSTANCE_NAME\$" @mockfalse grep "^$GREP_REGEXP_NAME\$"
@mock echo -n fooid
load-ufw-docker-function ufw-docker--instance-name load-ufw-docker-function ufw-docker--instance-name
ufw-docker--instance-name fooid ufw-docker--instance-name fooid
} }
test-ufw-docker--instance-name-found-an-id-assert() { test-ufw-docker--instance-name-found-an-id-assert() {
docker inspect --format="{{.Name}}" fooid docker inspect --format="{{.Name}}" fooid
@dryrun echo -n fooid
} }
function mock-ufw-status-numbered-foo() {
@mock ufw status numbered === @echo "Status: active
To Action From
-- ------ ----
[ 1] OpenSSH ALLOW IN Anywhere
[ 2] Anywhere ALLOW IN 192.168.56.128/28
[ 3] 172.17.0.3 80/tcp ALLOW FWD Anywhere # allow foo 80/tcp bridge
[ 4] 172.20.0.3 80/tcp ALLOW FWD Anywhere # allow bar 80/tcp bar-external
[ 5] 172.17.0.3 53/udp ALLOW FWD Anywhere # allow foo 53/udp foo-internal
[ 6] 172.17.0.3 53/tcp ALLOW FWD Anywhere # allow foo 53/tcp
[ 7] 172.18.0.2 29090/tcp ALLOW FWD Anywhere # allow id111111 29090/tcp
[ 8] 172.18.0.2 48080/tcp ALLOW FWD Anywhere # allow id222222 48080/tcp
[ 9] 172.18.0.2 40080/tcp ALLOW FWD Anywhere # allow id333333 40080/tcp
[10] OpenSSH (v6) ALLOW IN Anywhere (v6)
[11] Anywhere (v6) ALLOW IN fd00:a:b:0:cafe::/80
[12] fd00:a:b:deaf::3 80/tcp ALLOW FWD Anywhere (v6) # allow foo/v6 80/tcp bridge
[13] fd05:8f23:c937:2::3 80/tcp ALLOW FWD Anywhere (v6) # allow bar/v6 80/tcp bar-external
[14] fd00:a:b:deaf::3 53/udp ALLOW FWD Anywhere (v6) # allow foo/v6 53/udp foo-internal
[15] fd00:a:b:deaf::3 53/tcp ALLOW FWD Anywhere (v6) # allow foo/v6 53/tcp
"
}
test-ufw-docker--status() {
mock-ufw-status-numbered-foo
@allow-real grep '# allow [-_.[:alnum:]]\+\(/v6\)\?\( [[:digit:]]\+/\(tcp\|udp\)\( [-_.[:alnum:]]\+\)\?\)\?$'
load-ufw-docker-function ufw-docker--list
load-ufw-docker-function ufw-docker--status
ufw-docker--status
}
test-ufw-docker--status-assert() {
test-ufw-docker--list-all-assert
}
test-ufw-docker--list-all() {
mock-ufw-status-numbered-foo
@allow-real grep '# allow [-_.[:alnum:]]\+\(/v6\)\?\( [[:digit:]]\+/\(tcp\|udp\)\( [-_.[:alnum:]]\+\)\?\)\?$'
load-ufw-docker-function ufw-docker--list
ufw-docker--list
}
test-ufw-docker--list-all-assert() {
@stdout "[ 3] 172.17.0.3 80/tcp ALLOW FWD Anywhere # allow foo 80/tcp bridge"
@stdout "[ 4] 172.20.0.3 80/tcp ALLOW FWD Anywhere # allow bar 80/tcp bar-external"
@stdout "[ 5] 172.17.0.3 53/udp ALLOW FWD Anywhere # allow foo 53/udp foo-internal"
@stdout "[ 6] 172.17.0.3 53/tcp ALLOW FWD Anywhere # allow foo 53/tcp"
@stdout "[ 7] 172.18.0.2 29090/tcp ALLOW FWD Anywhere # allow id111111 29090/tcp"
@stdout "[ 8] 172.18.0.2 48080/tcp ALLOW FWD Anywhere # allow id222222 48080/tcp"
@stdout "[ 9] 172.18.0.2 40080/tcp ALLOW FWD Anywhere # allow id333333 40080/tcp"
@stdout "[12] fd00:a:b:deaf::3 80/tcp ALLOW FWD Anywhere (v6) # allow foo/v6 80/tcp bridge"
@stdout "[13] fd05:8f23:c937:2::3 80/tcp ALLOW FWD Anywhere (v6) # allow bar/v6 80/tcp bar-external"
@stdout "[14] fd00:a:b:deaf::3 53/udp ALLOW FWD Anywhere (v6) # allow foo/v6 53/udp foo-internal"
@stdout "[15] fd00:a:b:deaf::3 53/tcp ALLOW FWD Anywhere (v6) # allow foo/v6 53/tcp"
}
test-ufw-docker--list-name() { test-ufw-docker--list-name() {
@mocktrue ufw status numbered mock-ufw-status-numbered-foo
@allow-real grep '# allow foo\(/v6\)\?\( [[:digit:]]\+/\(tcp\|udp\)\( [-_.[:alnum:]]\+\)\?\)\?$'
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\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\\( [[:graph:]]*\\)\\?\$" @stdout "[ 3] 172.17.0.3 80/tcp ALLOW FWD Anywhere # allow foo 80/tcp bridge"
@stdout "[ 5] 172.17.0.3 53/udp ALLOW FWD Anywhere # allow foo 53/udp foo-internal"
@stdout "[ 6] 172.17.0.3 53/tcp ALLOW FWD Anywhere # allow foo 53/tcp"
@stdout "[12] fd00:a:b:deaf::3 80/tcp ALLOW FWD Anywhere (v6) # allow foo/v6 80/tcp bridge"
@stdout "[14] fd00:a:b:deaf::3 53/udp ALLOW FWD Anywhere (v6) # allow foo/v6 53/udp foo-internal"
@stdout "[15] fd00:a:b:deaf::3 53/tcp ALLOW FWD Anywhere (v6) # allow foo/v6 53/tcp"
} }
test-ufw-docker--list-name-udp() { test-ufw-docker--list-name-udp() {
@mocktrue ufw status numbered mock-ufw-status-numbered-foo
@allow-real grep '# allow foo\(/v6\)\? [[:digit:]]\+/udp\( [-_.[:alnum:]]\+\)\?$'
load-ufw-docker-function ufw-docker--list load-ufw-docker-function ufw-docker--list
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:]]*\\)\\?\$" @stdout "[ 5] 172.17.0.3 53/udp ALLOW FWD Anywhere # allow foo 53/udp foo-internal"
@stdout "[14] fd00:a:b:deaf::3 53/udp ALLOW FWD Anywhere (v6) # allow foo/v6 53/udp foo-internal"
} }
test-ufw-docker--list-name-80() { test-ufw-docker--list-name-80-_-bridge() {
@mocktrue ufw status numbered mock-ufw-status-numbered-foo
@allow-real grep '# allow foo\(/v6\)\? 80/tcp bridge$'
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 80 "" bridge
}
test-ufw-docker--list-name-80-_-bridge-assert() {
@stdout "[ 3] 172.17.0.3 80/tcp ALLOW FWD Anywhere # allow foo 80/tcp bridge"
@stdout "[12] fd00:a:b:deaf::3 80/tcp ALLOW FWD Anywhere (v6) # allow foo/v6 80/tcp bridge"
}
test-ufw-docker--list-name-53-udp() {
mock-ufw-status-numbered-foo
@allow-real grep '# allow foo\(/v6\)\? 53/udp\( [-_.[:alnum:]]\+\)\?$'
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 53 udp
}
test-ufw-docker--list-name-53-udp-assert() {
@stdout "[ 5] 172.17.0.3 53/udp ALLOW FWD Anywhere # allow foo 53/udp foo-internal"
@stdout "[14] fd00:a:b:deaf::3 53/udp ALLOW FWD Anywhere (v6) # allow foo/v6 53/udp foo-internal"
}
test-ufw-docker--list-grep-with-incorrect-network() {
mock-ufw-status-numbered-foo
@allow-real grep '# allow foo\(/v6\)\? 53/udp incorrect-network$'
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 53 udp incorrect-network
}
test-ufw-docker--list-grep-with-incorrect-network-assert() {
@fail
}
test-ufw-docker--list-foo-80-_-_() {
mock-ufw-status-numbered-foo
@allow-real grep '# allow foo\(/v6\)\? 80/tcp\( [-_.[:alnum:]]\+\)\?$'
load-ufw-docker-function ufw-docker--list load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 80 ufw-docker--list foo 80
} }
test-ufw-docker--list-name-80-assert() { test-ufw-docker--list-foo-80-_-_-assert() {
grep "# allow foo\\( 80\\/tcp\\)\\?\\( [[:graph:]]*\\)\\?\$" @stdout "[ 3] 172.17.0.3 80/tcp ALLOW FWD Anywhere # allow foo 80/tcp bridge"
} @stdout "[12] fd00:a:b:deaf::3 80/tcp ALLOW FWD Anywhere (v6) # allow foo/v6 80/tcp bridge"
test-ufw-docker--list-name-80-udp() {
@mocktrue ufw status numbered
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 80 udp
}
test-ufw-docker--list-name-80-udp-assert() {
grep "# allow foo\\( 80\\/udp\\)\\?\\( [[:graph:]]*\\)\\?\$"
} }
@ -520,7 +748,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 ""
@mock sort -rn @mockpipe 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
@ -532,7 +760,8 @@ 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
@mock sort -rn @mockpipe sort -rn
@ignore echo
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
@ -542,3 +771,91 @@ test-ufw-docker--delete-all-assert() {
ufw delete 8 ufw delete 8
ufw delete 9 ufw delete 9
} }
test-ufw-docker--check-install_ipv4() {
@mock mktemp === @stdout /tmp/after_rules_tmp
@mock sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" /etc/ufw/after.rules
@mock tee "/tmp/after_rules_tmp"
@capture tee -a /tmp/after_rules_tmp
@allow-real cat
load-ufw-docker-function ufw-docker--check-install
ufw-docker--check-install
}
test-ufw-docker--check-install_ipv4-assert() {
rm-on-exit /tmp/after_rules_tmp
sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" /etc/ufw/after.rules
@assert-capture tee -a /tmp/after_rules_tmp <<\EOF
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-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
-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
-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 udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j RETURN
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER
EOF
diff -u --color=auto /etc/ufw/after.rules /tmp/after_rules_tmp
}
test-ufw-docker--check-install_ipv4-subnets() {
@mock ufw-docker--list-docker-subnets IPv4 192.168.56.128/28 172.16.0.0/12 === @stdout "172.16.0.0/12" "192.168.56.128/28"
@mock mktemp === @stdout /tmp/after_rules_tmp
@mock sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" /etc/ufw/after.rules
@mock tee "/tmp/after_rules_tmp"
@capture tee -a /tmp/after_rules_tmp
@allow-real cat
load-ufw-docker-function ufw-docker--check-install
ufw-docker--check-install --docker-subnets 192.168.56.128/28 172.16.0.0/12
}
test-ufw-docker--check-install_ipv4-subnets-assert() {
rm-on-exit /tmp/after_rules_tmp
sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" /etc/ufw/after.rules
@assert-capture tee -a /tmp/after_rules_tmp <<\EOF
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.56.128/28
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-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
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.56.128/28
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.56.128/28
-A DOCKER-USER -j RETURN
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER
EOF
diff -u --color=auto /etc/ufw/after.rules /tmp/after_rules_tmp
}

View file

@ -2,16 +2,20 @@
set -euo pipefail set -euo pipefail
[[ -n "${DEBUG:-}" ]] && set -x [[ -n "${DEBUG:-}" ]] && set -x
LANG=en_US.UTF-8 # UFW-DOCKER GLOBAL VARIABLES START #
LANGUAGE=en_US: LC_ALL=C
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_NAME="[-_.[:alnum:]]\\+"
DEFAULT_PROTO=tcp DEFAULT_PROTO=tcp
ENV_VALUE_SPLITTER=','
ufw_docker_agent=ufw-docker-agent ufw_docker_agent=ufw-docker-agent
ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:220920-legacy}" ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:250708-nf_tables}"
after_rules="/etc/ufw/after.rules"
after6_rules="/etc/ufw/after6.rules"
# UFW-DOCKER GLOBAL VARIABLES END #
if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then
if iptables --version | grep -F '(legacy)' &>/dev/null; then if iptables --version | grep -F '(legacy)' &>/dev/null; then
@ -24,25 +28,37 @@ fi
test -n "$ufw_docker_agent_image" test -n "$ufw_docker_agent_image"
function ufw-docker--status() { function ufw-docker--status() {
ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" ufw-docker--list
} }
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:-}"
local NETWORK="${4:-}" local NETWORK="${4:-}"
local params_count="$#"
local _grep_regexp
if [[ -z "$INSTANCE_PORT" ]]; then [[ -n "${INSTANCE_NAME:-}" ]] || INSTANCE_NAME="$GREP_REGEXP_NAME"
if [[ -z "${INSTANCE_PORT:-}" && -z "${PROTO:-}" && -z "${NETWORK-}" ]]; then
INSTANCE_PORT="[[:digit:]]\\+" INSTANCE_PORT="[[:digit:]]\\+"
PROTO="\\(tcp\\|udp\\)" [[ -n "${PROTO:-}" ]] || PROTO="\\(tcp\\|udp\\)"
_grep_regexp="\\( ${INSTANCE_PORT}/${PROTO}\\( ${GREP_REGEXP_NAME}\\)\\?\\)\\?"
else
[[ -n "${INSTANCE_PORT:-}" ]] || INSTANCE_PORT="[[:digit:]]\\+"
[[ -n "${PROTO:-}" ]] || PROTO="${DEFAULT_PROTO}"
if [[ -n "${NETWORK:-}" ]]; then
NETWORK=" ${NETWORK}"
else
NETWORK="\\( ${GREP_REGEXP_NAME}\\)\\?"
fi
_grep_regexp=" ${INSTANCE_PORT}/${PROTO}${NETWORK}"
fi fi
if [[ -z "$NETWORK" ]]; then local ufw_output
NETWORK="[[:graph:]]*" ufw_output="$(ufw status numbered)"
fi
ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\?\\( ${NETWORK}\\)\\?\$" grep "# allow ${INSTANCE_NAME}\\(/v6\\)\\?${_grep_regexp}\$" <<< "$ufw_output"
} }
function ufw-docker--list-number() { function ufw-docker--list-number() {
@ -61,15 +77,15 @@ function ufw-docker--allow() {
local INSTANCE_PORT="$2" local INSTANCE_PORT="$2"
local PROTO="$3" local PROTO="$3"
local NETWORK="${4:-}" 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 INSTANCE_IP_ADDRESSES < <(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' "$INSTANCE_NAME" 2>/dev/null | remove_blank_lines) 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)
[[ -z "${INSTANCE_IP_ADDRESSES:-}" ]] && die "Could not find a running instance \"$INSTANCE_NAME\"." [[ -z "${NETWORK_ADDRESSES:-}" ]] && die "Could not find a running instance \"$INSTANCE_NAME\"."
mapfile -t INSTANCE_NETWORK_NAMES < <(docker inspect --format='{{range $k, $v := .NetworkSettings.Networks}}{{printf "%s\n" $k}}{{end}}' "$INSTANCE_NAME" 2>/dev/null | 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) mapfile -t PORT_PROTO_LIST < <(docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' "$INSTANCE_NAME" | remove_blank_lines)
if [[ -z "${PORT_PROTO_LIST:-}" ]]; then if [[ -z "${PORT_PROTO_LIST:-}" ]]; then
@ -77,25 +93,26 @@ function ufw-docker--allow() {
return 1 return 1
fi fi
RETVAL=1 local count=0
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
ITER=0 for item in "${NETWORK_ADDRESSES[@]}"; do
for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do INSTANCE_NETWORK="${item% *}"
INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" IP="${item#* }"
ITER=$((ITER+1))
if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then
continue continue
fi fi
ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" if [[ "$IP" = *:* ]]; then SUFFIX="/v6"; else SUFFIX=""; fi
RETVAL="$?" ufw-docker--add-rule "${INSTANCE_NAME}${SUFFIX}" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}"
(( ++count ))
done done
fi fi
done done
if [[ "$RETVAL" -ne 0 ]]; then if [[ "$count" -eq 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 "$RETVAL" return 0
} }
function ufw-docker--add-service-rule() { function ufw-docker--add-service-rule() {
@ -148,7 +165,7 @@ function ufw-docker--instance-name() {
{ {
{ {
docker inspect --format='{{.Name}}' "$INSTANCE_ID" 2>/dev/null | sed -e 's,^/,,' | docker inspect --format='{{.Name}}' "$INSTANCE_ID" 2>/dev/null | sed -e 's,^/,,' |
grep "^${GREP_REGEXP_INSTANCE_NAME}\$" 2>/dev/null grep "^${GREP_REGEXP_NAME}\$" 2>/dev/null
} || echo -n "$INSTANCE_ID"; } || echo -n "$INSTANCE_ID";
} | remove_blank_lines } | remove_blank_lines
} }
@ -164,7 +181,7 @@ function ufw-docker--service() {
shift || true shift || true
declare service_id_or_name="${1:?Missing swarm service name or service ID}" declare service_id_or_name="${1:?Missing swarm service name or service ID}"
"ufw-docker--service-${service_action}" "${service_id_or_name}" "ufw-docker--service-${service_action}" "$@"
;; ;;
allow) allow)
shift || true shift || true
@ -189,6 +206,12 @@ function ufw-docker--get-service-name() {
docker service inspect "${service_name}" --format "{{.Spec.Name}}" docker service inspect "${service_name}" --format "{{.Spec.Name}}"
} }
function ufw-docker--list-service-ports() {
declare service_name="$1"
docker service inspect "$service_name" \
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}'
}
function ufw-docker--service-allow() { function ufw-docker--service-allow() {
declare service_name="$1" declare service_name="$1"
declare service_port="$2" declare service_port="$2"
@ -208,23 +231,24 @@ function ufw-docker--service-allow() {
declare service_id declare service_id
service_id="$(ufw-docker--get-service-id "${service_name}")" service_id="$(ufw-docker--get-service-id "${service_name}")"
[[ -z "${service_id:-}" ]] && die "Could not find service \"$service_name\"" [[ -z "${service_id:-}" ]] && die "Could not find service \"$service_name\""
service_name="$(ufw-docker--get-service-name "${service_name}")" service_name="$(ufw-docker--get-service-name "${service_name}")"
exec 9< <(docker service inspect "$service_name" \ declare env_value= published_port= target_port=
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}') exec 9< <(ufw-docker--list-service-ports "$service_name")
while read -u 9 -r port target_port; do while read -u 9 -r published_port target_port; do
if [[ "$target_port" = "${service_port}/${service_proto}" ]]; then if [[ "$target_port" = "${service_port}/${service_proto}" ]]; then
declare service_env="ufw_public_${service_id}=${service_name}/${port}/${service_proto}" env_value="${service_name}/${published_port}/${service_proto}"
break; break;
fi fi
done done
exec 9<&- exec 9<&-
[[ -z "${service_env:-}" ]] && die "Service $service_name does not publish port $service_port." [[ -z "${env_value:-}" ]] && die "Service $service_name does not publish port $service_port."
declare service_env
if ! docker service inspect "$ufw_docker_agent" &>/dev/null; then if ! docker service inspect "$ufw_docker_agent" &>/dev/null; then
err "Not found ufw-docker-agent service, creating ..." err "Not found ufw-docker-agent service, creating ..."
service_env="ufw_public_${service_id}=${env_value}"
docker service create --name "$ufw_docker_agent" --mode global \ docker service create --name "$ufw_docker_agent" --mode global \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \ --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/ufw,target=/etc/ufw,readonly=true \ --mount type=bind,source=/etc/ufw,target=/etc/ufw,readonly=true \
@ -233,15 +257,20 @@ function ufw-docker--service-allow() {
--env "${service_env}" \ --env "${service_env}" \
"${ufw_docker_agent_image}" "${ufw_docker_agent_image}"
else else
declare -a service_env_list declare -a value_list=() service_env_list=()
service_env_list+=(--env-add "${service_env}")
exec 8< <(ufw-docker--get-env-list) exec 8< <(ufw-docker--get-env-list)
while read -u 8 -r id value; do while read -u 8 -r id value; do
[[ "$id" = "$service_id" ]] && continue [[ "$id" != "$service_id" && "$value" = "${service_name}"/* ]] && service_env_list+=(--env-rm "ufw_public_${id}")
[[ "$value" = "${service_name}"/* ]] && service_env_list+=(--env-rm "ufw_public_${id}") [[ "$id" = "$service_id" ]] || continue
[[ "${value}" = "${env_value}" || "${value}" = "${env_value}"/* ]] ||
value_list+=("${value}")
done done
exec 8<&- exec 8<&-
value_list=("${env_value}" "${value_list[@]}")
service_env="ufw_public_${service_id}=$(IFS="${ENV_VALUE_SPLITTER}"; printf '%s' "${value_list[*]}")"
service_env_list+=(--env-add "${service_env}")
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}" \
@ -257,28 +286,62 @@ function ufw-docker--get-env-list() {
--format '{{range $k,$v := .Spec.TaskTemplate.ContainerSpec.Env}}{{ $v }}{{"\n"}}{{end}}' | --format '{{range $k,$v := .Spec.TaskTemplate.ContainerSpec.Env}}{{ $v }}{{"\n"}}{{end}}' |
sed -e '/^ufw_public_/!d' \ sed -e '/^ufw_public_/!d' \
-e 's/^ufw_public_//' \ -e 's/^ufw_public_//' \
-e 's/=/ /' -e 's/=/ /' |
while read -r id value; do
tr ',' '\n' <<< "$value" | sed "s/^/${id} /g"
done
} }
function ufw-docker--service-delete() { function ufw-docker--service-delete() {
declare service_name="$1" declare service_name="$1"
declare service_port="${2:-}"
declare port proto
if [[ "${service_port:-}" = */* ]]; then
port="${service_port%/*}"
proto="${service_port#*/}"
elif [[ -n "${service_port:-}" ]]; then
port="$service_port"
proto="$DEFAULT_PROTO"
fi
exec 8< <(ufw-docker--get-env-list) declare service_id
while read -u 8 -r id value; do service_id="$(ufw-docker--get-service-id "${service_name}")"
if [[ "$id" = "$service_name" ]] || [[ "$value" = "${service_name}"/* ]]; then [[ -z "${service_id:-}" ]] && die "Could not find service \"$service_name\""
declare service_id="$id" service_name="$(ufw-docker--get-service-name "${service_name}")"
service_name="${value%%/*}"
declare service_env="ufw_public_${service_id}=${service_name}/deny" declare env_value= published_port= target_port=
if [[ -n "${port:-}" ]]; then
exec 9< <(ufw-docker--list-service-ports "$service_name")
while read -u 9 -r published_port target_port; do
if [[ "$target_port" = "${port}/${proto}" ]]; then
env_value="${service_name}/${published_port}/${proto}"
break; break;
fi fi
done done
exec 8<&- exec 9<&-
[[ -n "${env_value:-}" ]] || die "Service $service_name does not publish port $service_port."
else
declare env_value="${service_name}"
fi
[[ -z "${service_env:-}" ]] && die "Could not find service \"$service_name\"" declare id value
declare -a value_list=()
exec 8< <(ufw-docker--get-env-list)
while read -u 8 -r id value; do
[[ "$id" = "$service_id" ]] || continue
[[ "${value}" = "${env_value}" || "${value}" = "${env_value}"/* ]] ||
value_list+=("${value}")
done
exec 8<&-
value_list=("${env_value}/deny" "${value_list[@]}")
declare service_env
service_env="ufw_public_${service_id}=$(IFS="${ENV_VALUE_SPLITTER}"; printf '%s' "${value_list[*]}")"
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}"
} }
@ -287,14 +350,20 @@ function ufw-docker--raw-command() {
ufw "$@" ufw "$@"
} }
after_rules="/etc/ufw/after.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 done." ufw-docker--check-install "$@" && err "\\nCheck IPv4 firewall rules 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
@ -312,12 +381,45 @@ function on-exit() {
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
declare cidr
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)}" after_rules_tmp="${after_rules_tmp:-$(mktemp)}"
rm-on-exit "$after_rules_tmp" rm-on-exit "$after_rules_tmp"
sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp" sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" | tee "$after_rules_tmp" >/dev/null
>> "${after_rules_tmp}" cat <<-\EOF {
cat <<-\EOF
# BEGIN UFW AND DOCKER # BEGIN UFW AND DOCKER
*filter *filter
:ufw-user-forward - [0:0] :ufw-user-forward - [0:0]
@ -325,18 +427,26 @@ 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
-A DOCKER-USER -j RETURN -s 10.0.0.0/8 EOF
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16 for cidr in "${cidr_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 -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-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 EOF
-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
-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 for cidr in "${cidr_list[@]}"; do
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 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 10.0.0.0/8 done
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 for cidr in "${cidr_list[@]}"; do
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
@ -346,17 +456,87 @@ function ufw-docker--check-install() {
COMMIT COMMIT
# END UFW AND DOCKER # END UFW AND DOCKER
EOF EOF
} | tee -a "${after_rules_tmp}" >/dev/null
diff -u --color=auto "$after_rules" "$after_rules_tmp" diff -u --color=auto "$after_rules" "$after_rules_tmp"
} }
function ufw-docker--check-install_ipv6() {
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" | tee "$after6_rules_tmp" >/dev/null
{
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
} | tee -a "${after6_rules_tmp}"
diff -u --color=auto "$after6_rules" "$after6_rules_tmp"
}
function ufw-docker--install() { function ufw-docker--install() {
if ! ufw-docker--check-install; then local changed=false
if ! ufw-docker--check-install "$@"; then
changed=true
local after_rules_bak local after_rules_bak
after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~" after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~"
err "\\nBacking up $after_rules to $after_rules_bak" err "\\nBacking up $after_rules to $after_rules_bak"
cp "$after_rules" "$after_rules_bak" cp "$after_rules" "$after_rules_bak"
cat "$after_rules_tmp" > "$after_rules" cat "$after_rules_tmp" > "$after_rules"
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:" err "Please restart UFW service manually by using the following command:"
if type systemctl &>/dev/null; then if type systemctl &>/dev/null; then
err " sudo systemctl restart ufw" err " sudo systemctl restart ufw"
@ -366,6 +546,31 @@ function ufw-docker--install() {
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:
@ -375,19 +580,27 @@ function ufw-docker--help() {
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 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 # 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
@ -422,6 +635,10 @@ 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, or mismatched iptables legacy/nf_tables, current $(iptables --version)"
fi fi
if ! docker -v &> /dev/null; then
die "Docker executable not found."
fi
ufw_action="${1:-help}" ufw_action="${1:-help}"
case "$ufw_action" in case "$ufw_action" in
@ -452,14 +669,26 @@ case "$ufw_action" in
NETWORK="${1:-}" NETWORK="${1:-}"
INSTANCE_PORT="${INSTANCE_PORT%/*}" INSTANCE_PORT="${INSTANCE_PORT%/*}"
;;&
delete|list)
"ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO" "$NETWORK"
;;
allow)
"ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO" "$NETWORK" "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" "$@"
;; ;;
status|install|check) 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"
;; ;;
*) *)