92 Commits

Author SHA1 Message Date
Chai Feng cdad5e2a02 221002-legacy 2022-10-02 17:01:24 +08:00
Chai Feng 9d890ee3ee Add integration tests related to PR #71 2022-09-26 22:00:05 +08:00
Radosław Kłos a1d3517aeb Add integration tests for multiport app 2022-09-26 21:43:22 +08:00
Radosław Kłos d1e6c13156 Add unit tests for alternative greps in ufw-docker--list 2022-09-26 21:43:22 +08:00
Radosław Kłos 682d8b363f Fix existing unit tests 2022-09-26 21:43:22 +08:00
Radosław Kłos a689c4eb6e Fix (almost) always truthy regexp in ufw-docker--list 2022-09-26 21:43:22 +08:00
Chai Feng e99858510d Update Bach Testing Framework 2022-09-21 08:43:58 +08:00
Chai Feng 712b0e8075 Change to iptables (nf_tables), using Ubuntu 22.04 2022-09-20 21:51:39 +08:00
Chai Feng 5033bf815c Auto select the correct agent image for different version of iptables 2022-09-20 21:38:10 +08:00
Chai Feng d110fc00ff Testing on ubuntu 22.04 2022-09-01 19:53:22 +08:00
Radosław Kłos 9df291d39e Bump Ubuntu version 2022-08-22 18:59:22 +08:00
Chai Feng c95d51c975 Run integration tests on Apple Silicon with Parallels 2022-07-28 14:53:50 +08:00
Egor Panfilov 8aecb89d4e Update ufw-docker 2021-11-09 22:11:30 +08:00
Egor Panfilov 1333dcd298 Update ufw-docker 2021-11-09 22:11:30 +08:00
Egor Panfilov e40bfd517c Fix tabs in ufw-docker 2021-11-09 22:11:30 +08:00
Chai Feng 97543811ea Re-indenting ufw-docker--allow 2021-10-23 21:27:37 +08:00
Chai Feng afd62aa96b Version 210925 released 2021-09-25 20:43:12 +08:00
Chai Feng 6986267d30 Revert "Release the new version of ufw-docker-agent image" since the DockerHub
cannot build the image automatically

