From a444fb9457d89c95381453ce5961f5832d505acf Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Wed, 19 Aug 2020 17:50:20 +0800 Subject: [PATCH 01/44] Update README.md, add a link to the docker image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff4c96b..58f295c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ To Fix The Docker and UFW Security Flaw Without Disabling Iptables ================== [![Build Status](https://travis-ci.org/chaifeng/ufw-docker.svg)](https://travis-ci.org/chaifeng/ufw-docker) -![chaifeng/ufw-docker-agent](https://img.shields.io/docker/pulls/chaifeng/ufw-docker-agent) +[![chaifeng/ufw-docker-agent](https://img.shields.io/docker/pulls/chaifeng/ufw-docker-agent)](https://hub.docker.com/r/chaifeng/ufw-docker-agent) - [English](#tldr) - [中文](#太长不想读) From fa5cec9dc52da9773597ae3da4e77fc9e5937abe Mon Sep 17 00:00:00 2001 From: Further <55025025+ifurther@users.noreply.github.com> Date: Mon, 26 Oct 2020 20:14:45 +0800 Subject: [PATCH 02/44] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58f295c..c24151b 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Download `ufw-docker` script sudo wget -O /usr/local/bin/ufw-docker \ https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker - chmod +x /usr/local/bin/ufw-docker + sudo chmod +x /usr/local/bin/ufw-docker Then using the following command to modify the `after.rules` file of `ufw` From 2b4a44ff7a4afaaa63fa33784fc9f355b766f750 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Thu, 1 Apr 2021 17:31:56 +0800 Subject: [PATCH 03/44] Update the vagrant box --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 1499184..fa062d9 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -5,7 +5,7 @@ Vagrant.configure('2') do |config| - config.vm.box = "chaifeng/ubuntu-20.04-docker-19.03.11" + config.vm.box = "chaifeng/ubuntu-20.04-docker-19.03.13" #config.vm.box = "chaifeng/ubuntu-16.04-docker-18.03" config.vm.provider 'virtualbox' do |vb| From cc58088bc58a4f523d785a2d38d82bf786fdaace Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sun, 12 Sep 2021 08:52:29 +0800 Subject: [PATCH 04/44] Update test/bach to the latest version --- test/bach | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bach b/test/bach index ff94833..447edb6 160000 --- a/test/bach +++ b/test/bach @@ -1 +1 @@ -Subproject commit ff948334df72f25410d03cbff72b5eaa5e9de409 +Subproject commit 447edb60db232d3dbc2267f37c49bd7a070cc83d From 0150af87dca49947bd4506dac379eafa3de78ed9 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Mon, 13 Sep 2021 13:46:10 +0800 Subject: [PATCH 05/44] Add Github Actions to run unit tests --- .github/workflows/testing.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/testing.yml diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 0000000..5d9119e --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,16 @@ +name: Unit Testing ufw-docker +on: [push, pull_request] +jobs: + test: + name: Unit Testing + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Checkout submodules + shell: bash + run: | + auth_header="$(git config --local --get http.https://github.com/.extraheader)" + git submodule sync --recursive + git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 + - name: Test ufw-docker + run: ./test.sh From 80a691f084121ecd86ab7ba9638e27132f992474 Mon Sep 17 00:00:00 2001 From: Patrice Brend'amour Date: Sat, 4 Sep 2021 14:16:56 +0200 Subject: [PATCH 06/44] Added support for multiple networks Now networks are added as well, so if a container has multiple networks (e.g. when using docker-compose) it still works --- ufw-docker | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/ufw-docker b/ufw-docker index 197c2d7..e44ea09 100755 --- a/ufw-docker +++ b/ufw-docker @@ -21,12 +21,18 @@ function ufw-docker--list() { local INSTANCE_NAME="$1" local INSTANCE_PORT="${2:-}" local PROTO="${3:-${DEFAULT_PROTO}}" + local NETWORK="${4:-}" if [[ -z "$INSTANCE_PORT" ]]; then INSTANCE_PORT="[[:digit:]]\\+" PROTO="\\(tcp\\|udp\\)" fi - ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\?\$" + + if [[ -z "$NETWORK" ]]; then + NETWORK="[[:graph:]]*" + fi + + ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\?\\( ${NETWORK}\\)\\?\$" } function ufw-docker--list-number() { @@ -44,6 +50,7 @@ function ufw-docker--allow() { local INSTANCE_NAME="$1" local INSTANCE_PORT="$2" local PROTO="$3" + local NETWORK="${4:-}" docker inspect "$INSTANCE_NAME" &>/dev/null || die "Docker instance \"$INSTANCE_NAME\" doesn't exist." @@ -52,6 +59,7 @@ function ufw-docker--allow() { [[ -z "${INSTANCE_IP_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) if [[ -z "${PORT_PROTO_LIST:-}" ]]; then @@ -62,8 +70,15 @@ function ufw-docker--allow() { RETVAL=1 for PORT_PROTO in "${PORT_PROTO_LIST[@]}"; do if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then + ITER=0 for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do - ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" + INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" + ITER=$(expr $ITER + 1) + if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then + continue + fi + + ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" RETVAL="$?" done fi @@ -92,10 +107,11 @@ function ufw-docker--add-rule() { local INSTANCE_IP_ADDRESS="$2" local PORT="$3" local PROTO="$4" + local NETWORK="$5" declare comment - echo "allow ${INSTANCE_NAME} ${PORT}/${PROTO}" + echo "allow ${INSTANCE_NAME} ${PORT}/${PROTO} ${NETWORK}" typeset -a UFW_OPTS UFW_OPTS=(route allow proto "${PROTO}" from any to "$INSTANCE_IP_ADDRESS") @@ -104,12 +120,15 @@ function ufw-docker--add-rule() { UFW_OPTS+=(port "${PORT}") comment="$comment ${PORT}/${PROTO}" } + [[ -n "$NETWORK" ]] && { + comment="$comment ${NETWORK}" + } UFW_OPTS+=(comment "$comment") - if ufw-docker--list "$INSTANCE_NAME" "$PORT" "$PROTO" &>/dev/null; then + if ufw-docker--list "$INSTANCE_NAME" "$PORT" "$PROTO" "$NETWORK" &>/dev/null; then ufw --dry-run "${UFW_OPTS[@]}" | grep "^Skipping" && return 0 err "Remove outdated rule." - ufw-docker--delete "$INSTANCE_NAME" "$PORT" "$PROTO" + ufw-docker--delete "$INSTANCE_NAME" "$PORT" "$PROTO" "$NETWORK" fi echo ufw "${UFW_OPTS[@]}" ufw "${UFW_OPTS[@]}" @@ -341,8 +360,8 @@ function ufw-docker--install() { function ufw-docker--help() { cat <<-EOF >&2 Usage: - ufw-docker [docker-instance-id-or-name [port[/tcp|/udp]]] - ufw-docker delete allow [docker-instance-id-or-name [port[/tcp|/udp]]] + ufw-docker [docker-instance-id-or-name [port[/tcp|/udp]] [network]] + ufw-docker delete allow [docker-instance-id-or-name [port[/tcp|/udp]] [network]] ufw-docker service allow >> ufw-docker service delete allow @@ -363,10 +382,11 @@ function ufw-docker--help() { ufw-docker allow httpd ufw-docker allow httpd 80 ufw-docker allow httpd 80/tcp + ufw-docker allow httpd 80/tcp default ufw-docker delete allow httpd ufw-docker delete allow httpd 80/tcp - + ufw-docker delete allow httpd 80/tcp default ufw-docker service allow httpd 80/tcp @@ -418,10 +438,13 @@ case "$ufw_action" in if [[ "$INSTANCE_PORT" = */udp ]]; then PROTO=udp fi + shift || true + + NETWORK="${1:-}" INSTANCE_PORT="${INSTANCE_PORT%/*}" - "ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO" + "ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO" "$NETWORK" ;; service|raw-command|add-service-rule) shift || true From cd783f91d754fc1c0fa319a49c9511fb46e61726 Mon Sep 17 00:00:00 2001 From: Patrice Brend'amour Date: Sun, 12 Sep 2021 17:43:58 +0200 Subject: [PATCH 07/44] fixed tests --- test/ufw-docker.test.sh | 97 ++++++++++++++++++++++++++++------------- ufw-docker | 2 +- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index 35b3919..b99a402 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -143,7 +143,7 @@ test-ufw-docker-list-httpd() { ufw-docker list httpd } test-ufw-docker-list-httpd-assert() { - ufw-docker--list httpd-container-name "" tcp + ufw-docker--list httpd-container-name "" tcp "" } @@ -152,7 +152,7 @@ test-ufw-docker-allow-httpd() { ufw-docker allow httpd } test-ufw-docker-allow-httpd-assert() { - ufw-docker--allow httpd-container-name "" tcp + ufw-docker--allow httpd-container-name "" tcp "" } @@ -161,7 +161,7 @@ test-ufw-docker-allow-httpd-80() { ufw-docker allow httpd 80 } test-ufw-docker-allow-httpd-80-assert() { - ufw-docker--allow httpd-container-name 80 tcp + ufw-docker--allow httpd-container-name 80 tcp "" } @@ -170,7 +170,7 @@ test-ufw-docker-allow-httpd-80tcp() { ufw-docker allow httpd 80/tcp } test-ufw-docker-allow-httpd-80tcp-assert() { - ufw-docker--allow httpd-container-name 80 tcp + ufw-docker--allow httpd-container-name 80 tcp "" } @@ -179,7 +179,7 @@ test-ufw-docker-allow-httpd-80udp() { ufw-docker allow httpd 80/udp } test-ufw-docker-allow-httpd-80udp-assert() { - ufw-docker--allow httpd-container-name 80 udp + ufw-docker--allow httpd-container-name 80 udp "" } @@ -196,7 +196,7 @@ test-ufw-docker-list-httpd() { ufw-docker list httpd } test-ufw-docker-list-httpd-assert() { - ufw-docker--list httpd-container-name "" tcp + ufw-docker--list httpd-container-name "" tcp "" } @@ -205,7 +205,7 @@ test-ufw-docker-delete-allow-httpd() { ufw-docker delete allow httpd } test-ufw-docker-delete-allow-httpd-assert() { - ufw-docker--delete httpd-container-name "" tcp + ufw-docker--delete httpd-container-name "" tcp "" } @@ -223,6 +223,16 @@ function setup-ufw-docker--allow() { @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 $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-ufw-docker--allow--multinetwork() { + load-ufw-docker-function ufw-docker--allow + + @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 $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 } @@ -269,7 +279,7 @@ test-ufw-docker--allow-instance-and-match-the-port() { ufw-docker--allow instance-name 5000 tcp } test-ufw-docker--allow-instance-and-match-the-port-assert() { - ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp + ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default } @@ -279,9 +289,9 @@ test-ufw-docker--allow-instance-all-published-port() { ufw-docker--allow instance-name "" "" } test-ufw-docker--allow-instance-all-published-port-assert() { - ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp - ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp - ufw-docker--add-rule instance-name 172.18.0.3 5353 udp + ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default + ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default + ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default } @@ -291,45 +301,70 @@ test-ufw-docker--allow-instance-all-published-tcp-port() { ufw-docker--allow instance-name "" tcp } test-ufw-docker--allow-instance-all-published-tcp-port-assert() { - ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp - ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp - ufw-docker--add-rule instance-name 172.18.0.3 5353 udp # FIXME + ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default + ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default + ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default # FIXME } +test-ufw-docker--allow-instance-all-published-port-multinetwork() { + setup-ufw-docker--allow--multinetwork + + ufw-docker--allow instance-name "" "" +} +test-ufw-docker--allow-instance-all-published-port-multinetwork-assert() { + ufw-docker--add-rule instance-name 172.18.0.3 5000 tcp default + ufw-docker--add-rule instance-name 172.19.0.7 5000 tcp awesomenet + ufw-docker--add-rule instance-name 172.18.0.3 8080 tcp default + ufw-docker--add-rule instance-name 172.19.0.7 8080 tcp awesomenet + ufw-docker--add-rule instance-name 172.18.0.3 5353 udp default + ufw-docker--add-rule instance-name 172.19.0.7 5353 udp awesomenet +} + +test-ufw-docker--allow-instance-all-published-port-multinetwork-select-network() { + setup-ufw-docker--allow--multinetwork + + ufw-docker--allow instance-name "" "" awesomenet +} +test-ufw-docker--allow-instance-all-published-port-multinetwork-select-network-assert() { + ufw-docker--add-rule instance-name 172.19.0.7 5000 tcp awesomenet + ufw-docker--add-rule instance-name 172.19.0.7 8080 tcp awesomenet + ufw-docker--add-rule instance-name 172.19.0.7 5353 udp awesomenet +} + test-ufw-docker--add-rule-a-non-existing-rule() { - @mockfalse ufw-docker--list webapp 5000 tcp + @mockfalse ufw-docker--list webapp 5000 tcp default load-ufw-docker-function ufw-docker--add-rule - ufw-docker--add-rule webapp 172.18.0.4 5000 tcp + ufw-docker--add-rule webapp 172.18.0.4 5000 tcp default } test-ufw-docker--add-rule-a-non-existing-rule-assert() { - ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp" + ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default" } test-ufw-docker--add-rule-modify-an-existing-rule() { - @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-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" @mockfalse grep "^Skipping" 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 default } test-ufw-docker--add-rule-modify-an-existing-rule-assert() { - ufw-docker--delete webapp 5000 tcp + ufw-docker--delete webapp 5000 tcp default - ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp" + ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default" } 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 grep "^Skipping" load-ufw-docker-function ufw-docker--add-rule - ufw-docker--add-rule webapp 172.18.0.4 5000 tcp + ufw-docker--add-rule webapp 172.18.0.4 5000 tcp "" } test-ufw-docker--add-rule-skip-an-existing-rule-assert() { @do-nothing @@ -337,17 +372,17 @@ test-ufw-docker--add-rule-skip-an-existing-rule-assert() { test-ufw-docker--add-rule-modify-an-existing-rule-without-port() { - @mocktrue ufw-docker--list webapp "" tcp + @mocktrue ufw-docker--list webapp "" tcp "" @mocktrue ufw --dry-run route allow proto tcp from any to 172.18.0.4 comment "allow webapp" @mockfalse grep "^Skipping" load-ufw-docker-function ufw-docker--add-rule - ufw-docker--add-rule webapp 172.18.0.4 "" tcp + ufw-docker--add-rule webapp 172.18.0.4 "" tcp "" } test-ufw-docker--add-rule-modify-an-existing-rule-without-port-assert() { - ufw-docker--delete webapp "" tcp + ufw-docker--delete webapp "" tcp "" ufw route allow proto tcp from any to 172.18.0.4 comment "allow webapp" } @@ -388,7 +423,7 @@ test-ufw-docker--list-name() { ufw-docker--list foo } test-ufw-docker--list-name-assert() { - grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\$" + grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\\( [[:graph:]]*\\)\\?\$" } test-ufw-docker--list-name-udp() { @@ -397,7 +432,7 @@ test-ufw-docker--list-name-udp() { ufw-docker--list foo "" udp } test-ufw-docker--list-name-udp-assert() { - grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\$" + grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\\( [[:graph:]]*\\)\\?\$" } @@ -407,7 +442,7 @@ test-ufw-docker--list-name-80() { ufw-docker--list foo 80 } test-ufw-docker--list-name-80-assert() { - grep "# allow foo\\( 80\\/tcp\\)\\?\$" + grep "# allow foo\\( 80\\/tcp\\)\\?\\( [[:graph:]]*\\)\\?\$" } @@ -417,7 +452,7 @@ test-ufw-docker--list-name-80-udp() { ufw-docker--list foo 80 udp } test-ufw-docker--list-name-80-udp-assert() { - grep "# allow foo\\( 80\\/udp\\)\\?\$" + grep "# allow foo\\( 80\\/udp\\)\\?\\( [[:graph:]]*\\)\\?\$" } diff --git a/ufw-docker b/ufw-docker index e44ea09..d49770b 100755 --- a/ufw-docker +++ b/ufw-docker @@ -73,7 +73,7 @@ function ufw-docker--allow() { ITER=0 for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" - ITER=$(expr $ITER + 1) + ITER=$((ITER+1)) if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then continue fi From 4335d6fb82186b8f4c6a706561171a230d550f4a Mon Sep 17 00:00:00 2001 From: Patrice Brend'amour Date: Tue, 21 Sep 2021 23:58:34 +0200 Subject: [PATCH 08/44] applied requested changes --- Vagrantfile | 26 ++++++++++++++++++++++++++ ufw-docker | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index fa062d9..c07b29b 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -125,6 +125,29 @@ DOCKERFILE ufw-docker allow public_webapp SHELL + master.vm.provision "multiple-network", type: 'shell', inline: <<-SHELL + set -euo pipefail + if ! docker network ls | grep -F foo-internal; then + docker network create --internal foo-internal + fi + if ! docker network ls | grep -F bar-external; then + docker network create bar-external + fi + + for app in internal-multinet-app:7000 public-multinet-app:17070; do + if ! docker inspect "${app%:*}" &>/dev/null; then + docker run -d --restart unless-stopped --name "${app%:*}" \ + -p "${app#*:}":80 --env name="${app}" \ + --network foo-internal \ + 192.168.56.130:5000/chaifeng/hostname-webapp + docker network connect bar-external "${app%:*}" + fi + done + + ufw-docker allow public-multinet-app 80 bar-external + ufw-docker allow internal-multinet-app 80 foo-internal + SHELL + master.vm.provision "swarm-webapp", type: 'shell', inline: <<-SHELL set -euo pipefail for name in public:29090 local:9000; do @@ -166,6 +189,9 @@ DOCKERFILE test-webapp "$server:18080" ! test-webapp "$server:8000" + test-webapp "$server:17070" # multiple networks app + ! test-webapp "$server:7000" # internal multiple networks app + test-webapp "$server:29090" ! test-webapp "$server:9000" diff --git a/ufw-docker b/ufw-docker index d49770b..bfa3399 100755 --- a/ufw-docker +++ b/ufw-docker @@ -107,7 +107,7 @@ function ufw-docker--add-rule() { local INSTANCE_IP_ADDRESS="$2" local PORT="$3" local PROTO="$4" - local NETWORK="$5" + local NETWORK="${5:-}" declare comment From 22f04125d6969f4760a2599e93160d10827461eb Mon Sep 17 00:00:00 2001 From: Patrice Brend'amour Date: Wed, 22 Sep 2021 00:02:49 +0200 Subject: [PATCH 09/44] added missing test --- test/ufw-docker.test.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index b99a402..3253994 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -333,12 +333,22 @@ test-ufw-docker--allow-instance-all-published-port-multinetwork-select-network-a } test-ufw-docker--add-rule-a-non-existing-rule() { + @mockfalse ufw-docker--list webapp 5000 tcp "" + + load-ufw-docker-function ufw-docker--add-rule + ufw-docker--add-rule webapp 172.18.0.4 5000 tcp +} +test-ufw-docker--add-rule-a-non-existing-rule-assert() { + ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp" +} + +test-ufw-docker--add-rule-a-non-existing-rule-with-network() { @mockfalse ufw-docker--list webapp 5000 tcp default load-ufw-docker-function ufw-docker--add-rule ufw-docker--add-rule webapp 172.18.0.4 5000 tcp default } -test-ufw-docker--add-rule-a-non-existing-rule-assert() { +test-ufw-docker--add-rule-a-non-existing-rule-with-network-assert() { ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default" } From fc7840efefd9e3d4aa4b48a682a7a3d3f6f8cbf7 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sat, 25 Sep 2021 15:25:29 +0800 Subject: [PATCH 10/44] Release the new version of ufw-docker-agent image --- ufw-docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufw-docker b/ufw-docker index bfa3399..defbde8 100755 --- a/ufw-docker +++ b/ufw-docker @@ -11,7 +11,7 @@ GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp ufw_docker_agent=ufw-docker-agent -ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:200812}" +ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:210925}" function ufw-docker--status() { ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" From 4833b190ff74e1300192bc284159fa6981c3506f Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sat, 25 Sep 2021 15:26:02 +0800 Subject: [PATCH 11/44] Update readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index c24151b..75146dc 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,10 @@ Expose the `443` port of the container `httpd` and the protocol is `tcp` ufw-docker allow httpd 443/tcp +Expose the `443` port of the container `httpd` and the protocol is `tcp` and the network is `foobar-external-network` when the container `httpd` is attached to multiple networks + + ufw-docker allow httpd 443/tcp foobar-external-network + Expose all published ports of the container `httpd` ufw-docker allow httpd @@ -563,6 +567,10 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管 ufw-docker allow httpd 443/tcp +如果容器 `httpd` 绑定到多个网络上,暴露其 `443` 端口,协议为 `tcp`,网络为 `foobar-external-network` + + ufw-docker allow httpd 443/tcp foobar-external-network + 把容器 `httpd` 的所有映射端口都暴露出来 ufw-docker allow httpd From 6986267d302f6c0086b32a91334bfda9ea03b11e Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sat, 25 Sep 2021 15:41:17 +0800 Subject: [PATCH 12/44] Revert "Release the new version of ufw-docker-agent image" since the DockerHub cannot build the image automatically This reverts commit fc7840efefd9e3d4aa4b48a682a7a3d3f6f8cbf7. --- ufw-docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufw-docker b/ufw-docker index defbde8..bfa3399 100755 --- a/ufw-docker +++ b/ufw-docker @@ -11,7 +11,7 @@ GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp ufw_docker_agent=ufw-docker-agent -ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:210925}" +ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:200812}" function ufw-docker--status() { ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" From afd62aa96b04d7fd4f35a0cf555b78b23bf7a1f0 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sat, 25 Sep 2021 15:25:29 +0800 Subject: [PATCH 13/44] Version `210925` released --- ufw-docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufw-docker b/ufw-docker index bfa3399..defbde8 100755 --- a/ufw-docker +++ b/ufw-docker @@ -11,7 +11,7 @@ GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp ufw_docker_agent=ufw-docker-agent -ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:200812}" +ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:210925}" function ufw-docker--status() { ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" From 97543811eab472a159bc7f178b3ad3afe70f5c1b Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sat, 23 Oct 2021 21:27:37 +0800 Subject: [PATCH 14/44] Re-indenting `ufw-docker--allow` --- ufw-docker | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ufw-docker b/ufw-docker index defbde8..53cd693 100755 --- a/ufw-docker +++ b/ufw-docker @@ -72,12 +72,11 @@ function ufw-docker--allow() { if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then ITER=0 for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do - INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" - ITER=$((ITER+1)) - if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then - continue - fi - + INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" + ITER=$((ITER+1)) + if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then + continue + fi ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" RETVAL="$?" done From e40bfd517cd0faa5a40bb18a37a6e85afd9bbe6c Mon Sep 17 00:00:00 2001 From: Egor Panfilov Date: Thu, 4 Nov 2021 03:44:00 +0300 Subject: [PATCH 15/44] Fix tabs in ufw-docker --- ufw-docker | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ufw-docker b/ufw-docker index 53cd693..c106519 100755 --- a/ufw-docker +++ b/ufw-docker @@ -31,7 +31,7 @@ function ufw-docker--list() { if [[ -z "$NETWORK" ]]; then NETWORK="[[:graph:]]*" fi - + ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\?\\( ${NETWORK}\\)\\?\$" } @@ -437,9 +437,9 @@ case "$ufw_action" in if [[ "$INSTANCE_PORT" = */udp ]]; then PROTO=udp fi - shift || true - - NETWORK="${1:-}" + shift || true + + NETWORK="${1:-}" INSTANCE_PORT="${INSTANCE_PORT%/*}" From 1333dcd29820331a2c0dc93fe5ce6393fd280e06 Mon Sep 17 00:00:00 2001 From: Egor Panfilov Date: Thu, 4 Nov 2021 03:47:01 +0300 Subject: [PATCH 16/44] Update ufw-docker --- ufw-docker | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/ufw-docker b/ufw-docker index c106519..58e5977 100755 --- a/ufw-docker +++ b/ufw-docker @@ -72,11 +72,11 @@ function ufw-docker--allow() { if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then ITER=0 for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do - INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" - ITER=$((ITER+1)) - if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then - continue - fi + INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" + ITER=$((ITER+1)) + if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then + continue + fi ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" RETVAL="$?" done @@ -303,11 +303,11 @@ function on-exit() { trap on-exit EXIT INT TERM QUIT ABRT ERR function ufw-docker--check-install() { - after_rules_tmp="${after_rules_tmp:-$(mktemp)}" - rm-on-exit "$after_rules_tmp" + after_rules_tmp="${after_rules_tmp:-$(mktemp)}" + rm-on-exit "$after_rules_tmp" - sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp" - >> "${after_rules_tmp}" cat <<-\EOF + sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp" + >> "${after_rules_tmp}" cat <<-\EOF # BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] @@ -337,16 +337,16 @@ function ufw-docker--check-install() { # END UFW AND DOCKER EOF - diff -u --color=auto "$after_rules" "$after_rules_tmp" + diff -u --color=auto "$after_rules" "$after_rules_tmp" } function ufw-docker--install() { if ! ufw-docker--check-install; then - local after_rules_bak + local after_rules_bak after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~" - err "\\nBacking up $after_rules to $after_rules_bak" - cp "$after_rules" "$after_rules_bak" - cat "$after_rules_tmp" > "$after_rules" + err "\\nBacking up $after_rules to $after_rules_bak" + cp "$after_rules" "$after_rules_bak" + cat "$after_rules_tmp" > "$after_rules" err "Please restart UFW service manually by using the following command:" if type systemctl &>/dev/null; then err " sudo systemctl restart ufw" @@ -357,7 +357,7 @@ function ufw-docker--install() { } function ufw-docker--help() { - cat <<-EOF >&2 + cat <<-EOF >&2 Usage: ufw-docker [docker-instance-id-or-name [port[/tcp|/udp]] [network]] ufw-docker delete allow [docker-instance-id-or-name [port[/tcp|/udp]] [network]] @@ -437,9 +437,9 @@ case "$ufw_action" in if [[ "$INSTANCE_PORT" = */udp ]]; then PROTO=udp fi - shift || true + shift || true - NETWORK="${1:-}" + NETWORK="${1:-}" INSTANCE_PORT="${INSTANCE_PORT%/*}" From 8aecb89d4e12a5b2573bd452b537c75e6039a9e9 Mon Sep 17 00:00:00 2001 From: Egor Panfilov Date: Thu, 4 Nov 2021 03:52:23 +0300 Subject: [PATCH 17/44] Update ufw-docker --- ufw-docker | 64 +++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ufw-docker b/ufw-docker index 58e5977..28d53e8 100755 --- a/ufw-docker +++ b/ufw-docker @@ -72,11 +72,11 @@ function ufw-docker--allow() { if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then ITER=0 for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do - INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" - ITER=$((ITER+1)) - if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then - continue - fi + INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" + ITER=$((ITER+1)) + if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then + continue + fi ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" RETVAL="$?" done @@ -280,34 +280,34 @@ function ufw-docker--raw-command() { after_rules="/etc/ufw/after.rules" function ufw-docker--check() { - err "\\n########## iptables -n -L DOCKER-USER ##########" - iptables -n -L DOCKER-USER + err "\\n########## iptables -n -L DOCKER-USER ##########" + iptables -n -L DOCKER-USER - err "\\n\\n########## diff $after_rules ##########" - ufw-docker--check-install && err "\\nCheck done." + err "\\n\\n########## diff $after_rules ##########" + ufw-docker--check-install && err "\\nCheck done." } declare -a files_to_be_deleted function rm-on-exit() { - [[ $# -gt 0 ]] && files_to_be_deleted+=("$@") + [[ $# -gt 0 ]] && files_to_be_deleted+=("$@") } function on-exit() { - for file in "${files_to_be_deleted[@]:-}"; do - [[ -f "$file" ]] && rm -r "$file" - done - files_to_be_deleted=() + for file in "${files_to_be_deleted[@]:-}"; do + [[ -f "$file" ]] && rm -r "$file" + done + files_to_be_deleted=() } trap on-exit EXIT INT TERM QUIT ABRT ERR function ufw-docker--check-install() { - after_rules_tmp="${after_rules_tmp:-$(mktemp)}" - rm-on-exit "$after_rules_tmp" + after_rules_tmp="${after_rules_tmp:-$(mktemp)}" + rm-on-exit "$after_rules_tmp" - sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp" - >> "${after_rules_tmp}" cat <<-\EOF + sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp" + >> "${after_rules_tmp}" cat <<-\EOF # BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] @@ -337,27 +337,27 @@ function ufw-docker--check-install() { # END UFW AND DOCKER EOF - diff -u --color=auto "$after_rules" "$after_rules_tmp" + diff -u --color=auto "$after_rules" "$after_rules_tmp" } function ufw-docker--install() { - if ! ufw-docker--check-install; then - local after_rules_bak - after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~" - err "\\nBacking up $after_rules to $after_rules_bak" - cp "$after_rules" "$after_rules_bak" - cat "$after_rules_tmp" > "$after_rules" - err "Please restart UFW service manually by using the following command:" - if type systemctl &>/dev/null; then - err " sudo systemctl restart ufw" - else - err " sudo service ufw restart" + if ! ufw-docker--check-install; then + local after_rules_bak + after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~" + err "\\nBacking up $after_rules to $after_rules_bak" + cp "$after_rules" "$after_rules_bak" + cat "$after_rules_tmp" > "$after_rules" + err "Please restart UFW service manually by using the following command:" + if type systemctl &>/dev/null; then + err " sudo systemctl restart ufw" + else + err " sudo service ufw restart" + fi fi - fi } function ufw-docker--help() { - cat <<-EOF >&2 + cat <<-EOF >&2 Usage: ufw-docker [docker-instance-id-or-name [port[/tcp|/udp]] [network]] ufw-docker delete allow [docker-instance-id-or-name [port[/tcp|/udp]] [network]] From c95d51c975bea76f2dbe505ace134c13a2728e7f Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Thu, 28 Jul 2022 14:49:19 +0800 Subject: [PATCH 18/44] Run integration tests on Apple Silicon with Parallels --- Dockerfile | 13 +++++++------ Vagrantfile | 34 +++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Dockerfile b/Dockerfile index e9095d0..074a5ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,15 @@ FROM ubuntu:20.04 -ARG docker_version="19.03.12" +ARG docker_version="20.10.17" ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ - && apt-get install -y --no-install-recommends apt-transport-https \ - ca-certificates curl software-properties-common gnupg dirmngr \ - && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 \ - && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ - $(lsb_release -cs) stable" \ + && apt-get install -y ca-certificates curl gnupg lsb-release \ + && mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg]" \ + "https://download.docker.com/linux/ubuntu" "$(lsb_release -cs) stable" \ + | tee /etc/apt/sources.list.d/docker.list > /dev/null \ && apt-get update \ && apt-get install -y --no-install-recommends locales ufw \ && ( apt-get install -y --no-install-recommends "docker-ce=5:${docker_version}~*" || \ diff --git a/Vagrantfile b/Vagrantfile index c07b29b..d880da7 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,9 +3,11 @@ # -*- mode: ruby -*- # vi: set ft=ruby : +ENV['VAGRANT_NO_PARALLEL']="true" + Vagrant.configure('2') do |config| - config.vm.box = "chaifeng/ubuntu-20.04-docker-19.03.13" + config.vm.box = "chaifeng/ubuntu-20.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" #config.vm.box = "chaifeng/ubuntu-16.04-docker-18.03" config.vm.provider 'virtualbox' do |vb| @@ -13,6 +15,11 @@ Vagrant.configure('2') do |config| vb.default_nic_type = "virtio" end + config.vm.provider 'parallels' do |prl| + prl.memory = '1024' + prl.check_guest_tools = false + end + ip_prefix="192.168.56" config.vm.provision 'docker-daemon-config', type: 'shell', inline: <<-SHELL @@ -57,15 +64,16 @@ Vagrant.configure('2') do |config| private_registry="#{ip_prefix}.130:5000" config.vm.define "master" do |master| + master_ip_address = "#{ip_prefix}.130" master.vm.hostname = "master" - master.vm.network "private_network", ip: "#{ip_prefix}.130" + master.vm.network "private_network", ip: "#{master_ip_address}" - master.vm.provision "unit-testing", type: 'shell', inline: <<-SHELL + master.vm.provision "unit-testing", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail /vagrant/test.sh SHELL - master.vm.provision "docker-registry", type: 'docker' do |d| + master.vm.provision "docker-registry", preserve_order: true, type: 'docker' do |d| d.run "registry", image: "registry:2", args: "-p 5000:5000", @@ -75,7 +83,7 @@ Vagrant.configure('2') do |config| ufw_docker_agent_image = "#{private_registry}/chaifeng/ufw-docker-agent:test" - master.vm.provision "docker-build-ufw-docker-agent", type: 'shell', inline: <<-SHELL + master.vm.provision "docker-build-ufw-docker-agent", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail docker build -t #{ufw_docker_agent_image} /vagrant docker push #{ufw_docker_agent_image} @@ -87,15 +95,15 @@ Vagrant.configure('2') do |config| echo "Defaults env_keep += DEBUG" >> /etc/sudoers.d/98_ufw-docker SHELL - master.vm.provision "swarm-init", type: 'shell', inline: <<-SHELL + master.vm.provision "swarm-init", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail docker info | fgrep 'Swarm: active' && exit 0 - docker swarm init --advertise-addr eth1 + docker swarm init --advertise-addr "#{master_ip_address}" docker swarm join-token worker --quiet > /vagrant/.vagrant/docker-join-token SHELL - master.vm.provision "build-webapp", type: 'shell', inline: <<-SHELL + master.vm.provision "build-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail docker build -t #{private_registry}/chaifeng/hostname-webapp - <<\\DOCKERFILE FROM httpd:alpine @@ -111,7 +119,7 @@ DOCKERFILE docker push #{private_registry}/chaifeng/hostname-webapp SHELL - master.vm.provision "local-webapp", type: 'shell', inline: <<-SHELL + master.vm.provision "local-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail for name in public:18080 local:8000; do webapp="${name%:*}_webapp" @@ -125,7 +133,7 @@ DOCKERFILE ufw-docker allow public_webapp SHELL - master.vm.provision "multiple-network", type: 'shell', inline: <<-SHELL + master.vm.provision "multiple-network", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail if ! docker network ls | grep -F foo-internal; then docker network create --internal foo-internal @@ -148,7 +156,7 @@ DOCKERFILE ufw-docker allow internal-multinet-app 80 foo-internal SHELL - master.vm.provision "swarm-webapp", type: 'shell', inline: <<-SHELL + master.vm.provision "swarm-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail for name in public:29090 local:9000; do webapp="${name%:*}_service" @@ -167,7 +175,7 @@ DOCKERFILE node.vm.hostname = "node#{ip}" node.vm.network "private_network", ip: "#{ip_prefix}.#{ 130 + ip }" - node.vm.provision "swarm-join", type: 'shell', inline: <<-SHELL + node.vm.provision "swarm-join", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail docker info | fgrep 'Swarm: active' && exit 0 @@ -181,7 +189,7 @@ DOCKERFILE external.vm.hostname = "external" external.vm.network "private_network", ip: "#{ip_prefix}.127" - external.vm.provision "testing", type: 'shell', inline: <<-SHELL + external.vm.provision "testing", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail set -x server="http://#{ip_prefix}.130" From 9df291d39e737c4ea7ba196bd37686814e9b748f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Mon, 22 Aug 2022 11:01:31 +0200 Subject: [PATCH 19/44] Bump Ubuntu version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 074a5ff..eee76c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG docker_version="20.10.17" From d110fc00ffb954d5f25a26099e6c1b20813916d6 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Thu, 1 Sep 2022 19:53:22 +0800 Subject: [PATCH 20/44] Testing on ubuntu 22.04 --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index d880da7..c8f5702 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,7 +7,7 @@ ENV['VAGRANT_NO_PARALLEL']="true" Vagrant.configure('2') do |config| - config.vm.box = "chaifeng/ubuntu-20.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" + config.vm.box = "chaifeng/ubuntu-22.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" #config.vm.box = "chaifeng/ubuntu-16.04-docker-18.03" config.vm.provider 'virtualbox' do |vb| From 5033bf815ca77ab875900d32c7c0644a69e4f9a5 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Tue, 20 Sep 2022 21:26:16 +0800 Subject: [PATCH 21/44] Auto select the correct agent image for different version of iptables --- Dockerfile | 2 +- Vagrantfile | 10 +++++----- test/ufw-docker.test.sh | 43 ++++++++++++++++++++++++++++++++++++++++- ufw-docker | 14 ++++++++++++-- 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index eee76c2..074a5ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:20.04 ARG docker_version="20.10.17" diff --git a/Vagrantfile b/Vagrantfile index c8f5702..3147eac 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,8 +7,8 @@ ENV['VAGRANT_NO_PARALLEL']="true" Vagrant.configure('2') do |config| - config.vm.box = "chaifeng/ubuntu-22.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" - #config.vm.box = "chaifeng/ubuntu-16.04-docker-18.03" + #config.vm.box = "chaifeng/ubuntu-22.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" + config.vm.box = "chaifeng/ubuntu-20.04-docker-#{(`uname -m`.strip == "arm64")?"19.03.13-arm64":"19.03.13"}" config.vm.provider 'virtualbox' do |vb| vb.memory = '1024' @@ -85,10 +85,10 @@ Vagrant.configure('2') do |config| master.vm.provision "docker-build-ufw-docker-agent", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail - docker build -t #{ufw_docker_agent_image} /vagrant - docker push #{ufw_docker_agent_image} + docker build -t #{ufw_docker_agent_image}-legacy /vagrant + docker push #{ufw_docker_agent_image}-legacy - echo "export UFW_DOCKER_AGENT_IMAGE=#{ufw_docker_agent_image}" > /etc/profile.d/ufw-docker.sh + echo "export UFW_DOCKER_AGENT_IMAGE=#{ufw_docker_agent_image}-nf_tables" > /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 diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index 3253994..83f992e 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -12,12 +12,17 @@ source "$working_dir"/bach/bach.sh @mocktrue ufw status @mocktrue grep -Fq "Status: active" + @mock iptables --version + @mocktrue grep -F '(legacy)' + @ignore remove_blank_lines @ignore echo @ignore err DEFAULT_PROTO=tcp GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" + + UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:090502-legacy } function ufw-docker() { @@ -30,6 +35,41 @@ function load-ufw-docker-function() { @load_function "$working_dir/../ufw-docker" "$1" } +test-ufw-docker-init-legacy() { + @mocktrue grep -F '(legacy)' + @source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help +} +test-ufw-docker-init-legacy-assert() { + iptables --version + test -n chaifeng/ufw-docker-agent:090502-legacy + trap on-exit EXIT INT TERM QUIT ABRT ERR + @dryrun cat +} + + +test-ufw-docker-init-nf_tables() { + @mockfalse grep -F '(legacy)' + @source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help +} +test-ufw-docker-init-nf_tables-assert() { + iptables --version + test -n chaifeng/ufw-docker-agent:090502-nf_tables + trap on-exit EXIT INT TERM QUIT ABRT ERR + @dryrun cat +} + + +test-ufw-docker-init() { + UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:100917 + @source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help +} +test-ufw-docker-init-assert() { + test -n chaifeng/ufw-docker-agent:100917 + trap on-exit EXIT INT TERM QUIT ABRT ERR + @dryrun cat +} + + test-ufw-docker-help() { ufw-docker help } @@ -48,11 +88,12 @@ test-ufw-docker-without-parameters-assert() { test-ufw-is-disabled() { @mockfalse grep -Fq "Status: active" + @mock iptables --version === @stdout 'iptables v1.8.4 (legacy)' ufw-docker } test-ufw-is-disabled-assert() { - die "UFW is disabled or you are not root user." + die "UFW is disabled or you are not root user, or mismatched iptables legacy/nf_tables, current iptables v1.8.4 (legacy)" ufw-docker--help } diff --git a/ufw-docker b/ufw-docker index 28d53e8..e346a7b 100755 --- a/ufw-docker +++ b/ufw-docker @@ -11,7 +11,17 @@ GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp ufw_docker_agent=ufw-docker-agent -ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:210925}" +ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:220920-legacy}" + +if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then + if iptables --version | grep -F '(legacy)' &>/dev/null; then + ufw_docker_agent_image="${ufw_docker_agent_image%-*}-legacy" + else + ufw_docker_agent_image="${ufw_docker_agent_image%-*}-nf_tables" + fi +fi + +test -n "$ufw_docker_agent_image" function ufw-docker--status() { ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" @@ -409,7 +419,7 @@ function die() { # __main__ if ! ufw status 2>/dev/null | grep -Fq "Status: active" ; then - die "UFW is disabled or you are not root user." + die "UFW is disabled or you are not root user, or mismatched iptables legacy/nf_tables, current $(iptables --version)" fi ufw_action="${1:-help}" From 712b0e8075b0a09944cf2d42b90dd41d815fb9e7 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Tue, 20 Sep 2022 21:51:39 +0800 Subject: [PATCH 22/44] Change to iptables (nf_tables), using Ubuntu 22.04 --- Dockerfile | 2 +- Vagrantfile | 10 +++++----- ufw-docker | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 074a5ff..eee76c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG docker_version="20.10.17" diff --git a/Vagrantfile b/Vagrantfile index 3147eac..5ce177c 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,8 +7,8 @@ ENV['VAGRANT_NO_PARALLEL']="true" Vagrant.configure('2') do |config| - #config.vm.box = "chaifeng/ubuntu-22.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" - config.vm.box = "chaifeng/ubuntu-20.04-docker-#{(`uname -m`.strip == "arm64")?"19.03.13-arm64":"19.03.13"}" + config.vm.box = "chaifeng/ubuntu-22.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" + #config.vm.box = "chaifeng/ubuntu-20.04-docker-#{(`uname -m`.strip == "arm64")?"19.03.13-arm64":"19.03.13"}" config.vm.provider 'virtualbox' do |vb| vb.memory = '1024' @@ -85,10 +85,10 @@ Vagrant.configure('2') do |config| master.vm.provision "docker-build-ufw-docker-agent", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail - docker build -t #{ufw_docker_agent_image}-legacy /vagrant - docker push #{ufw_docker_agent_image}-legacy + docker build -t #{ufw_docker_agent_image}-nf_tables /vagrant + docker push #{ufw_docker_agent_image}-nf_tables - 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}-legacy" > /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 diff --git a/ufw-docker b/ufw-docker index e346a7b..bc8a787 100755 --- a/ufw-docker +++ b/ufw-docker @@ -11,7 +11,7 @@ GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp 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}:220920-nf_tables}" if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then if iptables --version | grep -F '(legacy)' &>/dev/null; then From e99858510d71e319ab635c00a60eabdfb6993eeb Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Wed, 21 Sep 2022 08:43:58 +0800 Subject: [PATCH 23/44] Update Bach Testing Framework --- test/bach | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bach b/test/bach index 447edb6..452ebfb 160000 --- a/test/bach +++ b/test/bach @@ -1 +1 @@ -Subproject commit 447edb60db232d3dbc2267f37c49bd7a070cc83d +Subproject commit 452ebfba3e48fa91b8110a8ddb65379f50d11668 From a689c4eb6e3ac125e1bbb96c1bf70be99c747036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Mon, 22 Aug 2022 11:00:10 +0200 Subject: [PATCH 24/44] Fix (almost) always truthy regexp in ufw-docker--list --- ufw-docker | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ufw-docker b/ufw-docker index bc8a787..5568e11 100755 --- a/ufw-docker +++ b/ufw-docker @@ -42,7 +42,9 @@ function ufw-docker--list() { NETWORK="[[:graph:]]*" fi - ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\?\\( ${NETWORK}\\)\\?\$" + ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\( ${NETWORK}\\)\$" || \ + ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" || \ + ufw status numbered | grep "# allow ${INSTANCE_NAME}\$" } function ufw-docker--list-number() { From 682d8b363f04027fded2b0885855df36d16de66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Thu, 22 Sep 2022 23:49:47 +0200 Subject: [PATCH 25/44] Fix existing unit tests --- test/ufw-docker.test.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index 83f992e..527b5fd 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -474,7 +474,7 @@ test-ufw-docker--list-name() { ufw-docker--list foo } test-ufw-docker--list-name-assert() { - grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\\( [[:graph:]]*\\)\\?\$" + grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$" } test-ufw-docker--list-name-udp() { @@ -483,7 +483,7 @@ test-ufw-docker--list-name-udp() { ufw-docker--list foo "" udp } test-ufw-docker--list-name-udp-assert() { - grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\\( [[:graph:]]*\\)\\?\$" + grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$" } @@ -493,7 +493,7 @@ test-ufw-docker--list-name-80() { ufw-docker--list foo 80 } test-ufw-docker--list-name-80-assert() { - grep "# allow foo\\( 80\\/tcp\\)\\?\\( [[:graph:]]*\\)\\?\$" + grep "# allow foo\\( 80\\/tcp\\)\\( [[:graph:]]*\\)\$" } @@ -503,7 +503,7 @@ test-ufw-docker--list-name-80-udp() { ufw-docker--list foo 80 udp } test-ufw-docker--list-name-80-udp-assert() { - grep "# allow foo\\( 80\\/udp\\)\\?\\( [[:graph:]]*\\)\\?\$" + grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$" } From d1e6c1315674d46107f84ce084266bc7349647c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Fri, 23 Sep 2022 00:24:34 +0200 Subject: [PATCH 26/44] Add unit tests for alternative greps in ufw-docker--list --- test/ufw-docker.test.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index 527b5fd..9d5daa7 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -507,6 +507,29 @@ test-ufw-docker--list-name-80-udp-assert() { } +test-ufw-docker--list-grep-without-network() { + @mocktrue ufw status numbered + @mockfalse grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$" + load-ufw-docker-function ufw-docker--list + ufw-docker--list foo 80 udp +} +test-ufw-docker--list-grep-without-network-assert() { + grep "# allow foo\\( 80\\/udp\\)\$" +} + + +test-ufw-docker--list-grep-without-network-and-port() { + @mocktrue ufw status numbered + @mockfalse grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$" + @mockfalse grep "# allow foo\\( 80\\/udp\\)\$" + load-ufw-docker-function ufw-docker--list + ufw-docker--list foo 80 udp +} +test-ufw-docker--list-grep-without-network-and-port-assert() { + grep "# allow foo\$" +} + + test-ufw-docker--list-number() { @mocktrue ufw-docker--list foo 53 udp From a1d3517aebf3b82ab03fa2e408e09951fb2890e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Fri, 23 Sep 2022 00:49:48 +0200 Subject: [PATCH 27/44] Add integration tests for multiport app --- Vagrantfile | 15 +++++++++++++++ test/bach | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 5ce177c..bfb7f79 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -167,6 +167,18 @@ DOCKERFILE done ufw-docker service allow public_service 80/tcp + + for name in public_multiport; do + webapp="${name}_service" + port_1="23080" + port_2="23443" + if docker service inspect "$webapp" &>/dev/null; then docker service rm "$webapp"; fi + docker service create --name "$webapp" \ + -p "${port_1}:80" -p "${port_2}:443" --env name="$webapp" --replicas 3 #{private_registry}/chaifeng/hostname-webapp + done + + ufw-docker service allow public_multiport 443/tcp + ufw-docker service allow public_multiport 80/tcp SHELL end @@ -203,6 +215,9 @@ DOCKERFILE test-webapp "$server:29090" ! test-webapp "$server:9000" + test-webapp "$server:23080" + test-webapp "$server:23443" + echo "=====================" echo " TEST DONE " echo "=====================" diff --git a/test/bach b/test/bach index 452ebfb..447edb6 160000 --- a/test/bach +++ b/test/bach @@ -1 +1 @@ -Subproject commit 452ebfba3e48fa91b8110a8ddb65379f50d11668 +Subproject commit 447edb60db232d3dbc2267f37c49bd7a070cc83d From 9d890ee3eed0ff49d3194a45e5bd72bbcb59a993 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Mon, 26 Sep 2022 22:00:05 +0800 Subject: [PATCH 28/44] Add integration tests related to PR #71 --- Vagrantfile | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index bfb7f79..67d7d50 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,8 +7,8 @@ ENV['VAGRANT_NO_PARALLEL']="true" Vagrant.configure('2') do |config| - config.vm.box = "chaifeng/ubuntu-22.04-docker-#{(`uname -m`.strip == "arm64")?"20.10.17-arm64":"19.03.13"}" - #config.vm.box = "chaifeng/ubuntu-20.04-docker-#{(`uname -m`.strip == "arm64")?"19.03.13-arm64":"19.03.13"}" + config.vm.box = "chaifeng/ubuntu-22.04-docker-20.10.17#{(`uname -m`.strip == "arm64")?"-arm64":""}" + #config.vm.box = "chaifeng/ubuntu-20.04-docker-20.10.17#{(`uname -m`.strip == "arm64")?"-arm64":""}" config.vm.provider 'virtualbox' do |vb| vb.memory = '1024' @@ -81,14 +81,15 @@ Vagrant.configure('2') do |config| daemonize: true end - ufw_docker_agent_image = "#{private_registry}/chaifeng/ufw-docker-agent:test" + ufw_docker_agent_image = "#{private_registry}/chaifeng/ufw-docker-agent:test-legacy" master.vm.provision "docker-build-ufw-docker-agent", preserve_order: true, type: 'shell', inline: <<-SHELL set -euo pipefail - docker build -t #{ufw_docker_agent_image}-nf_tables /vagrant - docker push #{ufw_docker_agent_image}-nf_tables + suffix="$(iptables --version | grep -o '\\(nf_tables\\|legacy\\)')" + docker build -t "#{ufw_docker_agent_image}-${suffix}" /vagrant + docker push "#{ufw_docker_agent_image}-${suffix}" - echo "export UFW_DOCKER_AGENT_IMAGE=#{ufw_docker_agent_image}-legacy" > /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 "Defaults env_keep += UFW_DOCKER_AGENT_IMAGE" > /etc/sudoers.d/98_ufw-docker @@ -110,6 +111,8 @@ FROM httpd:alpine RUN { echo '#!/bin/sh'; \\ echo 'set -e; (echo -n "${name:-Hi} "; hostname;) > /usr/local/apache2/htdocs/index.html'; \\ + echo 'grep "^Listen 7000" || echo Listen 7000 >> /usr/local/apache2/conf/httpd.conf'; \\ + echo 'grep "^Listen 8080" || echo Listen 8080 >> /usr/local/apache2/conf/httpd.conf'; \\ echo 'exec "$@"'; \\ } > /entrypoint.sh; chmod +x /entrypoint.sh @@ -168,17 +171,12 @@ DOCKERFILE ufw-docker service allow public_service 80/tcp - for name in public_multiport; do - webapp="${name}_service" - port_1="23080" - port_2="23443" - if docker service inspect "$webapp" &>/dev/null; then docker service rm "$webapp"; fi - docker service create --name "$webapp" \ - -p "${port_1}:80" -p "${port_2}:443" --env name="$webapp" --replicas 3 #{private_registry}/chaifeng/hostname-webapp - done + docker service create --name "public_multiport" \ + --publish "40080:80" --publish "47000:7000" --publish "48080:8080" \ + --env name="public_multiport" --replicas 3 #{private_registry}/chaifeng/hostname-webapp - ufw-docker service allow public_multiport 443/tcp ufw-docker service allow public_multiport 80/tcp + ufw-docker service allow public_multiport 8080/tcp SHELL end @@ -205,7 +203,12 @@ DOCKERFILE set -euo pipefail set -x server="http://#{ip_prefix}.130" - function test-webapp() { timeout 3 curl --silent "$@"; } + function test-webapp() { + if timeout 3 curl --silent "$@" + then echo "Success: $*" + else echo "Cannot visit: $*"; return 1 + fi + } test-webapp "$server:18080" ! test-webapp "$server:8000" @@ -215,8 +218,9 @@ DOCKERFILE test-webapp "$server:29090" ! test-webapp "$server:9000" - test-webapp "$server:23080" - test-webapp "$server:23443" + test-webapp "$server:40080" + test-webapp "$server:48080" + ! test-webapp "$server:47000" echo "=====================" echo " TEST DONE " From cdad5e2a028af3834e18dc8fda77b23c8c80d2a5 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sun, 2 Oct 2022 16:59:05 +0800 Subject: [PATCH 29/44] 221002-legacy --- Dockerfile | 2 +- Vagrantfile | 9 ++++++++- ufw-docker | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index eee76c2..074a5ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:20.04 ARG docker_version="20.10.17" diff --git a/Vagrantfile b/Vagrantfile index 67d7d50..ced906d 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,7 +7,14 @@ ENV['VAGRANT_NO_PARALLEL']="true" Vagrant.configure('2') do |config| - config.vm.box = "chaifeng/ubuntu-22.04-docker-20.10.17#{(`uname -m`.strip == "arm64")?"-arm64":""}" + docker_version = "20.10.17" + + ubuntu_version = File.readlines("Dockerfile").filter { |line| + line.start_with?("FROM ") + }.first.match(/\d\d\.\d\d/)[0] + + config.vm.box = "chaifeng/ubuntu-#{ubuntu_version}-docker-#{docker_version}#{(`uname -m`.strip == "arm64")?"-arm64":""}" + #config.vm.box = "chaifeng/ubuntu-20.04-docker-20.10.17#{(`uname -m`.strip == "arm64")?"-arm64":""}" config.vm.provider 'virtualbox' do |vb| diff --git a/ufw-docker b/ufw-docker index 5568e11..dffc501 100755 --- a/ufw-docker +++ b/ufw-docker @@ -11,7 +11,7 @@ GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp ufw_docker_agent=ufw-docker-agent -ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:220920-nf_tables}" +ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:221002-legacy}" if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then if iptables --version | grep -F '(legacy)' &>/dev/null; then From a273ac9d5147a94daa6d30633518f994c2b50d81 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sun, 2 Oct 2022 17:03:51 +0800 Subject: [PATCH 30/44] 221002-nf_tables --- Dockerfile | 2 +- ufw-docker | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 074a5ff..eee76c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG docker_version="20.10.17" diff --git a/ufw-docker b/ufw-docker index dffc501..6ea6b7c 100755 --- a/ufw-docker +++ b/ufw-docker @@ -11,7 +11,7 @@ GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp ufw_docker_agent=ufw-docker-agent -ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:221002-legacy}" +ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:221002-nf_tables}" if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then if iptables --version | grep -F '(legacy)' &>/dev/null; then From 17e6047590e14d3ff1dc6c01f0b4755d115fc078 Mon Sep 17 00:00:00 2001 From: anuragpeshne Date: Sun, 1 Jan 2023 10:51:09 -0800 Subject: [PATCH 31/44] Adds check for docker executable and adds snap to path --- test/ufw-docker.test.sh | 3 +++ ufw-docker | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index 9d5daa7..7a8fa55 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -15,6 +15,9 @@ source "$working_dir"/bach/bach.sh @mock iptables --version @mocktrue grep -F '(legacy)' + @mocktrue docker -v + @mock docker -v === @stdout Docker version 0.0.0, build dummy + @ignore remove_blank_lines @ignore echo @ignore err diff --git a/ufw-docker b/ufw-docker index 6ea6b7c..cc5a187 100755 --- a/ufw-docker +++ b/ufw-docker @@ -5,7 +5,7 @@ set -euo pipefail LANG=en_US.UTF-8 LANGUAGE=en_US: LC_ALL=en_US.UTF-8 -PATH="/bin:/usr/bin:/sbin:/usr/sbin" +PATH="/bin:/usr/bin:/sbin:/usr/sbin:/snap/bin/" GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp @@ -424,6 +424,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)" fi +if ! docker -v &> /dev/null; then + die "Docker executable not found." +fi + ufw_action="${1:-help}" case "$ufw_action" in From 6cdd4dfd2fdc2aefd467e02b8de11e795061509e Mon Sep 17 00:00:00 2001 From: anuragpeshne Date: Sun, 1 Jan 2023 17:56:45 -0800 Subject: [PATCH 32/44] adds test for docker exist --- test/ufw-docker.test.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index 7a8fa55..45ec074 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -101,6 +101,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() { ufw-docker status } From a9fe32d1fa1d6a48580bd34ed8cfc2029a2d1a8c Mon Sep 17 00:00:00 2001 From: Vladislav Fursov Date: Mon, 20 Mar 2023 01:23:23 +0400 Subject: [PATCH 33/44] Add IPv6 support --- README.md | 16 +++++++++++ ufw-docker | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 75146dc..465d602 100644 --- a/README.md +++ b/README.md @@ -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. +### 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 diff --git a/ufw-docker b/ufw-docker index cc5a187..965f3e8 100755 --- a/ufw-docker +++ b/ufw-docker @@ -24,7 +24,7 @@ fi test -n "$ufw_docker_agent_image" function ufw-docker--status() { - ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" + ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" | uniq } function ufw-docker--list() { @@ -42,13 +42,19 @@ function ufw-docker--list() { NETWORK="[[:graph:]]*" fi + # IPv4 ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\( ${NETWORK}\\)\$" || \ ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" || \ ufw status numbered | grep "# allow ${INSTANCE_NAME}\$" + + # IPv6 + ufw status numbered | grep "# allow ${INSTANCE_NAME}_IPv6\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\( ${NETWORK}\\)\$" || \ + ufw status numbered | grep "# allow ${INSTANCE_NAME}_IPv6\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" || \ + ufw status numbered | grep "# allow ${INSTANCE_NAME}_IPv6\$" } function ufw-docker--list-number() { - ufw-docker--list "$@" | sed -e 's/^\[[[:blank:]]*\([[:digit:]]\+\)\].*/\1/' + ufw-docker--list "$@" | sed -e 's/^\[[[:blank:]]*\([[:digit:]]\+\)\].*/\1/' | uniq } function ufw-docker--delete() { @@ -68,6 +74,7 @@ function ufw-docker--allow() { 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 INSTANCE_IP_V6_ADDRESSES < <(docker inspect --format='{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{"\n"}}{{end}}' "$INSTANCE_NAME" 2>/dev/null | remove_blank_lines) [[ -z "${INSTANCE_IP_ADDRESSES:-}" ]] && die "Could not find a running instance \"$INSTANCE_NAME\"." @@ -92,6 +99,17 @@ function ufw-docker--allow() { ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" RETVAL="$?" done + + ITER_V6=0 + for IP in "${INSTANCE_IP_V6_ADDRESSES[@]}"; do + INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER_V6]}" + ITER_V6=$((ITER_V6+1)) + if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then + continue + fi + ufw-docker--add-rule "${INSTANCE_NAME}_IPv6" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" + RETVAL="$?" + done fi done if [[ "$RETVAL" -ne 0 ]]; then @@ -290,6 +308,7 @@ function ufw-docker--raw-command() { } after_rules="/etc/ufw/after.rules" +after6_rules="/etc/ufw/after6.rules" function ufw-docker--check() { err "\\n########## iptables -n -L DOCKER-USER ##########" @@ -297,6 +316,12 @@ function ufw-docker--check() { err "\\n\\n########## diff $after_rules ##########" ufw-docker--check-install && err "\\nCheck done." + + 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 done." } declare -a files_to_be_deleted @@ -352,6 +377,43 @@ function ufw-docker--check-install() { diff -u --color=auto "$after_rules" "$after_rules_tmp" } +function ufw-docker--check-install_ipv6() { + DOCKER_IPV6_NETWORK=$(sed -En 's/.*"fixed-cidr-v6":.?"([^"]*).*/\1/p' /etc/docker/daemon.json) + [[ -z "${DOCKER_IPV6_NETWORK:-}" ]] && die "Could not find \"fixed-cidr-v6\" in \"/etc/docker/daemon.json\"." + + after6_rules_tmp="${after6_rules_tmp:-$(mktemp)}" + rm-on-exit "$after6_rules_tmp" + + sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after6_rules" > "$after6_rules_tmp" + >> "${after6_rules_tmp}" cat <<-\EOF + # BEGIN UFW AND DOCKER + *filter + :ufw6-user-forward - [0:0] + :ufw6-docker-logging-deny - [0:0] + :DOCKER-USER - [0:0] + -A DOCKER-USER -j ufw6-user-forward + + -A DOCKER-USER -j RETURN -s {DOCKER_IPV6_NETWORK} + + -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN + + -A DOCKER-USER -j ufw6-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d {DOCKER_IPV6_NETWORK} + -A DOCKER-USER -j ufw6-docker-logging-deny -p udp -m udp --dport 0:32767 -d {DOCKER_IPV6_NETWORK} + + -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 + + sed -i "s/{DOCKER_IPV6_NETWORK}/${DOCKER_IPV6_NETWORK/\//\\/}/g" "$after6_rules_tmp" + + diff -u --color=auto "$after6_rules" "$after6_rules_tmp" +} + function ufw-docker--install() { if ! ufw-docker--check-install; then local after_rules_bak @@ -366,6 +428,20 @@ function ufw-docker--install() { err " sudo service ufw restart" fi fi + + if ! ufw-docker--check-install_ipv6; then + 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" + err "Please restart UFW service manually by using the following command:" + if type systemctl &>/dev/null; then + err " sudo systemctl restart ufw" + else + err " sudo service ufw restart" + fi + fi } function ufw-docker--help() { From 3d6896cdd1c8312f44fd54013c76888cf6a3948d Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sun, 29 Sep 2024 10:22:50 +0800 Subject: [PATCH 34/44] Update Bach to the latest unreleased version --- test/bach | 2 +- test/ufw-docker.test.sh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/bach b/test/bach index 447edb6..27885eb 160000 --- a/test/bach +++ b/test/bach @@ -1 +1 @@ -Subproject commit 447edb60db232d3dbc2267f37c49bd7a070cc83d +Subproject commit 27885eb79c11e4652dede994c886ae5f9e30994f diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index 45ec074..73ca3f7 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -18,7 +18,7 @@ source "$working_dir"/bach/bach.sh @mocktrue docker -v @mock docker -v === @stdout Docker version 0.0.0, build dummy - @ignore remove_blank_lines + @mockpipe remove_blank_lines @ignore echo @ignore err @@ -465,7 +465,7 @@ test-ufw-docker--instance-name-found-a-name() { } test-ufw-docker--instance-name-found-a-name-assert() { docker inspect --format="{{.Name}}" foo - echo -n foo + @dryrun echo -n foo } @@ -557,7 +557,7 @@ test-ufw-docker--list-number-assert() { test-ufw-docker--delete-empty-result() { @mock ufw-docker--list-number webapp 80 tcp === @stdout "" - @mock sort -rn + @mockpipe sort -rn load-ufw-docker-function ufw-docker--delete ufw-docker--delete webapp 80 tcp @@ -569,7 +569,7 @@ test-ufw-docker--delete-empty-result-assert() { test-ufw-docker--delete-all() { @mock ufw-docker--list-number webapp 80 tcp === @stdout 5 8 9 - @mock sort -rn + @mockpipe sort -rn load-ufw-docker-function ufw-docker--delete ufw-docker--delete webapp 80 tcp From 1fa425bf170dc9da75dce2b8bec643e9f65a33fe Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Fri, 8 Nov 2024 10:25:22 +0800 Subject: [PATCH 35/44] Add node-internal --- Vagrantfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index ced906d..82eb229 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -202,6 +202,11 @@ DOCKERFILE end end + config.vm.define "node-internal" do |node| + node.vm.hostname = "node-internal" + node.vm.network "private_network", ip: "#{ip_prefix}.142" + end + config.vm.define "external" do |external| external.vm.hostname = "external" external.vm.network "private_network", ip: "#{ip_prefix}.127" From 9474084f3fd9d926c57d552655534be6a152737f Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Mon, 11 Nov 2024 18:07:06 +0800 Subject: [PATCH 36/44] Update Dockerfile with improved version matching --- Dockerfile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index eee76c2..f8e5fd9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM ubuntu:22.04 +FROM ubuntu:24.04 -ARG docker_version="20.10.17" +ARG docker_version="27.3.1" ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ @@ -12,8 +12,7 @@ RUN apt-get update \ | tee /etc/apt/sources.list.d/docker.list > /dev/null \ && apt-get update \ && 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=${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:]]')" \ && locale-gen en_US.UTF-8 \ && apt-get clean autoclean \ && apt-get autoremove --yes \ From c9547cb4eccf9542dda9a5568b705cadd8db4f7b Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Mon, 11 Nov 2024 18:08:27 +0800 Subject: [PATCH 37/44] Refactor Vagrantfile with getting docker version automatically --- Vagrantfile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 82eb229..ee11fa8 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -6,16 +6,15 @@ ENV['VAGRANT_NO_PARALLEL']="true" Vagrant.configure('2') do |config| - - docker_version = "20.10.17" - ubuntu_version = File.readlines("Dockerfile").filter { |line| line.start_with?("FROM ") }.first.match(/\d\d\.\d\d/)[0] - config.vm.box = "chaifeng/ubuntu-#{ubuntu_version}-docker-#{docker_version}#{(`uname -m`.strip == "arm64")?"-arm64":""}" + docker_version = File.readlines("Dockerfile").filter { |line| + line.start_with?("ARG docker_version=") + }.first.match(/"([\d\.]+)"/)[1] - #config.vm.box = "chaifeng/ubuntu-20.04-docker-20.10.17#{(`uname -m`.strip == "arm64")?"-arm64":""}" + config.vm.box = "chaifeng/ubuntu-#{ubuntu_version}-docker-#{docker_version}" config.vm.provider 'virtualbox' do |vb| vb.memory = '1024' @@ -178,9 +177,10 @@ DOCKERFILE ufw-docker service allow public_service 80/tcp - docker service create --name "public_multiport" \ - --publish "40080:80" --publish "47000:7000" --publish "48080:8080" \ - --env name="public_multiport" --replicas 3 #{private_registry}/chaifeng/hostname-webapp + docker service inspect "public_multiport" || + docker service create --name "public_multiport" \ + --publish "40080:80" --publish "47000:7000" --publish "48080:8080" \ + --env name="public_multiport" --replicas 3 #{private_registry}/chaifeng/hostname-webapp ufw-docker service allow public_multiport 80/tcp ufw-docker service allow public_multiport 8080/tcp From 8f9335326fed4366770620e3fe6cc5d8570c5688 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Mon, 13 Jan 2025 11:01:15 +0800 Subject: [PATCH 38/44] Build multi-arch docker images --- .github/workflows/build.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bcf0c20 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,34 @@ +name: ci + +on: + push: + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + 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: ${{ vars.DOCKERHUB_USERNAME }}/ufw-docker-agent + + - name: Build and push + uses: docker/build-push-action@v6 + with: + push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64,linux/arm64/v8 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file From 5908cde29662b70c0be44ea33313a0ebeb61fd84 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Mon, 13 Jan 2025 11:24:08 +0800 Subject: [PATCH 39/44] Update github actions that build docker iamges --- .github/workflows/build.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bcf0c20..dd8b45e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: ci +name: Build Images on: push: @@ -7,10 +7,11 @@ jobs: docker: runs-on: ubuntu-latest steps: - - name: Login to Docker Hub + - name: Log into DockerHub + if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - username: ${{ vars.DOCKERHUB_USERNAME }} + username: ${{ github.actor }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up QEMU @@ -31,4 +32,4 @@ jobs: push: ${{ github.event_name != 'pull_request' }} platforms: linux/amd64,linux/arm64/v8 tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file + labels: ${{ steps.meta.outputs.labels }} From 1a51b59cf871f9536bc803ed0e48521f2cc8e2a5 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Mon, 13 Jan 2025 11:31:05 +0800 Subject: [PATCH 40/44] Update build docker image actions, fix username --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd8b45e..7d77234 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ vars.DOCKERHUB_USERNAME }}/ufw-docker-agent + images: ${{ github.actor }}/ufw-docker-agent - name: Build and push uses: docker/build-push-action@v6 From 276324acf0698f77d64d8e1535ffcb3a62f34f03 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sat, 28 Jun 2025 09:19:14 +0800 Subject: [PATCH 41/44] Update Dockerfile, add support for iptables legacy mode via a build arg --- Dockerfile | 10 ++++++++++ Vagrantfile | 13 +++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index f8e5fd9..b63c63e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM ubuntu:24.04 ARG docker_version="27.3.1" +ARG use_iptables_legacy=false ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ @@ -14,6 +15,15 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends locales ufw \ && apt-get install -y --no-install-recommends "docker-ce=$(apt-cache madison docker-ce | grep -m1 -F "${docker_version}" | cut -d'|' -f2 | tr -d '[[:blank:]]')" \ && 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 autoremove --yes \ && rm -rf /var/lib/{apt,dpkg,cache,log}/ diff --git a/Vagrantfile b/Vagrantfile index ee11fa8..7e69446 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -87,12 +87,13 @@ Vagrant.configure('2') do |config| daemonize: true end - ufw_docker_agent_image = "#{private_registry}/chaifeng/ufw-docker-agent:test-legacy" + 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 - set -euo pipefail + set -xeuo pipefail suffix="$(iptables --version | grep -o '\\(nf_tables\\|legacy\\)')" - docker build -t "#{ufw_docker_agent_image}-${suffix}" /vagrant + 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}-${suffix}" > /etc/profile.d/ufw-docker.sh @@ -115,10 +116,10 @@ Vagrant.configure('2') do |config| docker build -t #{private_registry}/chaifeng/hostname-webapp - <<\\DOCKERFILE FROM httpd:alpine +RUN printf "Listen %s\\n" 7000 8080 >> /usr/local/apache2/conf/httpd.conf + RUN { echo '#!/bin/sh'; \\ echo 'set -e; (echo -n "${name:-Hi} "; hostname;) > /usr/local/apache2/htdocs/index.html'; \\ - echo 'grep "^Listen 7000" || echo Listen 7000 >> /usr/local/apache2/conf/httpd.conf'; \\ - echo 'grep "^Listen 8080" || echo Listen 8080 >> /usr/local/apache2/conf/httpd.conf'; \\ echo 'exec "$@"'; \\ } > /entrypoint.sh; chmod +x /entrypoint.sh @@ -187,7 +188,7 @@ DOCKERFILE SHELL end - 1.upto 2 do |ip| + 1.upto 1 do |ip| config.vm.define "node#{ip}" do | node | node.vm.hostname = "node#{ip}" node.vm.network "private_network", ip: "#{ip_prefix}.#{ 130 + ip }" From e9a9f130959da090875ce7f3730a4e0ee65395e5 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Sat, 28 Jun 2025 10:36:13 +0800 Subject: [PATCH 42/44] Update README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 465d602..08e4ff3 100644 --- a/README.md +++ b/README.md @@ -518,6 +518,18 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管 不支持老版本的 Ubuntu,而且命令的使用上可能也会比较复杂。 +### 支持 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" +} +``` + #### 结论 如果我们正在使用老版本的 Ubuntu,我们可以使用 `ufw-user-input`。但是要小心避免把不该暴露的服务暴露出去。 From 168fc59905f287184a1d431988dd577cf78405b3 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Wed, 2 Jul 2025 16:52:56 +0800 Subject: [PATCH 43/44] 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. --- README.md | 70 +++++++++- Vagrantfile | 190 ++++++++++++++++++------- test/ufw-docker-service.test.sh | 1 + test/ufw-docker.test.sh | 164 ++++++++++++++++++---- ufw-docker | 239 ++++++++++++++++++++++---------- 5 files changed, 512 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 08e4ff3..1d836f1 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ UFW is a popular iptables front end on Ubuntu that makes it easy to manage firew The issue is: 1. UFW is enabled on a server that provides external services, and all incoming connections that are not allowed are blocked by default. -2. Run a Docker container on the server and use the `-p` option to publish ports for that container on all IP addresses. +2. Run a Docker container on the server and use the `-p` option to publish ports for that container on all IP addresses. For example: `docker run -d --name httpd -p 0.0.0.0:8080:80 httpd:alpine`, this command will run an httpd service and publish port 80 of the container to port 8080 of the server. 3. UFW will not block all external requests to visit port 8080. Even the command `ufw deny 8080` will not prevent external access to this port. 4. This problem is actually quite serious, which means that a port that was originally intended to provide services internally is exposed to the public network. @@ -43,7 +43,7 @@ Almost all of these solutions are similar. It requires to disable docker's iptab The solutions that we can find on internet are very similar and not elegant, I hope a new solution can: -- Don't need to disable Docker's iptables and let Docker to manage it's network. +- Don't need to disable Docker's iptables and let Docker to manage it's network. We don't need to manually maintain iptables rules for any new Docker networks, and avoid potential side effects after disabling iptables in Docker. - The public network cannot access ports that published by Docker. Even if the port is published on all IP addresses using an option like `-p 8080:80`. Containers and internal networks can visit each other normally. Although it is possible to have Docker publish a container's port to the server's private IP address, the port will not be accessed on the public network. But, this server may have multiple private IP addresses, and these private IP addresses may also change. @@ -195,7 +195,7 @@ Doesn't support older versions of Ubuntu, and the command is a bit more complica ### 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` +[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, @@ -236,6 +236,36 @@ This command does the following things: - Back up the file `/etc/ufw/after.rules` - 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 We can only use this script on manager nodes to manage firewall rules when using in Swarm mode. @@ -305,7 +335,7 @@ Remove rules from all nodes related to the service `web` ### Try it out -We use [Vagrant](https://www.vagrantup.com/) to set up a local testing environment. +We use [Vagrant](https://www.vagrantup.com/) to set up a local testing environment. Run the following command to create 1 master node and 2 worker nodes @@ -422,7 +452,7 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管 COMMIT # END UFW AND DOCKER - + 然后重启 UFW,`sudo systemctl restart ufw`。现在外部就已经无法访问 Docker 发布出来的任何端口了,但是容器内部以及私有网络地址上可以正常互相访问,而且容器也可以正常访问外部的网络。**可能由于某些未知原因,重启 UFW 之后规则也无法生效,请重启服务器。** 如果希望允许外部网络访问 Docker 容器提供的服务,比如有一个容器的服务端口是 `80`。那就可以用以下命令来允许外部网络访问这个服务: @@ -520,7 +550,8 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管 ### 支持 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` +要让 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, @@ -556,6 +587,33 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管 - 备份文件 `/etc/ufw/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 管理的网络。 + +#### 示例 + + # 使用默认的私有局域网子网(IPv4:10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16;IPv6:fd00::/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 环境安装 仅仅可以在管理节点上使用 `ufw-docker` 这个脚本来管理防火墙规则。 diff --git a/Vagrantfile b/Vagrantfile index 7e69446..baf5815 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -27,14 +27,35 @@ Vagrant.configure('2') do |config| end ip_prefix="192.168.56" + ip6_prefix="fd00:a:b" + worker_count=1 - config.vm.provision 'docker-daemon-config', type: 'shell', inline: <<-SHELL + 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 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 ' "insecure-registries": ["localhost:5000", "#{ip_prefix}.130:5000"]' >> /etc/docker/daemon.json [[ -n "#{ENV['DOCKER_REGISTRY_MIRROR']}" ]] && 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 if type systemctl &>/dev/null; then systemctl restart docker @@ -44,27 +65,30 @@ Vagrant.configure('2') do |config| fi SHELL - config.vm.provision 'ufw-docker', type: 'shell', inline: <<-SHELL - set -euo pipefail + config.vm.provision 'ufw-docker', preserve_order: true, type: 'shell', inline: <<-SHELL + set -xeuo pipefail export DEBUG=true lsb_release -is | grep -Fi ubuntu - /vagrant/ufw-docker check || { - ufw allow OpenSSH - ufw allow from #{ip_prefix}.128/28 to any - yes | ufw enable || true - ufw status | grep '^Status: active' + 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 from #{ip_prefix}.128/28 to any + #{env_true_str?('ENABLE_DOCKER_IPV6')} && + ufw allow from #{ip6_prefix}:0:cafe::/80 to any - /vagrant/ufw-docker install + yes | ufw enable || true + ufw status | grep '^Status: active' - sed -i -e 's,192\.168\.0\.0/16,#{ip_prefix}.128/28,' /etc/ufw/after.rules - - systemctl restart ufw + /vagrant/ufw-docker install "${subnets[@]-}" + 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] ' - } + fi SHELL private_registry="#{ip_prefix}.130:5000" @@ -73,10 +97,14 @@ Vagrant.configure('2') do |config| master_ip_address = "#{ip_prefix}.130" master.vm.hostname = "master" 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 - /vagrant/test.sh + [[ -n "#{ENV['DISABLE_UNIT_TESTING']}" ]] || + /vagrant/test.sh SHELL master.vm.provision "docker-registry", preserve_order: true, type: 'docker' do |d| @@ -103,7 +131,7 @@ Vagrant.configure('2') do |config| echo "Defaults env_keep += DEBUG" >> /etc/sudoers.d/98_ufw-docker 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 docker info | fgrep 'Swarm: active' && exit 0 @@ -111,8 +139,8 @@ Vagrant.configure('2') do |config| docker swarm join-token worker --quiet > /vagrant/.vagrant/docker-join-token SHELL - master.vm.provision "build-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL - set -euo pipefail + master.vm.provision "build-webapp", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL + set -xeuo pipefail docker build -t #{private_registry}/chaifeng/hostname-webapp - <<\\DOCKERFILE FROM httpd:alpine @@ -130,7 +158,7 @@ DOCKERFILE 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 webapp="${name%:*}_webapp" port="${name#*:}" @@ -144,12 +172,16 @@ DOCKERFILE 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 - 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 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 for app in internal-multinet-app:7000 public-multinet-app:17070; do @@ -167,33 +199,42 @@ DOCKERFILE 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 webapp="${name%:*}_service" port="${name#*:}" if docker service inspect "$webapp" &>/dev/null; then docker service rm "$webapp"; fi - docker service create --name "$webapp" \ - --publish "${port}:80" --env name="$webapp" --replicas 3 #{private_registry}/chaifeng/hostname-webapp + docker service create --name "$webapp" "${docker_opts[@]}" \ + --publish "${port}:80" --env name="$webapp" --replicas #{worker_count} #{private_registry}/chaifeng/hostname-webapp done ufw-docker service allow public_service 80/tcp docker service inspect "public_multiport" || - docker service create --name "public_multiport" \ + docker service create --name "public_multiport" "${docker_opts[@]}" \ --publish "40080:80" --publish "47000:7000" --publish "48080:8080" \ - --env name="public_multiport" --replicas 3 #{private_registry}/chaifeng/hostname-webapp + --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 end - 1.upto 1 do |ip| + 1.upto worker_count do |ip| config.vm.define "node#{ip}" do | node | node.vm.hostname = "node#{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 docker info | fgrep 'Swarm: active' && exit 0 @@ -206,38 +247,89 @@ DOCKERFILE 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| external.vm.hostname = "external" 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 - set -euo pipefail - set -x - server="http://#{ip_prefix}.130" + external.vm.provision "testing", preserve_order: true, type: 'shell', privileged: false, inline: <<-SHELL + set -xuo pipefail + error_count=0 function test-webapp() { - if timeout 3 curl --silent "$@" - then echo "Success: $*" - else echo "Cannot visit: $*"; return 1 + 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:8000" + + test-webapp "$server:17070" # multiple networks app + test-webapp ! "$server:7000" # internal multiple networks app + + # Docker Swarm + test-webapp ! "$server:9000" + test-webapp ! "$server:47000" } - test-webapp "$server:18080" - ! test-webapp "$server:8000" + function run_tests_ipv4() { + local server="$1" + # Docker Swarm + test-webapp "$server:29090" - test-webapp "$server:17070" # multiple networks app - ! test-webapp "$server:7000" # internal multiple networks app + test-webapp "$server:40080" + test-webapp "$server:48080" - test-webapp "$server:29090" - ! test-webapp "$server:9000" + } + function run_tests_ipv6() { + local server="$1" - test-webapp "$server:40080" - test-webapp "$server:48080" - ! test-webapp "$server:47000" + 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 " TEST DONE " + if [[ "$error_count" -eq 0 ]]; then echo " TEST DONE " + else echo " TESTS FAIL: ${error_count}" + fi echo "=====================" + exit "${error_count}" + } 2>/dev/null SHELL end end diff --git a/test/ufw-docker-service.test.sh b/test/ufw-docker-service.test.sh index dd74bb6..c536d31 100755 --- a/test/ufw-docker-service.test.sh +++ b/test/ufw-docker-service.test.sh @@ -284,6 +284,7 @@ test-ufw-docker--service-delete-matches-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}" } diff --git a/test/ufw-docker.test.sh b/test/ufw-docker.test.sh index 73ca3f7..52ab301 100755 --- a/test/ufw-docker.test.sh +++ b/test/ufw-docker.test.sh @@ -128,6 +128,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() { ufw-docker check } @@ -136,6 +144,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() { ufw-docker service allow httpd } @@ -198,7 +214,7 @@ test-ufw-docker-list-httpd() { ufw-docker list httpd } test-ufw-docker-list-httpd-assert() { - ufw-docker--list httpd-container-name "" tcp "" + ufw-docker--list 'httpd-container-name\(/v6\)\?' "" tcp "" } @@ -246,21 +262,12 @@ 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() { @mock ufw-docker--instance-name httpd === @stdout httpd-container-name ufw-docker delete allow httpd } test-ufw-docker-delete-allow-httpd-assert() { - ufw-docker--delete httpd-container-name "" tcp "" + ufw-docker--delete 'httpd-container-name\(/v6\)\?' "" tcp "" } @@ -277,8 +284,15 @@ function setup-ufw-docker--allow() { load-ufw-docker-function ufw-docker--allow @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 $k, $v := .NetworkSettings.Networks}}{{printf "%s\n" $k}}{{end}}' instance-name === @stdout default + @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 $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 } @@ -286,8 +300,15 @@ function setup-ufw-docker--allow--multinetwork() { load-ufw-docker-function ufw-docker--allow @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 $k, $v := .NetworkSettings.Networks}}{{printf "%s\n" $k}}{{end}}' instance-name === @stdout default awesomenet + @mock docker inspect --format '{{range $name, $net := .NetworkSettings.Networks}}{{if $net.IPAddress}}{{$name}} {{$net.IPAddress}}{{"\n"}}{{end}}{{if $net.GlobalIPv6Address}}{{$name}} {{$net.GlobalIPv6Address}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout "default 172.18.0.3" "awesomenet 172.19.0.7" + @mock docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout 5000/tcp 8080/tcp 5353/udp +} + +function setup-IPv6-ufw-docker--allow--multinetwork() { + load-ufw-docker-function ufw-docker--allow + + @mocktrue docker inspect instance-name + @mock docker inspect --format '{{range $name, $net := .NetworkSettings.Networks}}{{if $net.IPAddress}}{{$name}} {{$net.IPAddress}}{{"\n"}}{{end}}{{if $net.GlobalIPv6Address}}{{$name}} {{$net.GlobalIPv6Address}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout "default 172.18.0.3" "default fd00:cf::42" "awesomenet 172.19.0.7" "awesomenet fd00:cf::207" @mock docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' instance-name === @stdout 5000/tcp 8080/tcp 5353/udp } @@ -387,6 +408,83 @@ 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 } + +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() { @mockfalse ufw-docker--list webapp 5000 tcp "" @@ -410,7 +508,7 @@ test-ufw-docker--add-rule-a-non-existing-rule-with-network-assert() { test-ufw-docker--add-rule-modify-an-existing-rule() { @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" load-ufw-docker-function ufw-docker--add-rule @@ -423,6 +521,21 @@ 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" + + 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() { @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" @@ -438,8 +551,7 @@ test-ufw-docker--add-rule-skip-an-existing-rule-assert() { test-ufw-docker--add-rule-modify-an-existing-rule-without-port() { @mocktrue ufw-docker--list webapp "" tcp "" - - @mocktrue ufw --dry-run route allow proto tcp from any to 172.18.0.4 comment "allow webapp" + @mock ufw --dry-run route allow proto tcp from any to 172.18.0.4 comment "allow webapp" === @echo @mockfalse grep "^Skipping" load-ufw-docker-function ufw-docker--add-rule @@ -484,11 +596,14 @@ test-ufw-docker--instance-name-found-an-id-assert() { test-ufw-docker--list-name() { @mocktrue ufw status numbered + @mockfalse grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$" + @mockfalse grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\$" + load-ufw-docker-function ufw-docker--list ufw-docker--list foo } test-ufw-docker--list-name-assert() { - grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$" + grep "# allow foo\$" } test-ufw-docker--list-name-udp() { @@ -523,24 +638,23 @@ test-ufw-docker--list-name-80-udp-assert() { test-ufw-docker--list-grep-without-network() { @mocktrue ufw status numbered - @mockfalse grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$" load-ufw-docker-function ufw-docker--list ufw-docker--list foo 80 udp } test-ufw-docker--list-grep-without-network-assert() { - grep "# allow foo\\( 80\\/udp\\)\$" + grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$" } test-ufw-docker--list-grep-without-network-and-port() { @mocktrue ufw status numbered - @mockfalse grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$" - @mockfalse grep "# allow foo\\( 80\\/udp\\)\$" + @mockfalse grep "# allow foo\\( 80\\/tcp\\)\\( [[:graph:]]*\\)\$" + load-ufw-docker-function ufw-docker--list - ufw-docker--list foo 80 udp + ufw-docker--list foo 80 } test-ufw-docker--list-grep-without-network-and-port-assert() { - grep "# allow foo\$" + grep "# allow foo\\( 80\\/tcp\\)\$" } diff --git a/ufw-docker b/ufw-docker index 965f3e8..80f7921 100755 --- a/ufw-docker +++ b/ufw-docker @@ -24,7 +24,7 @@ fi test -n "$ufw_docker_agent_image" function ufw-docker--status() { - ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" | uniq + ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME\(/v6\)\?" } function ufw-docker--list() { @@ -32,6 +32,7 @@ function ufw-docker--list() { local INSTANCE_PORT="${2:-}" local PROTO="${3:-${DEFAULT_PROTO}}" local NETWORK="${4:-}" + local params_count="$#" if [[ -z "$INSTANCE_PORT" ]]; then INSTANCE_PORT="[[:digit:]]\\+" @@ -42,19 +43,16 @@ function ufw-docker--list() { NETWORK="[[:graph:]]*" fi - # IPv4 - ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\( ${NETWORK}\\)\$" || \ - ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" || \ - ufw status numbered | grep "# allow ${INSTANCE_NAME}\$" + local ufw_output + ufw_output="$(ufw status numbered)" - # IPv6 - ufw status numbered | grep "# allow ${INSTANCE_NAME}_IPv6\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\( ${NETWORK}\\)\$" || \ - ufw status numbered | grep "# allow ${INSTANCE_NAME}_IPv6\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" || \ - ufw status numbered | grep "# allow ${INSTANCE_NAME}_IPv6\$" + grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\( ${NETWORK}\\)\$" <<< "$ufw_output" || + grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" <<< "$ufw_output" || + grep "# allow ${INSTANCE_NAME}\$" <<< "$ufw_output" } function ufw-docker--list-number() { - ufw-docker--list "$@" | sed -e 's/^\[[[:blank:]]*\([[:digit:]]\+\)\].*/\1/' | uniq + ufw-docker--list "$@" | sed -e 's/^\[[[:blank:]]*\([[:digit:]]\+\)\].*/\1/' } function ufw-docker--delete() { @@ -69,16 +67,15 @@ function ufw-docker--allow() { local INSTANCE_PORT="$2" local PROTO="$3" local NETWORK="${4:-}" + local NETWORK_ADDRESSES PORT_PROTO_LIST PROT_PROTO IP SUFFIX docker inspect "$INSTANCE_NAME" &>/dev/null || 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 INSTANCE_IP_V6_ADDRESSES < <(docker inspect --format='{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{"\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) if [[ -z "${PORT_PROTO_LIST:-}" ]]; then @@ -86,36 +83,26 @@ function ufw-docker--allow() { return 1 fi - RETVAL=1 + local count=0 for PORT_PROTO in "${PORT_PROTO_LIST[@]}"; do if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then - ITER=0 - for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do - INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" - ITER=$((ITER+1)) + for item in "${NETWORK_ADDRESSES[@]}"; do + INSTANCE_NETWORK="${item% *}" + IP="${item#* }" if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then continue fi - ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" - RETVAL="$?" - done - - ITER_V6=0 - for IP in "${INSTANCE_IP_V6_ADDRESSES[@]}"; do - INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER_V6]}" - ITER_V6=$((ITER_V6+1)) - if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then - continue - fi - ufw-docker--add-rule "${INSTANCE_NAME}_IPv6" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" - RETVAL="$?" + if [[ "$IP" = *:* ]]; then SUFFIX="/v6"; else SUFFIX=""; fi + ufw-docker--add-rule "${INSTANCE_NAME}${SUFFIX}" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" + (( ++count )) done fi 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)." + return 1 fi - return "$RETVAL" + return 0 } function ufw-docker--add-service-rule() { @@ -299,6 +286,7 @@ function ufw-docker--service-delete() { docker service update --update-parallelism=0 \ --env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \ --env-add "${service_env}" \ + --env-add DEBUG="${DEBUG-}" \ --image "${ufw_docker_agent_image}" \ "${ufw_docker_agent}" } @@ -315,13 +303,15 @@ function ufw-docker--check() { iptables -n -L DOCKER-USER err "\\n\\n########## diff $after_rules ##########" - ufw-docker--check-install && err "\\nCheck done." + ufw-docker--check-install "$@" && err "\\nCheck IPv4 firewall rules done." - err "\\n########## ip6tables -n -L DOCKER-USER ##########" - ip6tables -n -L DOCKER-USER + 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 done." + err "\\n\\n########## diff $after6_rules ##########" + ufw-docker--check-install_ipv6 "$@" && err "\\nCheck IPv6 firewall rules done." + fi } declare -a files_to_be_deleted @@ -339,12 +329,45 @@ function on-exit() { 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() { + 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)}" rm-on-exit "$after_rules_tmp" sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp" - >> "${after_rules_tmp}" cat <<-\EOF + { + cat <<-\EOF # BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] @@ -352,18 +375,26 @@ function ufw-docker--check-install() { :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 + EOF + + 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 -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 - -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 - -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 udp -m udp --dport 0:32767 -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 + EOF + + for cidr in "${cidr_list[@]}"; do + echo "-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d ${cidr}" + done + 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 @@ -373,19 +404,30 @@ function ufw-docker--check-install() { COMMIT # END UFW AND DOCKER EOF - + } >> "${after_rules_tmp}" diff -u --color=auto "$after_rules" "$after_rules_tmp" } function ufw-docker--check-install_ipv6() { - DOCKER_IPV6_NETWORK=$(sed -En 's/.*"fixed-cidr-v6":.?"([^"]*).*/\1/p' /etc/docker/daemon.json) - [[ -z "${DOCKER_IPV6_NETWORK:-}" ]] && die "Could not find \"fixed-cidr-v6\" in \"/etc/docker/daemon.json\"." + declare -a cidr6_list + declare cidr + if [[ -z "${1-}" ]]; then + cidr6_list=(fd00::/8) + elif [[ "${1-}" = '--docker-subnets' ]]; then + shift || true + mapfile -t cidr6_list < <(ufw-docker--list-docker-subnets IPv6 "$@") + fi + if [[ -z "${cidr6_list:-}" ]]; then + err "INFO: Could not find any IPv6 subnets used by docker engine, will disable IPv6 support.\n" + return 0 + fi after6_rules_tmp="${after6_rules_tmp:-$(mktemp)}" rm-on-exit "$after6_rules_tmp" sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after6_rules" > "$after6_rules_tmp" - >> "${after6_rules_tmp}" cat <<-\EOF + { + cat <<-\EOF # BEGIN UFW AND DOCKER *filter :ufw6-user-forward - [0:0] @@ -393,12 +435,22 @@ function ufw-docker--check-install_ipv6() { :DOCKER-USER - [0:0] -A DOCKER-USER -j ufw6-user-forward - -A DOCKER-USER -j RETURN -s {DOCKER_IPV6_NETWORK} + 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 - -A DOCKER-USER -j ufw6-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d {DOCKER_IPV6_NETWORK} - -A DOCKER-USER -j ufw6-docker-logging-deny -p udp -m udp --dport 0:32767 -d {DOCKER_IPV6_NETWORK} + 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 @@ -408,33 +460,31 @@ function ufw-docker--check-install_ipv6() { COMMIT # END UFW AND DOCKER EOF - - sed -i "s/{DOCKER_IPV6_NETWORK}/${DOCKER_IPV6_NETWORK/\//\\/}/g" "$after6_rules_tmp" - + } >> "${after6_rules_tmp}" diff -u --color=auto "$after6_rules" "$after6_rules_tmp" } 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 after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~" err "\\nBacking up $after_rules to $after_rules_bak" cp "$after_rules" "$after_rules_bak" cat "$after_rules_tmp" > "$after_rules" - err "Please restart UFW service manually by using the following command:" - if type systemctl &>/dev/null; then - err " sudo systemctl restart ufw" - else - err " sudo service ufw restart" - fi fi - if ! ufw-docker--check-install_ipv6; then + if ! ufw-docker--check-install_ipv6 "$@"; then + changed=true local after6_rules_bak after6_rules_bak="${after6_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~" err "\\nBacking up $after6_rules to $after6_rules_bak" cp "$after6_rules" "$after6_rules_bak" cat "$after6_rules_tmp" > "$after6_rules" + fi + + if "$changed"; then err "Please restart UFW service manually by using the following command:" if type systemctl &>/dev/null; then err " sudo systemctl restart ufw" @@ -444,6 +494,31 @@ function ufw-docker--install() { fi } +function ufw-docker--install--help() { + cat <&2 Usage: @@ -453,19 +528,27 @@ function ufw-docker--help() { ufw-docker service allow >> ufw-docker service delete allow + ufw-docker [--docker-subnets [SUBNET0 SUBNET1 ...]] + ufw-docker Examples: ufw-docker help + ufw-docker check --help + ufw-docker install --help - ufw-docker check # Check the installation of firewall rules - ufw-docker install # Install 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 --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 list httpd - ufw-docker allow httpd ufw-docker allow httpd 80 ufw-docker allow httpd 80/tcp @@ -534,14 +617,26 @@ case "$ufw_action" in NETWORK="${1:-}" INSTANCE_PORT="${INSTANCE_PORT%/*}" - + ;;& + delete|list) + "ufw-docker--$ufw_action" "$INSTANCE_NAME\\(/v6\\)\\?" "$INSTANCE_PORT" "$PROTO" "$NETWORK" + ;; + allow) "ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO" "$NETWORK" ;; service|raw-command|add-service-rule) shift || true "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" ;; *) From 9659e5d27d1140bf911af36e4003b5ee3125e6a2 Mon Sep 17 00:00:00 2001 From: Chai Feng Date: Wed, 2 Jul 2025 19:51:59 +0800 Subject: [PATCH 44/44] Release version 250702-nf_tables --- ufw-docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufw-docker b/ufw-docker index 80f7921..ad0c8e6 100755 --- a/ufw-docker +++ b/ufw-docker @@ -11,7 +11,7 @@ GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" DEFAULT_PROTO=tcp ufw_docker_agent=ufw-docker-agent -ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:221002-nf_tables}" +ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:250702-nf_tables}" if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then if iptables --version | grep -F '(legacy)' &>/dev/null; then