Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a273ac9d51 | |||
| cdad5e2a02 | |||
| 9d890ee3ee | |||
| a1d3517aeb | |||
| d1e6c13156 | |||
| 682d8b363f | |||
| a689c4eb6e | |||
| e99858510d | |||
| 712b0e8075 | |||
| 5033bf815c | |||
| d110fc00ff | |||
| 9df291d39e | |||
| c95d51c975 | |||
| 8aecb89d4e | |||
| 1333dcd298 | |||
| e40bfd517c | |||
| 97543811ea | |||
| afd62aa96b | |||
| 6986267d30 | |||
| 4833b190ff | |||
| fc7840efef | |||
| 22f04125d6 | |||
| 4335d6fb82 | |||
| cd783f91d7 | |||
| 80a691f084 | |||
| 0150af87dc | |||
| cc58088bc5 | |||
| 2b4a44ff7a | |||
| fa5cec9dc5 | |||
| a444fb9457 | |||
| 347bd313e6 | |||
| e12810d055 | |||
| 173f9659d7 | |||
| 3739f1396b | |||
| 9d7a654a17 | |||
| 4e16b4d359 | |||
| 6bc7eed24c | |||
| d9ee549565 | |||
| 972716443f | |||
| 409fb88131 | |||
| 8521a6d1de | |||
| b7e1f8860e | |||
| 149e84e8af | |||
| 6d1c5a229b | |||
| 52fa5dc1e1 | |||
| 4e6d4332e0 | |||
| 67bfc771c6 | |||
| 95353863b1 | |||
| 36fec96d9f | |||
| bac764e44e | |||
| 9137695937 | |||
| 2d2b968024 | |||
| 5be7dfb366 | |||
| 6291251734 | |||
| b701f6baee | |||
| bcfca79758 | |||
| 491324a1f4 | |||
| 1c6dd31de7 | |||
| 8444f87760 | |||
| aef8f7df82 | |||
| 3893c86236 | |||
| 3598011787 | |||
| c79d1ad1a6 | |||
| b37d892048 | |||
| 6c695ed208 | |||
| 22463cbf6e | |||
| df963fdb31 | |||
| 05006bf638 | |||
| 82479773c5 | |||
| 0df69e787c | |||
| f3cf64d598 | |||
| 0c3da26e83 | |||
| 665d3c5a2a | |||
| 0ad2ebbd47 | |||
| ab03a6aa64 | |||
| e2e6d76187 | |||
| 86568044ed | |||
| 3d2259125b | |||
| d02334ffc1 | |||
| 2545d2977b | |||
| 8e9a7c20f5 | |||
| d4fbb6685c | |||
| 10ce4a8dbe | |||
| da0738d8c7 | |||
| 2fdeecb4cb | |||
| 768d7b0577 | |||
| d831601aa9 | |||
| ef16648ecb | |||
| 766aa9c727 | |||
| 34e84c01b3 | |||
| ce5010172a | |||
| 099f49653c | |||
| 1fe82b9560 |
@@ -1,3 +1,5 @@
|
||||
*
|
||||
!LICENSE
|
||||
!README.md
|
||||
!ufw-docker
|
||||
!docker-entrypoint.sh
|
||||
+15
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
.vagrant
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "test/bach"]
|
||||
path = test/bach
|
||||
url = https://github.com/bach-sh/bach.git
|
||||
@@ -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
@@ -1,13 +1,20 @@
|
||||
FROM ubuntu:18.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
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 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 autoremove --yes \
|
||||
&& 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"]
|
||||
|
||||
CMD ["start"]
|
||||
|
||||
ADD LICENSE README.md /
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
To Fix The Docker and UFW Security Flaw Without Disabling Iptables
|
||||
==================
|
||||
|
||||
[](https://travis-ci.org/chaifeng/ufw-docker)
|
||||
[](https://hub.docker.com/r/chaifeng/ufw-docker-agent)
|
||||
|
||||
- [English](#tldr)
|
||||
- [中文](#太长不想读)
|
||||
|
||||
@@ -67,25 +70,32 @@ Modify the UFW configuration file `/etc/ufw/after.rules` and add the following r
|
||||
# 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 -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 DROP -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 DROP -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 DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
|
||||
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
|
||||
-A DOCKER-USER -j ufw-docker-logging-deny -p 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
|
||||
|
||||
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:
|
||||
|
||||
@@ -119,6 +129,10 @@ The following rules allow UFW to manage whether the public networks are allowed
|
||||
|
||||
-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`.
|
||||
|
||||
-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
|
||||
|
||||
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`
|
||||
|
||||
### using `ufw-user-input`
|
||||
@@ -190,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`
|
||||
|
||||
@@ -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.
|
||||
|
||||
- Modify all `after.rules` files on all nodes, including managers and workers
|
||||
- Deploy this script on mananger nodes
|
||||
- Modifying all `after.rules` files on all nodes, including managers and workers
|
||||
- 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.
|
||||
|
||||
@@ -215,6 +235,14 @@ Show 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
@@ -255,6 +287,34 @@ Remove rules from all nodes related to the service `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
|
||||
|
||||
- [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
|
||||
*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 -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 DROP -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 DROP -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 DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
|
||||
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
|
||||
-A DOCKER-USER -j ufw-docker-logging-deny -p 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
|
||||
|
||||
@@ -374,6 +441,10 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
|
||||
|
||||
-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 端口。
|
||||
|
||||
-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
|
||||
|
||||
如果一个容器在接受数据的时候,端口号没有遵循操作系统的设定,也就是说最小端口号要小余 `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-input`
|
||||
@@ -466,6 +543,14 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
|
||||
|
||||
ufw-docker help
|
||||
|
||||
检查 UFW 配置文件中防火墙规则的安装
|
||||
|
||||
ufw-docker check
|
||||
|
||||
更新 UFW 的配置文件,添加必要的防火墙规则
|
||||
|
||||
ufw-docker install
|
||||
|
||||
显示当前防火墙允许的转发规则
|
||||
|
||||
ufw-docker status
|
||||
@@ -482,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
|
||||
@@ -506,6 +595,34 @@ UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管
|
||||
|
||||
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)
|
||||
|
||||
Vendored
+237
@@ -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
|
||||
@@ -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
Executable
+289
@@ -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}"
|
||||
}
|
||||
Executable
+567
@@ -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
@@ -2,13 +2,26 @@
|
||||
set -euo pipefail
|
||||
[[ -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"
|
||||
|
||||
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}:181005}"
|
||||
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
|
||||
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"
|
||||
@@ -18,12 +31,20 @@ 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}\\)\$" || \
|
||||
ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" || \
|
||||
ufw status numbered | grep "# allow ${INSTANCE_NAME}\$"
|
||||
}
|
||||
|
||||
function ufw-docker--list-number() {
|
||||
@@ -41,6 +62,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."
|
||||
@@ -49,6 +71,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
|
||||
@@ -59,8 +82,14 @@ 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=$((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
|
||||
@@ -89,10 +118,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")
|
||||
@@ -101,12 +131,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[@]}"
|
||||
@@ -256,50 +289,101 @@ function ufw-docker--raw-command() {
|
||||
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() {
|
||||
if ! grep "^# BEGIN UFW AND DOCKER\$" /etc/ufw/after.rules &>/dev/null; then
|
||||
err "Back up /etc/ufw/after.rules"
|
||||
cp /etc/ufw/after.rules /etc/ufw/after.rules-ufw-docker~"$(date '+%Y-%m-%d-%H%M%S').bak"
|
||||
cat <<-\EOF | tee -a /etc/ufw/after.rules
|
||||
# BEGIN UFW AND DOCKER
|
||||
*filter
|
||||
:ufw-user-forward - [0:0]
|
||||
:DOCKER-USER - [0:0]
|
||||
-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 -j ufw-user-forward
|
||||
|
||||
-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
|
||||
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
|
||||
}
|
||||
|
||||
function ufw-docker--help() {
|
||||
cat <<-EOF >&2
|
||||
cat <<-EOF >&2
|
||||
Usage:
|
||||
ufw-docker <list|allow> [docker-instance-id-or-name [port[/tcp|/udp]]]
|
||||
ufw-docker delete 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]] [network]]
|
||||
|
||||
ufw-docker service allow <swarm-service-id-or-name <port</tcp|/udp>>>
|
||||
ufw-docker service delete allow <swarm-service-id-or-name>
|
||||
|
||||
ufw-docker <status|install|help>
|
||||
ufw-docker <status|install|check|help>
|
||||
|
||||
Examples:
|
||||
ufw-docker help
|
||||
ufw-docker install
|
||||
|
||||
ufw-docker check # Check the installation of firewall rules
|
||||
ufw-docker install # Install firewall rules
|
||||
|
||||
ufw-docker status
|
||||
|
||||
@@ -309,10 +393,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
|
||||
|
||||
@@ -325,7 +410,7 @@ function remove_blank_lines() {
|
||||
}
|
||||
|
||||
function err() {
|
||||
echo "$@" >&2
|
||||
echo -e "$@" >&2
|
||||
}
|
||||
|
||||
function die() {
|
||||
@@ -333,8 +418,10 @@ function die() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# __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}"
|
||||
@@ -362,16 +449,19 @@ 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
|
||||
"ufw-docker--$ufw_action" "$@"
|
||||
;;
|
||||
status|install)
|
||||
status|install|check)
|
||||
ufw-docker--"$ufw_action"
|
||||
;;
|
||||
*)
|
||||
|
||||
Reference in New Issue
Block a user