This reverts commit fc7840efef.
2021-09-25 15:41:17 +08:00
Chai Feng 4833b190ff Update readme 2021-09-25 15:26:02 +08:00
Chai Feng fc7840efef Release the new version of ufw-docker-agent image 2021-09-25 15:25:29 +08:00
Patrice Brend'amour 22f04125d6 added missing test 2021-09-22 08:24:24 +08:00
Patrice Brend'amour 4335d6fb82 applied requested changes 2021-09-22 08:24:24 +08:00
Patrice Brend'amour cd783f91d7 fixed tests 2021-09-22 08:24:24 +08:00
Patrice Brend'amour 80a691f084 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
2021-09-22 08:24:24 +08:00
Chai Feng 0150af87dc Add Github Actions to run unit tests 2021-09-13 13:46:10 +08:00
Chai Feng cc58088bc5 Update test/bach to the latest version 2021-09-12 08:52:29 +08:00
Chai Feng 2b4a44ff7a Update the vagrant box 2021-04-02 10:42:01 +08:00
Further fa5cec9dc5 Update README.md 2021-04-02 10:41:12 +08:00
Chai Feng a444fb9457 Update README.md, add a link to the docker image 2020-08-19 17:50:20 +08:00
Chai Feng 347bd313e6 Release Docker Image ufw-docker-agent:200812 2020-08-12 13:50:34 +08:00
Chai Feng e12810d055 Upgrade ufw-docker-agent image to Ubuntu 20.04 & Docker 19.03.12 2020-08-12 13:48:01 +08:00
Chai Feng 173f9659d7 Fix a variable undefined error 2020-08-12 13:48:01 +08:00
Chai Feng 3739f1396b Allow ufw to manage connections from docker containers 2020-08-12 13:48:01 +08:00
Chai Feng 9d7a654a17 Logging blocked connections 2020-08-12 13:48:01 +08:00
Chai Feng 4e16b4d359 Upgrade testing VM to Ubuntu 20.04 & Docker 19.03.11 2020-08-12 13:48:01 +08:00
Chai Feng 6bc7eed24c Forgot to init submodules 2020-08-12 13:48:01 +08:00
Chai Feng d9ee549565 Move fetching submdoule to Testing 2020-08-12 13:48:01 +08:00
Chai Feng 972716443f Set depth to 1 when fetch submodules 2020-08-12 13:48:01 +08:00
Chai Feng 409fb88131 Fetch submodules in Drone CI 2020-08-12 13:48:01 +08:00
Chai Feng 8521a6d1de Add .drone.yml 2020-08-12 13:48:01 +08:00
Chai Feng b7e1f8860e Using Docker 19.03.3, auto-restart webapp 2020-08-12 13:48:01 +08:00
Chai Feng 149e84e8af Update bach 2020-08-12 13:48:01 +08:00
Chai Feng 6d1c5a229b Add tests for ufw-docker--service-delete 2020-08-12 13:48:01 +08:00
Chai Feng 52fa5dc1e1 Add a test case for ufw-docker--get-env-list 2020-08-12 13:48:01 +08:00
Chai Feng 4e6d4332e0 Update readme, add build status 2020-08-12 13:48:01 +08:00
Chai Feng 67bfc771c6 Update travis ci 2020-08-12 13:48:01 +08:00
Chai Feng 95353863b1 Add testing in Vagrant 2020-08-12 13:48:01 +08:00
Chai Feng 36fec96d9f Update vm image to ubuntu 18.04 with docker 19.03 2020-08-12 13:48:01 +08:00
Chai Feng bac764e44e Add Tests for ufw-docker--service-allow 2020-08-12 13:48:01 +08:00
Chai Feng 9137695937 Fix a test case 2020-08-12 13:48:01 +08:00
Chai Feng 2d2b968024 Add tests for ufw-docker--service-allow, failed scenarios 2020-08-12 13:48:01 +08:00
Chai Feng 5be7dfb366 Add tests for ufw-docker--get-service-{id,name} 2020-08-12 13:48:01 +08:00
Chai Feng 6291251734 Add a new test suite for ufw-docker--service commands 2020-08-12 13:48:01 +08:00
Chai Feng b701f6baee Add two tests for ufw-docker--delete 2020-08-12 13:48:01 +08:00
Chai Feng bcfca79758 Add a test case for ufw-docker--list-number 2020-08-12 13:48:01 +08:00
Chai Feng 491324a1f4 Add tests for ufw-docker--list 2020-08-12 13:48:01 +08:00
Chai Feng 1c6dd31de7 Add tests for ufw-docker--instance-name 2020-08-12 13:48:01 +08:00
Chai Feng 8444f87760 Update Bach, add a test case for ufw-docker--allow 2020-08-12 13:48:01 +08:00
Chai Feng aef8f7df82 Run unit testing in Vagrant 2020-08-12 13:48:01 +08:00
Chai Feng 3893c86236 Add test cases for ufw-docker--add-rule 2020-08-12 13:48:01 +08:00
Chai Feng 3598011787 Add a test case for ufw-docker--allow, the proto doesn't match 2020-08-12 13:48:01 +08:00
Chai Feng c79d1ad1a6 Add test cases for ufw-docker--allow 2020-08-12 13:48:01 +08:00
Chai Feng b37d892048 Update slack token 2020-08-12 13:48:01 +08:00
Chai Feng 6c695ed208 Add travis-ci 2020-08-12 13:48:01 +08:00
Chai Feng 22463cbf6e Update bash to 0.2.6 2020-08-12 13:48:01 +08:00
Chai Feng df963fdb31 Add Bach Testing Framework and several basic test cases 2020-08-12 13:48:01 +08:00
Chai Feng 05006bf638 set -exu 2020-08-12 13:48:01 +08:00
Chai Feng 82479773c5 fix sudoers 2020-08-12 13:48:01 +08:00
Chai Feng 0df69e787c change default nic type 2020-08-12 13:48:01 +08:00
Chai Feng f3cf64d598 Enable debug in Vagrant 2020-08-12 13:48:01 +08:00
Chai Feng 0c3da26e83 use local build image in vagrant 2020-08-12 13:48:01 +08:00
Chai Feng 665d3c5a2a Including license and readme in the docker image 2020-08-12 13:48:01 +08:00
Chai Feng 0ad2ebbd47 build ufw-docker-agent image in Vagrant 2020-08-12 13:48:01 +08:00
Chai Feng ab03a6aa64 Always using english to avoid multilingual issue 2020-01-09 13:05:18 +08:00
Chai Feng e2e6d76187 Merge pull request #27 from tobiashe/master
fixing issue \#25 Doesn't with other locale
2020-01-09 12:14:14 +08:00
Artem Sherstnev 86568044ed fixing issue \#25 Doesn't with other locale 2020-01-06 16:14:48 +03:00
Chai Feng 3d2259125b Merge pull request #13 from kronthto/patch-1
ufw reload
2019-06-03 13:16:47 +08:00
Tobias Kronthaler d02334ffc1 Update section of applying rule changes
Add `ufw reload` hint and fix formatting
2019-06-02 15:06:09 +02:00
Chai Feng 2545d2977b allow re-provision for vagrant 2018-12-01 12:32:33 +08:00
Chai Feng 8e9a7c20f5 Add a logging rule in firewall rules 2018-12-01 12:31:59 +08:00
Chai Feng d4fbb6685c create the symbolic link if not exist 2018-12-01 12:08:20 +08:00
Chai Feng 10ce4a8dbe fix wrong check command in Vagrantfile 2018-12-01 12:08:01 +08:00
Chai Feng da0738d8c7 include license and readme in the docker image 2018-11-25 13:10:38 +08:00
Chai Feng 2fdeecb4cb update readme, to allow a bigger port range to receive DNS packages 2018-11-25 13:07:59 +08:00
Chai Feng 768d7b0577 refactor check-install function 2018-11-24 13:48:31 +08:00
Chai Feng d831601aa9 update readme, ufw-docker check 2018-11-23 22:10:56 +08:00
Chai Feng ef16648ecb add check sub-command, for checking installation of firewall rules 2018-11-23 22:10:06 +08:00
Chai Feng 766aa9c727 fix receiving DNS packages 2018-11-23 16:46:19 +08:00
Chai Feng 34e84c01b3 try it out 2018-10-07 08:52:47 +08:00
Chai Feng ce5010172a add Vagrantfile, for testing this script 2018-10-07 08:34:16 +08:00
Chai Feng 099f49653c extract function "is-installed" 2018-10-06 21:40:43 +08:00
Chai Feng 1fe82b9560 typo 2018-10-06 16:21:22 +08:00
14 changed files with 1450 additions and 68 deletions
+2
View File
@@ -1,3 +1,5 @@
* *
!LICENSE
!README.md
!ufw-docker !ufw-docker
!docker-entrypoint.sh !docker-entrypoint.sh
+15
View File
@@ -0,0 +1,15 @@
---
kind: pipeline
type: docker
name: default
steps:
- name: Fetch Submodules
image: alpine/git
commands:
- git submodule update --init --recursive --depth=1 --remote
- name: Testing
image: ubuntu:bionic
commands:
- ./test.sh
+16
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
.vagrant
+3
View File
@@ -0,0 +1,3 @@
[submodule "test/bach"]
path = test/bach
url = https://github.com/bach-sh/bach.git
+8
View File
@@ -0,0 +1,8 @@
language: bash
os: linux
dist: trusty
script:
- bash test.sh
notifications:
slack:
secure: pbbwN/dxrXXvQnJM5YhLt3A7t7ptvm5G3yQOimqBJVlLY7dJdPcUy6agntyLQ5cf+CHKVgMreu5m7qdTlSUeypNSi5+gbfNQD70l5TPG/ClqN/GnaNlrv4Y9ugL7kUWBqtMZSCwHHCy3Omv+oYjBixonJBfDMr0mn6ShkHRpgmjjUtByHhGy5gyNlKjoxB+04AnLkuIiz9mRUIkgGeeIcIkS8pE4E69Mh/K78h0oCZbQF1H4Eu22nOCdQhWH5StT0G+/pDqHGP4J8mDo3NVNMyySFRY0lSwUj2OUnX+VVK9RWUicaozCgob4Oi3Pf+5bNcxTdo2ntI/e0fEcpIqiSHOy/iLBrQMGmZY7aoVNG7IWQwM2Zt6wXORcYl6l2XMOVuFx+h9PQd+nol9Eh9JxGsQjrfdQ8rNK8DkdNat0axpex3w3PWpbFRtQJ3A21ixqINsKCZC0y5vO0LF0ttsJEf9QEJrYRlA8VOw79NnCE3PimACnb0UU2cy91HmLcR2OvXqNiS0TZWS0kEajpW9BuqszNPnMrpLi9t7kHTSFjGx1Nb6XXUBG2lmoiXVNqutq+0zCEa8otObjZ0igQ6Kb2joJfbwSSWXNzDtp+jRmxQacqmXwvByz2BI37xnCs4KP0VYM37r0BmVxpBQlyFreoB7PRyezYbQSkaq2bahMLjA=
+16 -7
View File
@@ -1,13 +1,20 @@
FROM ubuntu:18.04 FROM ubuntu:20.04
ARG docker_version="20.10.17"
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends apt-transport-https \ && apt-get install -y ca-certificates curl gnupg lsb-release \
ca-certificates curl software-properties-common gnupg dirmngr \ && mkdir -p /etc/apt/keyrings \
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 \ && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg]" \
$(lsb_release -cs) stable" \ "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 update \
&& apt-get install -y --no-install-recommends ufw "docker-ce=18.06.1~*" \ && 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}~*" ) \
&& locale-gen en_US.UTF-8 \
&& apt-get clean autoclean \ && apt-get clean autoclean \
&& apt-get autoremove --yes \ && apt-get autoremove --yes \
&& rm -rf /var/lib/{apt,dpkg,cache,log}/ && rm -rf /var/lib/{apt,dpkg,cache,log}/
@@ -17,3 +24,5 @@ ADD ufw-docker docker-entrypoint.sh /usr/bin/
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"] ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
CMD ["start"] CMD ["start"]
ADD LICENSE README.md /
+135 -18
View File
@@ -1,6 +1,9 @@
To Fix The Docker and UFW Security Flaw Without Disabling Iptables 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)](https://hub.docker.com/r/chaifeng/ufw-docker-agent)
- [English](#tldr) - [English](#tldr)
- [中文](#太长不想读) - [中文](#太长不想读)
@@ -67,25 +70,32 @@ Modify the UFW configuration file `/etc/ufw/after.rules` and add the following r
# BEGIN UFW AND DOCKER # BEGIN UFW AND DOCKER
*filter *filter
:ufw-user-forward - [0:0] :ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0] :DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j DROP -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 192.168.0.0/16
-A DOCKER-USER -j DROP -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 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -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 192.168.0.0/16
-A DOCKER-USER -j DROP -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 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN -A DOCKER-USER -j RETURN
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT COMMIT
# END UFW AND DOCKER # END UFW AND DOCKER
Using command `sudo systemctl restart ufw** to restart UFW after changing the file. Now the public network can't access any published docker ports, the container and the private network can visit each other normally, and the containers can also access the external network from inside. **There may be some unknown reasons cause the UFW rules will not take effect after restart UFW, please reboot servers. ** Using command `sudo systemctl restart ufw` or `sudo ufw reload` to restart UFW after changing the file. Now the public network can't access any published docker ports, the container and the private network can visit each other normally, and the containers can also access the external network from inside. **There may be some unknown reasons cause the UFW rules will not take effect after restart UFW, please reboot servers.**
If you want to allow public networks to access the services provided by the Docker container, for example, the service port of a container is `80`. Run the following command to allow the public networks to access this service: If you want to allow public networks to access the services provided by the Docker container, for example, the service port of a container is `80`. Run the following command to allow the public networks to access this service:
@@ -119,6 +129,10 @@ The following rules allow UFW to manage whether the public networks are allowed
-A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j ufw-user-forward
For example, we want to block all outgoing connections from inside a container whose IP address is 172.17.0.9 which means to block this container to access internet or external networks. Using the following command:
ufw route deny from 172.17.0.9 to any
The following rules block connection requests initiated by all public networks, but allow internal networks to access external networks. For TCP protocol, it prevents from actively establishing a TCP connection from public networks. For UDP protocol, all accesses to ports which is less then 32767 are blocked. Why is this port? Since the UDP protocol is stateless, it is not possible to block the handshake signal that initiates the connection request as TCP does. For GNU/Linux we can find the local port range in the file `/proc/sys/net/ipv4/ip_local_port_range`. The default range is `32768 60999`. When accessing a UDP protocol service from a running container, the local port will be randomly selected one from the port range, and the server will return the data to this random port. Therefore, we can assume that the listening port of the UDP protocol inside all containers are less then `32768`. This is the reason that we don't want public networks to access the UDP ports that less then `32768`. The following rules block connection requests initiated by all public networks, but allow internal networks to access external networks. For TCP protocol, it prevents from actively establishing a TCP connection from public networks. For UDP protocol, all accesses to ports which is less then 32767 are blocked. Why is this port? Since the UDP protocol is stateless, it is not possible to block the handshake signal that initiates the connection request as TCP does. For GNU/Linux we can find the local port range in the file `/proc/sys/net/ipv4/ip_local_port_range`. The default range is `32768 60999`. When accessing a UDP protocol service from a running container, the local port will be randomly selected one from the port range, and the server will return the data to this random port. Therefore, we can assume that the listening port of the UDP protocol inside all containers are less then `32768`. This is the reason that we don't want public networks to access the UDP ports that less then `32768`.
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
@@ -130,6 +144,12 @@ The following rules block connection requests initiated by all public networks,
-A DOCKER-USER -j RETURN -A DOCKER-USER -j RETURN
If a docker container doesn't follow the OS's settings when receiving data, that is to say, the minimal port number less than `32768`. For example, we have a Dnsmasq container. The minimal port number that Dnsmasq uses for receiving data is `1024`. We can use the following command to allow a bigger port range used for receiving DNS packages.
ufw route allow proto udp from any port 53 to any port 1024:65535
Because DNS is a very common service, so there is already a firewall rule to allow a bigger port range to receive DNS packages.
## The reason for choosing `ufw-user-forward`, not `ufw-user-input` ## The reason for choosing `ufw-user-forward`, not `ufw-user-input`
### using `ufw-user-input` ### using `ufw-user-input`
@@ -190,7 +210,7 @@ Download `ufw-docker` script
sudo wget -O /usr/local/bin/ufw-docker \ sudo wget -O /usr/local/bin/ufw-docker \
https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
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` Then using the following command to modify the `after.rules` file of `ufw`
@@ -204,8 +224,8 @@ This command does the following things:
We can only use this script on manager nodes to manage firewall rules when using in Swarm mode. We can only use this script on manager nodes to manage firewall rules when using in Swarm mode.
- Modify all `after.rules` files on all nodes, including managers and workers - Modifying all `after.rules` files on all nodes, including managers and workers
- Deploy this script on mananger nodes - Deploying this script on manager nodes
Running in Docker Swarm mode, this script will add a global service `ufw-docker-agent`. The image [chaifeng/ufw-docker-agent](https://hub.docker.com/r/chaifeng/ufw-docker-agent/) is also automatically built from this project. Running in Docker Swarm mode, this script will add a global service `ufw-docker-agent`. The image [chaifeng/ufw-docker-agent](https://hub.docker.com/r/chaifeng/ufw-docker-agent/) is also automatically built from this project.
@@ -215,6 +235,14 @@ Show help
ufw-docker help ufw-docker help
Check the installation of firewall rules in UFW configurations
ufw-docker check
Update UFW configurations, add the necessary firewall rules
ufw-docker install
Show the current firewall allowed forward rules Show the current firewall allowed forward rules
ufw-docker status ufw-docker status
@@ -231,6 +259,10 @@ Expose the `443` port of the container `httpd` and the protocol is `tcp`
ufw-docker allow httpd 443/tcp ufw-docker allow httpd 443/tcp
Expose the `443` port of the container `httpd` and the protocol is `tcp` and the network is `foobar-external-network` when the container `httpd` is attached to multiple networks
ufw-docker allow httpd 443/tcp foobar-external-network
Expose all published ports of the container `httpd` Expose all published ports of the container `httpd`
ufw-docker allow httpd ufw-docker allow httpd
@@ -255,6 +287,34 @@ Remove rules from all nodes related to the service `web`
ufw-docker service delete allow web ufw-docker service delete allow web
### Try it out
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
vagrant up
Log into the master node
vagrant ssh master
After logging in, create a `web` service
docker service create --name web --publish 8080:80 httpd:alpine
We shouldn't visit this `web` service from our host
curl -v http://192.168.56.131:8080
On the master node, run the command to allow the public access port `80` of the `web` service.
sudo ufw-docker service allow web 80
We can access the `web` service from our host now
curl "http://192.168.56.13{0,1,2}:8080"
## Discussions ## Discussions
- [What is the best practice of docker + ufw under Ubuntu - Stack Overflow](https://stackoverflow.com/questions/30383845/what-is-the-best-practice-of-docker-ufw-under-ubuntu/51741599#comment91451547_51741599) - [What is the best practice of docker + ufw under Ubuntu - Stack Overflow](https://stackoverflow.com/questions/30383845/what-is-the-best-practice-of-docker-ufw-under-ubuntu/51741599#comment91451547_51741599)
@@ -322,21 +382,28 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
# BEGIN UFW AND DOCKER # BEGIN UFW AND DOCKER
*filter *filter
:ufw-user-forward - [0:0] :ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0] :DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j DROP -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 192.168.0.0/16
-A DOCKER-USER -j DROP -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 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -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 192.168.0.0/16
-A DOCKER-USER -j DROP -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 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN -A DOCKER-USER -j RETURN
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT COMMIT
# END UFW AND DOCKER # END UFW AND DOCKER
@@ -374,6 +441,10 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
-A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j ufw-user-forward
例如,我们要阻止一个 IP 地址为 172.17.0.9 的容器内的所有对外连接,也就是阻止该容器访问外部网络,使用下列命令
ufw route deny from 172.17.0.9 to any
下面的规则阻止了所有外部网络发起的连接请求,但是允许内部网络访问外部网络。对于 TCP 协议,是阻止了从外部网络主动建立 TCP 连接。对于 UDP,是阻止了所有小余端口 `32767` 的访问。为什么是这个端口的?由于 UDP 协议是无状态的,无法像 TCP 那样阻止发起建立连接请求的握手信号。在 GNU/Linux 上查看文件 `/proc/sys/net/ipv4/ip_local_port_range` 可以看到发出 TCP/UDP 数据后,本地源端口的范围,默认为 `32768 60999`。当从一个运行的容器对外访问一个 UDP 协议的服务时,本地端口将会从这个端口范围里面随机选择一个,服务器将会把数据返回到这个随机端口上。所以,我们可以假定所有容器内部的 UDP 协议的监听端口都小余 `32768`,不允许外部网络主动连接小余 `32768` 的 UDP 端口。 下面的规则阻止了所有外部网络发起的连接请求,但是允许内部网络访问外部网络。对于 TCP 协议,是阻止了从外部网络主动建立 TCP 连接。对于 UDP,是阻止了所有小余端口 `32767` 的访问。为什么是这个端口的?由于 UDP 协议是无状态的,无法像 TCP 那样阻止发起建立连接请求的握手信号。在 GNU/Linux 上查看文件 `/proc/sys/net/ipv4/ip_local_port_range` 可以看到发出 TCP/UDP 数据后,本地源端口的范围,默认为 `32768 60999`。当从一个运行的容器对外访问一个 UDP 协议的服务时,本地端口将会从这个端口范围里面随机选择一个,服务器将会把数据返回到这个随机端口上。所以,我们可以假定所有容器内部的 UDP 协议的监听端口都小余 `32768`,不允许外部网络主动连接小余 `32768` 的 UDP 端口。
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
@@ -385,6 +456,12 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
-A DOCKER-USER -j RETURN -A DOCKER-USER -j RETURN
如果一个容器在接受数据的时候,端口号没有遵循操作系统的设定,也就是说最小端口号要小余 `32768`。比如运行了一个 Dnsmasq 的容器,Dnsmasq 用于接受数据的最小端口号默认是 `1024`。那可以用下面的命令来允许 Dnsmasq 这个容器使用一个更大的端口范围来接受数据。
ufw route allow proto udp from any port 53 to any port 1024:65535
因为 DNS 是一个非常常见的服务,所以已经有一条规则用于允许使用一个更大的端口范围来接受 DNS 数据包
### 选择 `ufw-user-forward` 而不是 `ufw-user-input` 的原因 ### 选择 `ufw-user-forward` 而不是 `ufw-user-input` 的原因
#### 使用 `ufw-user-input` #### 使用 `ufw-user-input`
@@ -466,6 +543,14 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
ufw-docker help ufw-docker help
检查 UFW 配置文件中防火墙规则的安装
ufw-docker check
更新 UFW 的配置文件,添加必要的防火墙规则
ufw-docker install
显示当前防火墙允许的转发规则 显示当前防火墙允许的转发规则
ufw-docker status ufw-docker status
@@ -482,6 +567,10 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
ufw-docker allow httpd 443/tcp ufw-docker allow httpd 443/tcp
如果容器 `httpd` 绑定到多个网络上,暴露其 `443` 端口,协议为 `tcp`,网络为 `foobar-external-network`
ufw-docker allow httpd 443/tcp foobar-external-network
把容器 `httpd` 的所有映射端口都暴露出来 把容器 `httpd` 的所有映射端口都暴露出来
ufw-docker allow httpd ufw-docker allow httpd
@@ -506,6 +595,34 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
ufw-docker service delete allow web ufw-docker service delete allow web
### 试试
我们使用 [Vagrant](https://www.vagrantup.com/) 来创建一个本地的测试环境。
运行下面的命令来创建 1 个 master 节点和 2 个 workder 节点
vagrant up
登录到 master 节点
vagrant ssh master
登录后,创建 `web` 服务
docker service create --name web --publish 8080:80 httpd:alpine
我们应该无法从我们的主机上访问这个 `web` 服务
curl -v http://192.168.56.131:8080
在 master 节点上,运行下面的命令来允许公共访问 `web` 服务端 `80` 端口。
sudo ufw-docker service allow web 80
现在我们可以在我们的主机上访问这个 `web` 服务了
curl "http://192.168.56.13{0,1,2}:8080"
## 讨论 ## 讨论
- [What is the best practice of docker + ufw under Ubuntu - Stack Overflow](https://stackoverflow.com/questions/30383845/what-is-the-best-practice-of-docker-ufw-under-ubuntu/51741599#comment91451547_51741599) - [What is the best practice of docker + ufw under Ubuntu - Stack Overflow](https://stackoverflow.com/questions/30383845/what-is-the-best-practice-of-docker-ufw-under-ubuntu/51741599#comment91451547_51741599)
Vendored
+237
View File
@@ -0,0 +1,237 @@
# frozen_string_literal: true
# -*- mode: ruby -*-
# vi: set ft=ruby :
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":""}"
#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'
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
set -eu
if [[ ! -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
echo '}' >> /etc/docker/daemon.json
if type systemctl &>/dev/null; then
systemctl restart docker
else
service docker restart
fi
fi
SHELL
config.vm.provision 'ufw-docker', type: 'shell', inline: <<-SHELL
set -euo 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'
/vagrant/ufw-docker install
sed -i -e 's,192\.168\.0\.0/16,#{ip_prefix}.128/28,' /etc/ufw/after.rules
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] '
}
SHELL
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: "#{master_ip_address}"
master.vm.provision "unit-testing", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail
/vagrant/test.sh
SHELL
master.vm.provision "docker-registry", preserve_order: true, type: 'docker' do |d|
d.run "registry",
image: "registry:2",
args: "-p 5000:5000",
restart: "always",
daemonize: true
end
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
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}-${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
echo "Defaults env_keep += DEBUG" >> /etc/sudoers.d/98_ufw-docker
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 "#{master_ip_address}"
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
docker build -t #{private_registry}/chaifeng/hostname-webapp - <<\\DOCKERFILE
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
ENTRYPOINT ["/entrypoint.sh"]
CMD ["httpd-foreground"]
DOCKERFILE
docker push #{private_registry}/chaifeng/hostname-webapp
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"
port="${name#*:}"
if docker inspect "$webapp" &>/dev/null; then docker rm -f "$webapp"; fi
docker run -d --restart unless-stopped --name "$webapp" \
-p "$port:80" --env name="$webapp" #{private_registry}/chaifeng/hostname-webapp
sleep 1
done
ufw-docker allow public_webapp
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
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", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail
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
done
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
ufw-docker service allow public_multiport 80/tcp
ufw-docker service allow public_multiport 8080/tcp
SHELL
end
1.upto 2 do |ip|
config.vm.define "node#{ip}" do | node |
node.vm.hostname = "node#{ip}"
node.vm.network "private_network", ip: "#{ip_prefix}.#{ 130 + ip }"
node.vm.provision "swarm-join", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail
docker info | fgrep 'Swarm: active' && exit 0
[[ -f /vagrant/.vagrant/docker-join-token ]] &&
docker swarm join --token "$(</vagrant/.vagrant/docker-join-token)" #{ip_prefix}.130:2377
SHELL
end
end
config.vm.define "external" do |external|
external.vm.hostname = "external"
external.vm.network "private_network", ip: "#{ip_prefix}.127"
external.vm.provision "testing", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail
set -x
server="http://#{ip_prefix}.130"
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"
test-webapp "$server:17070" # multiple networks app
! test-webapp "$server:7000" # internal multiple networks app
test-webapp "$server:29090"
! test-webapp "$server:9000"
test-webapp "$server:40080"
test-webapp "$server:48080"
! test-webapp "$server:47000"
echo "====================="
echo " TEST DONE "
echo "====================="
SHELL
end
end
Executable
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -uo pipefail
function out() {
printf "\n\e[1;37;497;m%s\e[0;m\n" "$@"
} >&2
function err() {
printf "\n\e[1;37;41;m%s\e[0;m\n\n" "$@"
} >&2
retval=0
cd "$(dirname "${BASH_SOURCE}")"
for file in test/*.test.sh; do
out "Running $file"
if grep -E "^[[:blank:]]*BACH_TESTS=.+" "$file"; then
err "Found defination of BACH_TESTS in $file"
retval=1
fi
bash "$file" || retval=1
done
if [[ "$retval" -ne 0 ]]; then
err "Test failed!"
fi
exit "$retval"
Submodule
+1
Submodule test/bach added at 447edb60db
+289
View File
@@ -0,0 +1,289 @@
#!/usr/bin/env bash
set -euo pipefail
working_dir="$(cd "$(dirname "$BASH_SOURCE")"; pwd -P)"
source "$working_dir"/bach/bach.sh
@setup {
set -euo pipefail
ufw_docker_agent=ufw-docker-agent
ufw_docker_agent_image=chaifeng/ufw-docker-agent:181005
}
@setup-test {
@mocktrue ufw status
@mocktrue grep -Fq "Status: active"
@ignore remove_blank_lines
@ignore echo
@ignore err
DEFAULT_PROTO=tcp
GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+"
DEBUG=false
}
function die() {
return 1
}
function load-ufw-docker-function() {
set -euo pipefail
@load_function "$working_dir/../ufw-docker" "$1"
}
test-ufw-docker--service-not-parameters() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service
}
test-ufw-docker--service-not-parameters-assert() {
ufw-docker--help
}
test-ufw-docker--service-allow() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service allow
}
test-ufw-docker--service-allow-assert() {
@do-nothing
@fail
}
test-ufw-docker--service-allow-webapp() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service allow webapp
}
test-ufw-docker--service-allow-webapp-assert() {
#ufw-docker--service-allow webapp "" ""
@do-nothing
@fail
}
test-ufw-docker--service-allow-webapp-80tcp() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service allow webapp 80/tcp
}
test-ufw-docker--service-allow-webapp-80tcp-assert() {
ufw-docker--service-allow webapp 80/tcp
}
test-ufw-docker--service-delete-deny() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service delete deny
}
test-ufw-docker--service-delete-deny-assert() {
@do-nothing
@fail
}
test-ufw-docker--service-delete-allow-no-service() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service delete allow
}
test-ufw-docker--service-delete-allow-no-service-assert() {
@do-nothing
@fail
}
test-ufw-docker--service-delete-allow-webapp() {
load-ufw-docker-function ufw-docker--service
ufw-docker--service delete allow webapp
}
test-ufw-docker--service-delete-allow-webapp-assert() {
ufw-docker--service-delete webapp
}
test-ufw-docker--get-service-id() {
load-ufw-docker-function ufw-docker--get-service-id
ufw-docker--get-service-id database
}
test-ufw-docker--get-service-id-assert() {
docker service inspect database --format "{{.ID}}"
}
test-ufw-docker--get-service-name() {
load-ufw-docker-function ufw-docker--get-service-name
ufw-docker--get-service-name database
}
test-ufw-docker--get-service-name-assert() {
docker service inspect database --format "{{.Spec.Name}}"
}
test-ufw-docker--service-allow-invalid-port-syntax() {
@mockfalse grep -E '^[0-9]+(/(tcp|udp))?$'
load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow webapp invalid-port
}
test-ufw-docker--service-allow-invalid-port-syntax-assert() {
@do-nothing
@fail
}
test-ufw-docker--service-allow-an-non-existed-service() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id web404 === @stdout ""
load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow web404 80/tcp
}
test-ufw-docker--service-allow-an-non-existed-service-assert() {
@do-nothing
@fail
}
test-ufw-docker--service-allow-a-service-without-ports-published() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id private-web === @stdout abcd1234
@mock ufw-docker--get-service-name private-web === @stdout private-web
@mock docker service inspect private-web \
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}' === @stdout ""
load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow private-web 80/tcp
}
test-ufw-docker--service-allow-a-service-without-ports-published-assert() {
@do-nothing
@fail
}
test-ufw-docker--service-allow-a-service-while-agent-not-running() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id webapp === @stdout abcd1234
@mock ufw-docker--get-service-name webapp === @stdout webapp
@mock docker service inspect webapp \
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}' \
=== @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
@mockfalse docker service inspect ufw-docker-agent
load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow webapp 80/tcp
}
test-ufw-docker--service-allow-a-service-while-agent-not-running-assert() {
docker service create --name ufw-docker-agent --mode global \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/ufw,target=/etc/ufw,readonly=true \
--env ufw_docker_agent_image="chaifeng/ufw-docker-agent:181005" \
--env DEBUG="false" \
--env "ufw_public_abcd1234=webapp/80/tcp" \
"chaifeng/ufw-docker-agent:181005"
}
test-ufw-docker--service-allow-a-service-add-new-env() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id webapp === @stdout abcd1234
@mock ufw-docker--get-service-name webapp === @stdout webapp
@mock docker service inspect webapp \
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}' \
=== @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
@mocktrue docker service inspect ufw-docker-agent
@mock ufw-docker--get-env-list === @stdout "abcd1234 webapp/80/tcp"
load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow webapp 80/tcp
}
test-ufw-docker--service-allow-a-service-add-new-env-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:181005" \
--env-add DEBUG="false" \
--env-add "ufw_public_abcd1234=webapp/80/tcp" \
--image "chaifeng/ufw-docker-agent:181005" \
ufw-docker-agent
}
test-ufw-docker--service-allow-a-service-update-a-env() {
@mocktrue grep -E '^[0-9]+(/(tcp|udp))?$'
@mock ufw-docker--get-service-id webapp === @stdout abcd1234
@mock ufw-docker--get-service-name webapp === @stdout webapp
@mock docker service inspect webapp \
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}' \
=== @stdout "53 53/udp" "80 80/tcp" "8080 8080/tcp"
@mocktrue docker service inspect ufw-docker-agent
@mock ufw-docker--get-env-list === @stdout "a_different_id webapp/80/tcp"
load-ufw-docker-function ufw-docker--service-allow
ufw-docker--service-allow webapp 80/tcp
}
test-ufw-docker--service-allow-a-service-update-a-env-assert() {
docker service update --update-parallelism=0 \
--env-add ufw_docker_agent_image="chaifeng/ufw-docker-agent:181005" \
--env-add DEBUG="false" \
--env-add "ufw_public_abcd1234=webapp/80/tcp" \
--env-rm "ufw_public_a_different_id" \
--image "chaifeng/ufw-docker-agent:181005" \
ufw-docker-agent
}
test-ufw-docker--get-env-list() {
@mock docker service inspect ufw-docker-agent \
--format '{{range $k,$v := .Spec.TaskTemplate.ContainerSpec.Env}}{{ $v }}{{"\n"}}{{end}}' \
=== @stdout \
"ufw_docker_agent_image=192.168.56.130:5000/chaifeng/ufw-docker-agent:test" \
"DEBUG=true" \
"ufw_public_zv6esvmwnmmgnlauqn7m77jo4=webapp/9090/tcp" \
"OTHER_ENV=blabla"
@mock sed -e '/^ufw_public_/!d' \
-e 's/^ufw_public_//' \
-e 's/=/ /' === @real sed -e '/^ufw_public_/!d' \
-e 's/^ufw_public_//' \
-e 's/=/ /'
load-ufw-docker-function ufw-docker--get-env-list
ufw-docker--get-env-list
}
test-ufw-docker--get-env-list-assert() {
@stdout "zv6esvmwnmmgnlauqn7m77jo4 webapp/9090/tcp"
}
test-ufw-docker--service-delete-no-matches() {
@mock ufw-docker--get-env-list === @stdout "ffff111 foo/80/tcp" "eeee2222 bar/53/udp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp
}
test-ufw-docker--service-delete-no-matches-assert() {
@do-nothing
@fail
}
test-ufw-docker--service-delete-matches() {
@mock ufw-docker--get-env-list === @stdout "ffff111 foo/80/tcp" "eeee2222 bar/53/udp" "abcd1234 webapp/5000/tcp"
load-ufw-docker-function ufw-docker--service-delete
ufw-docker--service-delete webapp
}
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" \
--image "${ufw_docker_agent_image}" \
"${ufw_docker_agent}"
}
+567
View File
@@ -0,0 +1,567 @@
#!/usr/bin/env bash
set -euo pipefail
working_dir="$(cd "$(dirname "$BASH_SOURCE")"; pwd -P)"
source "$working_dir"/bach/bach.sh
@setup {
set -euo pipefail
}
@setup-test {
@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() {
@source <(@sed -n '/^# __main__$/,$p' "$working_dir/../ufw-docker") "$@"
}
function load-ufw-docker-function() {
set -euo pipefail
@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
}
test-ufw-docker-help-assert() {
ufw-docker--help
}
test-ufw-docker-without-parameters() {
ufw-docker
}
test-ufw-docker-without-parameters-assert() {
test-ufw-docker-help-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, or mismatched iptables legacy/nf_tables, current iptables v1.8.4 (legacy)"
ufw-docker--help
}
test-ufw-docker-status() {
ufw-docker status
}
test-ufw-docker-status-assert() {
ufw-docker--status
}
test-ufw-docker-install() {
ufw-docker install
}
test-ufw-docker-install-assert() {
ufw-docker--install
}
test-ufw-docker-check() {
ufw-docker check
}
test-ufw-docker-check-assert() {
ufw-docker--check
}
test-ufw-docker-service() {
ufw-docker service allow httpd
}
test-ufw-docker-service-assert() {
ufw-docker--service allow httpd
}
test-ufw-docker-raw-command() {
ufw-docker raw-command status
}
test-ufw-docker-raw-command-assert() {
ufw-docker--raw-command status
}
test-ufw-docker-add-service-rule() {
ufw-docker add-service-rule httpd 80/tcp
}
test-ufw-docker-add-service-rule-assert() {
ufw-docker--add-service-rule httpd 80/tcp
}
test-ASSERT-FAIL-ufw-docker-delete-must-have-parameters() {
ufw-docker delete
}
test-ASSERT-FAIL-ufw-docker-list-must-have-parameters() {
ufw-docker list
}
test-ASSERT-FAIL-ufw-docker-allow-must-have-parameters() {
ufw-docker allow
}
test-ASSERT-FAIL-ufw-docker-delete-httpd-but-it-doesnt-exist() {
@mockfalse ufw-docker--instance-name httpd
ufw-docker delete httpd
}
test-ASSERT-FAIL-ufw-docker-list-httpd-but-it-doesnt-exist() {
@mockfalse ufw-docker--instance-name httpd
ufw-docker list httpd
}
test-ASSERT-FAIL-ufw-docker-allow-httpd-but-it-doesnt-exist() {
@mockfalse ufw-docker--instance-name httpd
ufw-docker allow httpd
}
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-allow-httpd() {
@mock ufw-docker--instance-name httpd === @stdout httpd-container-name
ufw-docker allow httpd
}
test-ufw-docker-allow-httpd-assert() {
ufw-docker--allow httpd-container-name "" tcp ""
}
test-ufw-docker-allow-httpd-80() {
@mock ufw-docker--instance-name httpd === @stdout httpd-container-name
ufw-docker allow httpd 80
}
test-ufw-docker-allow-httpd-80-assert() {
ufw-docker--allow httpd-container-name 80 tcp ""
}
test-ufw-docker-allow-httpd-80tcp() {
@mock ufw-docker--instance-name httpd === @stdout httpd-container-name
ufw-docker allow httpd 80/tcp
}
test-ufw-docker-allow-httpd-80tcp-assert() {
ufw-docker--allow httpd-container-name 80 tcp ""
}
test-ufw-docker-allow-httpd-80udp() {
@mock ufw-docker--instance-name httpd === @stdout httpd-container-name
ufw-docker allow httpd 80/udp
}
test-ufw-docker-allow-httpd-80udp-assert() {
ufw-docker--allow httpd-container-name 80 udp ""
}
test-ASSERT-FAIL-ufw-docker-allow-httpd-INVALID-port() {
@mock ufw-docker--instance-name httpd === @stdout httpd-container-name
@mock die 'invalid port syntax: "invalid".' === exit 1
ufw-docker allow httpd invalid
}
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 ""
}
test-ASSERT-FAIL-ufw-docker-delete-only-supports-allowed-rules() {
@mock ufw-docker--instance-name httpd === @stdout httpd-container-name
ufw-docker delete non-allow
}
test-ASSERT-FAIL-ufw-docker-delete-only-supports-allowed-rules-assert() {
die "\"delete\" command only support removing allowed rules"
}
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 $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
}
test-ufw-docker--allow-instance-not-found() {
setup-ufw-docker--allow
@mockfalse docker inspect invalid-instance
@mockfalse die "Docker instance \"invalid-instance\" doesn't exist."
ufw-docker--allow invalid-instance 80 tcp
}
test-ufw-docker--allow-instance-not-found-assert() {
@do-nothing
@fail
}
test-ufw-docker--allow-instance-but-the-port-not-match() {
setup-ufw-docker--allow
ufw-docker--allow instance-name 80 tcp
}
test-ufw-docker--allow-instance-but-the-port-not-match-assert() {
@do-nothing
@fail
}
test-ufw-docker--allow-instance-but-the-proto-not-match() {
setup-ufw-docker--allow
ufw-docker--allow instance-name 5353 tcp
}
test-ufw-docker--allow-instance-but-the-proto-not-match-assert() {
@do-nothing
@fail
}
test-ufw-docker--allow-instance-and-match-the-port() {
setup-ufw-docker--allow
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 default
}
test-ufw-docker--allow-instance-all-published-port() {
setup-ufw-docker--allow
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 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
}
test-ufw-docker--allow-instance-all-published-tcp-port() {
setup-ufw-docker--allow
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 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 ""
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-with-network-assert() {
ufw route allow proto tcp from any to 172.18.0.4 port 5000 comment "allow webapp 5000/tcp default"
}
test-ufw-docker--add-rule-modify-an-existing-rule() {
@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 default
}
test-ufw-docker--add-rule-modify-an-existing-rule-assert() {
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 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"
@mocktrue grep "^Skipping"
load-ufw-docker-function ufw-docker--add-rule
ufw-docker--add-rule webapp 172.18.0.4 5000 tcp ""
}
test-ufw-docker--add-rule-skip-an-existing-rule-assert() {
@do-nothing
}
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"
@mockfalse grep "^Skipping"
load-ufw-docker-function ufw-docker--add-rule
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 route allow proto tcp from any to 172.18.0.4 comment "allow webapp"
}
test-ufw-docker--instance-name-found-a-name() {
@mock docker inspect --format="{{.Name}}" foo
@mock sed -e 's,^/,,'
@mockfalse grep "^$GREP_REGEXP_INSTANCE_NAME\$"
@mock echo -n foo
load-ufw-docker-function ufw-docker--instance-name
ufw-docker--instance-name foo
}
test-ufw-docker--instance-name-found-a-name-assert() {
docker inspect --format="{{.Name}}" foo
echo -n foo
}
test-ufw-docker--instance-name-found-an-id() {
@mock docker inspect --format="{{.Name}}" fooid
@mock sed -e 's,^/,,'
@mockfalse grep "^$GREP_REGEXP_INSTANCE_NAME\$"
load-ufw-docker-function ufw-docker--instance-name
ufw-docker--instance-name fooid
}
test-ufw-docker--instance-name-found-an-id-assert() {
docker inspect --format="{{.Name}}" fooid
}
test-ufw-docker--list-name() {
@mocktrue ufw status numbered
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo
}
test-ufw-docker--list-name-assert() {
grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$"
}
test-ufw-docker--list-name-udp() {
@mocktrue ufw status numbered
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo "" udp
}
test-ufw-docker--list-name-udp-assert() {
grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$"
}
test-ufw-docker--list-name-80() {
@mocktrue ufw status numbered
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 80
}
test-ufw-docker--list-name-80-assert() {
grep "# allow foo\\( 80\\/tcp\\)\\( [[:graph:]]*\\)\$"
}
test-ufw-docker--list-name-80-udp() {
@mocktrue ufw status numbered
load-ufw-docker-function ufw-docker--list
ufw-docker--list foo 80 udp
}
test-ufw-docker--list-name-80-udp-assert() {
grep "# allow foo\\( 80\\/udp\\)\\( [[:graph:]]*\\)\$"
}
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
load-ufw-docker-function ufw-docker--list-number
ufw-docker--list-number foo 53 udp
}
test-ufw-docker--list-number-assert() {
sed -e 's/^\[[[:blank:]]*\([[:digit:]]\+\)\].*/\1/'
}
test-ufw-docker--delete-empty-result() {
@mock ufw-docker--list-number webapp 80 tcp === @stdout ""
@mock sort -rn
load-ufw-docker-function ufw-docker--delete
ufw-docker--delete webapp 80 tcp
}
test-ufw-docker--delete-empty-result-assert() {
@do-nothing
}
test-ufw-docker--delete-all() {
@mock ufw-docker--list-number webapp 80 tcp === @stdout 5 8 9
@mock sort -rn
load-ufw-docker-function ufw-docker--delete
ufw-docker--delete webapp 80 tcp
}
test-ufw-docker--delete-all-assert() {
ufw delete 5
ufw delete 8
ufw delete 9
}
+133 -43
View File
@@ -2,13 +2,26 @@
set -euo pipefail set -euo pipefail
[[ -n "${DEBUG:-}" ]] && set -x [[ -n "${DEBUG:-}" ]] && set -x
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"
GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+"
DEFAULT_PROTO=tcp DEFAULT_PROTO=tcp
ufw_docker_agent=ufw-docker-agent ufw_docker_agent=ufw-docker-agent
ufw_docker_agent_image="${ufw_docker_agent_image:-chaifeng/${ufw_docker_agent}:181005}" 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
ufw_docker_agent_image="${ufw_docker_agent_image%-*}-legacy"
else
ufw_docker_agent_image="${ufw_docker_agent_image%-*}-nf_tables"
fi
fi
test -n "$ufw_docker_agent_image"
function ufw-docker--status() { function ufw-docker--status() {
ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME"
@@ -18,12 +31,20 @@ function ufw-docker--list() {
local INSTANCE_NAME="$1" local INSTANCE_NAME="$1"
local INSTANCE_PORT="${2:-}" local INSTANCE_PORT="${2:-}"
local PROTO="${3:-${DEFAULT_PROTO}}" local PROTO="${3:-${DEFAULT_PROTO}}"
local NETWORK="${4:-}"
if [[ -z "$INSTANCE_PORT" ]]; then if [[ -z "$INSTANCE_PORT" ]]; then
INSTANCE_PORT="[[:digit:]]\\+" INSTANCE_PORT="[[:digit:]]\\+"
PROTO="\\(tcp\\|udp\\)" PROTO="\\(tcp\\|udp\\)"
fi fi
ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\?\$"
if [[ -z "$NETWORK" ]]; then
NETWORK="[[:graph:]]*"
fi
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() { function ufw-docker--list-number() {
@@ -41,6 +62,7 @@ function ufw-docker--allow() {
local INSTANCE_NAME="$1" local INSTANCE_NAME="$1"
local INSTANCE_PORT="$2" local INSTANCE_PORT="$2"
local PROTO="$3" local PROTO="$3"
local NETWORK="${4:-}"
docker inspect "$INSTANCE_NAME" &>/dev/null || docker inspect "$INSTANCE_NAME" &>/dev/null ||
die "Docker instance \"$INSTANCE_NAME\" doesn't exist." die "Docker instance \"$INSTANCE_NAME\" doesn't exist."
@@ -49,6 +71,7 @@ function ufw-docker--allow() {
[[ -z "${INSTANCE_IP_ADDRESSES:-}" ]] && die "Could not find a running instance \"$INSTANCE_NAME\"." [[ -z "${INSTANCE_IP_ADDRESSES:-}" ]] && die "Could not find a running instance \"$INSTANCE_NAME\"."
mapfile -t INSTANCE_NETWORK_NAMES < <(docker inspect --format='{{range $k, $v := .NetworkSettings.Networks}}{{printf "%s\n" $k}}{{end}}' "$INSTANCE_NAME" 2>/dev/null | remove_blank_lines)
mapfile -t PORT_PROTO_LIST < <(docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' "$INSTANCE_NAME" | remove_blank_lines) mapfile -t PORT_PROTO_LIST < <(docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{with $conf}}{{$p}}{{"\n"}}{{end}}{{end}}' "$INSTANCE_NAME" | remove_blank_lines)
if [[ -z "${PORT_PROTO_LIST:-}" ]]; then if [[ -z "${PORT_PROTO_LIST:-}" ]]; then
@@ -59,8 +82,14 @@ function ufw-docker--allow() {
RETVAL=1 RETVAL=1
for PORT_PROTO in "${PORT_PROTO_LIST[@]}"; do for PORT_PROTO in "${PORT_PROTO_LIST[@]}"; do
if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then
ITER=0
for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do 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=$((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="$?" RETVAL="$?"
done done
fi fi
@@ -89,10 +118,11 @@ function ufw-docker--add-rule() {
local INSTANCE_IP_ADDRESS="$2" local INSTANCE_IP_ADDRESS="$2"
local PORT="$3" local PORT="$3"
local PROTO="$4" local PROTO="$4"
local NETWORK="${5:-}"
declare comment declare comment
echo "allow ${INSTANCE_NAME} ${PORT}/${PROTO}" echo "allow ${INSTANCE_NAME} ${PORT}/${PROTO} ${NETWORK}"
typeset -a UFW_OPTS typeset -a UFW_OPTS
UFW_OPTS=(route allow proto "${PROTO}" UFW_OPTS=(route allow proto "${PROTO}"
from any to "$INSTANCE_IP_ADDRESS") from any to "$INSTANCE_IP_ADDRESS")
@@ -101,12 +131,15 @@ function ufw-docker--add-rule() {
UFW_OPTS+=(port "${PORT}") UFW_OPTS+=(port "${PORT}")
comment="$comment ${PORT}/${PROTO}" comment="$comment ${PORT}/${PROTO}"
} }
[[ -n "$NETWORK" ]] && {
comment="$comment ${NETWORK}"
}
UFW_OPTS+=(comment "$comment") UFW_OPTS+=(comment "$comment")
if ufw-docker--list "$INSTANCE_NAME" "$PORT" "$PROTO" &>/dev/null; then if ufw-docker--list "$INSTANCE_NAME" "$PORT" "$PROTO" "$NETWORK" &>/dev/null; then
ufw --dry-run "${UFW_OPTS[@]}" | grep "^Skipping" && return 0 ufw --dry-run "${UFW_OPTS[@]}" | grep "^Skipping" && return 0
err "Remove outdated rule." err "Remove outdated rule."
ufw-docker--delete "$INSTANCE_NAME" "$PORT" "$PROTO" ufw-docker--delete "$INSTANCE_NAME" "$PORT" "$PROTO" "$NETWORK"
fi fi
echo ufw "${UFW_OPTS[@]}" echo ufw "${UFW_OPTS[@]}"
ufw "${UFW_OPTS[@]}" ufw "${UFW_OPTS[@]}"
@@ -256,50 +289,101 @@ function ufw-docker--raw-command() {
ufw "$@" ufw "$@"
} }
after_rules="/etc/ufw/after.rules"
function ufw-docker--check() {
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."
}
declare -a files_to_be_deleted
function rm-on-exit() {
[[ $# -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=()
}
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"
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]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 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
-A DOCKER-USER -j RETURN
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER
EOF
diff -u --color=auto "$after_rules" "$after_rules_tmp"
}
function ufw-docker--install() { function ufw-docker--install() {
if ! grep "^# BEGIN UFW AND DOCKER\$" /etc/ufw/after.rules &>/dev/null; then if ! ufw-docker--check-install; then
err "Back up /etc/ufw/after.rules" local after_rules_bak
cp /etc/ufw/after.rules /etc/ufw/after.rules-ufw-docker~"$(date '+%Y-%m-%d-%H%M%S').bak" after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~"
cat <<-\EOF | tee -a /etc/ufw/after.rules err "\\nBacking up $after_rules to $after_rules_bak"
# BEGIN UFW AND DOCKER cp "$after_rules" "$after_rules_bak"
*filter cat "$after_rules_tmp" > "$after_rules"
:ufw-user-forward - [0:0] err "Please restart UFW service manually by using the following command:"
:DOCKER-USER - [0:0] if type systemctl &>/dev/null; then
-A DOCKER-USER -j RETURN -s 10.0.0.0/8 err " sudo systemctl restart ufw"
-A DOCKER-USER -j RETURN -s 172.16.0.0/12 else
-A DOCKER-USER -j RETURN -s 192.168.0.0/16 err " sudo service ufw restart"
fi
-A DOCKER-USER -j ufw-user-forward fi
-A DOCKER-USER -j DROP -d 192.168.0.0/16 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN
-A DOCKER-USER -j DROP -d 10.0.0.0/8 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN
-A DOCKER-USER -j DROP -d 172.16.0.0/12 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN
-A DOCKER-USER -j DROP -d 192.168.0.0/16 -p udp -m udp --dport 0:32767
-A DOCKER-USER -j DROP -d 10.0.0.0/8 -p udp -m udp --dport 0:32767
-A DOCKER-USER -j DROP -d 172.16.0.0/12 -p udp -m udp --dport 0:32767
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
EOF
err "Please restart UFW service manually."
fi
} }
function ufw-docker--help() { function ufw-docker--help() {
cat <<-EOF >&2 cat <<-EOF >&2
Usage: Usage:
ufw-docker <list|allow> [docker-instance-id-or-name [port[/tcp|/udp]]] ufw-docker <list|allow> [docker-instance-id-or-name [port[/tcp|/udp]] [network]]
ufw-docker delete allow [docker-instance-id-or-name [port[/tcp|/udp]]] ufw-docker delete allow [docker-instance-id-or-name [port[/tcp|/udp]] [network]]
ufw-docker service allow <swarm-service-id-or-name <port</tcp|/udp>>> ufw-docker service allow <swarm-service-id-or-name <port</tcp|/udp>>>
ufw-docker service delete allow <swarm-service-id-or-name> ufw-docker service delete allow <swarm-service-id-or-name>
ufw-docker <status|install|help> ufw-docker <status|install|check|help>
Examples: Examples:
ufw-docker help ufw-docker help
ufw-docker install
ufw-docker check # Check the installation of firewall rules
ufw-docker install # Install firewall rules
ufw-docker status ufw-docker status
@@ -309,10 +393,11 @@ function ufw-docker--help() {
ufw-docker allow httpd ufw-docker allow httpd
ufw-docker allow httpd 80 ufw-docker allow httpd 80
ufw-docker allow httpd 80/tcp ufw-docker allow httpd 80/tcp
ufw-docker allow httpd 80/tcp default
ufw-docker delete allow httpd ufw-docker delete allow httpd
ufw-docker delete allow httpd 80/tcp ufw-docker delete allow httpd 80/tcp
ufw-docker delete allow httpd 80/tcp default
ufw-docker service allow httpd 80/tcp ufw-docker service allow httpd 80/tcp
@@ -325,7 +410,7 @@ function remove_blank_lines() {
} }
function err() { function err() {
echo "$@" >&2 echo -e "$@" >&2
} }
function die() { function die() {
@@ -333,8 +418,10 @@ function die() {
exit 1 exit 1
} }
# __main__
if ! ufw status 2>/dev/null | grep -Fq "Status: active" ; then if ! ufw status 2>/dev/null | grep -Fq "Status: active" ; then
die "UFW is disabled or you are not root user." die "UFW is disabled or you are not root user, or mismatched iptables legacy/nf_tables, current $(iptables --version)"
fi fi
ufw_action="${1:-help}" ufw_action="${1:-help}"
@@ -362,16 +449,19 @@ case "$ufw_action" in
if [[ "$INSTANCE_PORT" = */udp ]]; then if [[ "$INSTANCE_PORT" = */udp ]]; then
PROTO=udp PROTO=udp
fi fi
shift || true
NETWORK="${1:-}"
INSTANCE_PORT="${INSTANCE_PORT%/*}" INSTANCE_PORT="${INSTANCE_PORT%/*}"
"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) service|raw-command|add-service-rule)
shift || true shift || true
"ufw-docker--$ufw_action" "$@" "ufw-docker--$ufw_action" "$@"
;; ;;
status|install) status|install|check)
ufw-docker--"$ufw_action" ufw-docker--"$ufw_action"
;; ;;
*) *)