26 Commits

Author SHA1 Message Date
schiwagoa 0cdc12f843 README.md auf Englisch gekürtzt
Unit Testing ufw-docker / Unit Testing (push) Has been cancelled
2024-11-15 18:50:17 +00:00
Chai Feng c9547cb4ec Refactor Vagrantfile with getting docker version automatically 2024-11-11 18:08:27 +08:00
Chai Feng 9474084f3f Update Dockerfile with improved version matching 2024-11-11 18:07:06 +08:00
Chai Feng 1fa425bf17 Add node-internal 2024-11-08 10:25:22 +08:00
Chai Feng 3d6896cdd1 Update Bach to the latest unreleased version 2024-09-29 10:25:06 +08:00
anuragpeshne 6cdd4dfd2f adds test for docker exist 2023-02-21 19:20:42 +08:00
anuragpeshne 17e6047590 Adds check for docker executable and adds snap to path 2023-02-21 19:20:42 +08:00
Chai Feng a273ac9d51 221002-nf_tables 2022-10-02 17:03:51 +08:00
Chai Feng cdad5e2a02 221002-legacy 2022-10-02 17:01:24 +08:00
Chai Feng 9d890ee3ee Add integration tests related to PR #71 2022-09-26 22:00:05 +08:00
Radosław Kłos a1d3517aeb Add integration tests for multiport app 2022-09-26 21:43:22 +08:00
Radosław Kłos d1e6c13156 Add unit tests for alternative greps in ufw-docker--list 2022-09-26 21:43:22 +08:00
Radosław Kłos 682d8b363f Fix existing unit tests 2022-09-26 21:43:22 +08:00
Radosław Kłos a689c4eb6e Fix (almost) always truthy regexp in ufw-docker--list 2022-09-26 21:43:22 +08:00
Chai Feng e99858510d Update Bach Testing Framework 2022-09-21 08:43:58 +08:00
Chai Feng 712b0e8075 Change to iptables (nf_tables), using Ubuntu 22.04 2022-09-20 21:51:39 +08:00
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
6 changed files with 213 additions and 390 deletions
+9 -9
View File
@@ -1,18 +1,18 @@
FROM ubuntu:20.04 FROM ubuntu:24.04
ARG docker_version="19.03.12" ARG docker_version="27.3.1"
ENV DEBIAN_FRONTEND=noninteractive 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 locales ufw \ && apt-get install -y --no-install-recommends locales ufw \
&& ( apt-get install -y --no-install-recommends "docker-ce=5:${docker_version}~*" || \ && apt-get install -y --no-install-recommends "docker-ce=$(apt-cache madison docker-ce | grep -m1 -F "${docker_version}" | cut -d'|' -f2 | tr -d '[[:blank:]]')" \
apt-get install -y --no-install-recommends "docker-ce=${docker_version}~*" ) \
&& locale-gen en_US.UTF-8 \ && locale-gen en_US.UTF-8 \
&& apt-get clean autoclean \ && apt-get clean autoclean \
&& apt-get autoremove --yes \ && apt-get autoremove --yes \
+1 -310
View File
@@ -5,7 +5,6 @@ To Fix The Docker and UFW Security Flaw Without Disabling Iptables
[![chaifeng/ufw-docker-agent](https://img.shields.io/docker/pulls/chaifeng/ufw-docker-agent)](https://hub.docker.com/r/chaifeng/ufw-docker-agent) [![chaifeng/ufw-docker-agent](https://img.shields.io/docker/pulls/chaifeng/ufw-docker-agent)](https://hub.docker.com/r/chaifeng/ufw-docker-agent)
- [English](#tldr) - [English](#tldr)
- [中文](#太长不想读)
## TL;DR ## TL;DR
@@ -318,312 +317,4 @@ We can access the `web` service from our host now
## 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)
- [docker and ufw serious problems · Issue #4737 · moby/moby](https://github.com/moby/moby/issues/4737#issuecomment-420112149) - [docker and ufw serious problems · Issue #4737 · moby/moby](https://github.com/moby/moby/issues/4737#issuecomment-420112149)
## 太长不想读
请直接看[解决 UFW 和 Docker 的问题](#解决-ufw-和-docker-的问题)。
## 问题
UFW 是 Ubuntu 上很流行的一个 iptables 前端,可以非常方便的管理防火墙的规则。但是当安装了 Docker,UFW 无法管理 Docker 发布出来的端口了。
具体现象是:
1. 在一个对外提供服务的服务器上启用了 UFW,并且默认阻止所有未被允许的传入连接。
2. 运行了一个 Docker 容器,并且使用 `-p` 选项来把该容器的某个端口发布到服务器的所有 IP 地址上。比如:`docker run -d --name httpd -p 0.0.0.0:8080:80 httpd:alpine` 将会运行一个 httpd 服务,并且将容器的 `80` 端口发布到服务器的 `8080` 端口上。
3. UFW 将不会阻止所有对 `8080` 端口访问的请求,用命令 `ufw deny 8080` 也无法阻止外部访问这个端口。
这个问题其实挺严重的,这意味着本来只是为了在内部提供服务的一个端口被暴露在公共网络上。
在网络上搜索 "ufw docker" 可以发现很多的讨论:
- https://github.com/moby/moby/issues/4737
- https://forums.docker.com/t/running-multiple-docker-containers-with-ufw-and-iptables-false/8953
- https://www.techrepublic.com/article/how-to-fix-the-docker-and-ufw-security-flaw/
- https://blog.viktorpetersson.com/2014/11/03/the-dangers-of-ufw-docker.html
- https://askubuntu.com/questions/652556/uncomplicated-firewall-ufw-is-not-blocking-anything-when-using-docker
- https://chjdev.com/2016/06/08/docker-ufw/
- https://askubuntu.com/questions/652556/uncomplicated-firewall-ufw-is-not-blocking-anything-when-using-docker
- https://my.oschina.net/abcfy2/blog/539485
- https://www.v2ex.com/amp/t/466666
- https://blog.36web.rocks/2016/07/08/docker-behind-ufw.html
- ...
基本上可以找到的解决办法就是首先禁用 docker 的 iptables 功能,但这也意味着放弃了 docker 的网络管理功能,很典型的现象就是容器将无法访问外部网络。在有的文章中也提到了可以在 UFW 的配置文件中手工添加一条规则,比如 `-A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE`。但这也只是允许了 `172.17.0.0/16` 这个网络。如果有了新增的网络,我们也必须手工再为新增的网络添加这样类似的 iptables 规则。
## 期望的目标
目前网络上的解决方案都非常类似,而且也不优雅,我希望一个新的解决方案可以:
1. 不要禁用 Docker 的 iptables,像往常一样由 Docker 来管理自己的网络。这样有任何新增的 Docker 网络时都无需手工维护 iptables 规则,也避免了在 Docker 中禁用 iptables 之后可能带来的副作用。
2. 公共网络不可以访问 Docker 发布出来的端口,即使是使用类似 `-p 0.0.0.0:8080:80` 的选项把端口发布在所有的 IP 地址上。容器之间、内部网络之间都可以正常互相访问,只有公共网络不可以访问。
虽然可以让 Docker 把容器的某一个端口映射到服务器的私有 IP 地址上,这样公共网络上将不会访问到这个端口。但是这个服务器可能有多个私有 IP 地址,这些私有 IP 地址可能也会发生变化。
3. 可以很方便的允许公共网络直接访问某个容器的端口,而无需额外的软件和配置。就像是用 `ufw allow 8080` 这样允许外部访问 8080 端口,然后用 `ufw delete allow 8080` 就不再允许外部访问。
## 如何做?
### 撤销原先的修改
如果已经按照目前网络上搜索到解决方案修改过了,请先修改回来,包括:
1. 启用 Docker 的 iptables 功能,删除所有类似 `--iptables=false` 的修改,包括 `/etc/docker/daemon.json` 配置文件。
2. UFW 的默认 `FORWARD` 规则改回默认的 `DROP`,而非 `ACCEPT`
3. 删除 UFW 配置文件 `/etc/ufw/after.rules` 中与 Docker 网络相关的规则。
4. 如果修改了 Docker 相关的配置文件,重启 Docker。稍后还要修改 UFW 的配置,可以一并重启。
### 解决 UFW 和 Docker 的问题
目前新的解决方案只需要修改一个 UFW 配置文件即可,Docker 的所有配置和选项都保持默认。
修改 UFW 的配置文件 `/etc/ufw/after.rules`,在最后添加上如下规则:
# 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
然后重启 UFW`sudo systemctl restart ufw`。现在外部就已经无法访问 Docker 发布出来的任何端口了,但是容器内部以及私有网络地址上可以正常互相访问,而且容器也可以正常访问外部的网络。**可能由于某些未知原因,重启 UFW 之后规则也无法生效,请重启服务器。**
如果希望允许外部网络访问 Docker 容器提供的服务,比如有一个容器的服务端口是 `80`。那就可以用以下命令来允许外部网络访问这个服务:
ufw route allow proto tcp from any to any port 80
这个命令会允许外部网络访问所有用 Docker 发布出来的并且内部服务端口为 `80` 的所有服务。
请注意,这个端口 `80` 是容器的端口,而非使用 `-p 0.0.0.0:8080:80` 选项发布在服务器上的 `8080` 端口。
如果有多个容器的服务端口为 80,但只希望外部网络访问某个特定的容器。比如该容器的私有地址为 `172.17.0.2`,就用类似下面的命令:
ufw route allow proto tcp from any to 172.17.0.2 port 80
如果一个容器的服务是 UDP 协议,假如是 DNS 服务,可以用下面的命令来允许外部网络访问所有发布出来的 DNS 服务:
ufw route allow proto udp from any to any port 53
同样的,如果只针对一个特定的容器,比如 IP 地址为 `172.17.0.2`
ufw route allow proto udp from any to 172.17.0.2 port 53
### 解释
在新增的这段规则中,下面这段规则是为了让私有网络地址可以互相访问。通常情况下,私有网络是比公共网络更信任的。
-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
下面的规则是为了可以用 UFW 来管理外部网络是否允许访问 Docker 容器提供的服务,这样我们就可以在一个地方来管理防火墙的规则了。
-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
-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 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`
优点:
使用的 UFW 命令比较简单,也比较容易理解,而且也支持老版本的 Ubuntu
比如,允许公众网络访问一个已经发布出来的容器端口 `8080`,使用命令:
ufw allow 8080
缺点:
不仅仅是暴露了已经发布的容器端口,也暴露了主机上的端口。
比如,如果在主机上运行了一个端口为 `8080` 的服务。命令 `ufw allow 8080` 允许了公共网络访问这个服务,也允许了访问所有已经发布的容器端口为 `8080` 的服务。但是我们可能只是希望保留主机上的这个服务,或者是运行在容器里面的服务,而不是两个同时暴露。
为了避免这个问题,我们可能需要使用类似下面的命令来管理已经发布的容器端口:
ufw allow proto tcp from any to 172.16.0.3 port 8080
#### 使用 `ufw-user-forward`
优点:
不会因为同一条命令而同时暴露主机和容器里面的服务。
比如,如果我们希望暴露所有容器端口为 `8080` 的服务,使用下面的命令:
ufw route allow 8080
现在公共网络可以访问所有容器端口为 `8080` 的已经发布的服务,但是运行在主机上的 `8080` 服务仍然不会被公开。如果我们希望公开主机上的 `8080` 端口,可以执行下面的命令:
ufw allow 8080
缺点:
不支持老版本的 Ubuntu,而且命令的使用上可能也会比较复杂。
#### 结论
如果我们正在使用老版本的 Ubuntu,我们可以使用 `ufw-user-input`。但是要小心避免把不该暴露的服务暴露出去。
如果正在使用支持 `ufw route` 命令的新版本的 Ubuntu,我们最好使用 `ufw-user-forward`,并且使用 `ufw route` 来管理与容器相关的防火墙规则。
## `ufw-docker` 工具
现在这个脚本也支持 Docker Swarm。
### 安装
下载 `ufw-docker` 脚本
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
使用下列命令来修改 ufw 的 `after.rules` 文件
ufw-docker install
这个命令做了以下事情:
- 备份文件 `/etc/ufw/after.rules`
- 把 UFW 和 Docker 的相关规则添加到文件 `after.rules` 的末尾
#### 为 Docker Swarm 环境安装
仅仅可以在管理节点上使用 `ufw-docker` 这个脚本来管理防火墙规则。
- 在所有的节点上修改 `after.rules` 这个文件,包括管理节点和工作节点
- 在管理节点上部署这个脚本
运行在 Docker Swarm 模式下,这个脚本将会创建一个全局服务 `ufw-docker-agent`。这个镜像 [chaifeng/ufw-docker-agent](https://hub.docker.com/r/chaifeng/ufw-docker-agent/) 是由本项目自动构建的。
### 使用方法
显示帮助
ufw-docker help
检查 UFW 配置文件中防火墙规则的安装
ufw-docker check
更新 UFW 的配置文件,添加必要的防火墙规则
ufw-docker install
显示当前防火墙允许的转发规则
ufw-docker status
列出所有和容器 `httpd` 相关的防火墙规则
ufw-docker list httpd
暴露容器 `httpd``80` 端口
ufw-docker allow httpd 80
暴露容器 `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` 的所有映射端口都暴露出来
ufw-docker allow httpd
删除所有和容器 `httpd` 相关的防火墙规则
ufw-docker delete allow httpd
删除容器 `httpd``tcp` 端口 `443` 的规则
ufw-docker delete allow httpd 443/tcp
暴露服务 `web``80` 端口
docker service create --name web --publish 8080:80 httpd:alpine
ufw-docker service allow web 80
# 或者
ufw-docker service allow web 80/tcp
删除与服务 `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)
- [docker and ufw serious problems · Issue #4737 · moby/moby](https://github.com/moby/moby/issues/4737#issuecomment-420112149)
Vendored
+59 -20
View File
@@ -3,16 +3,29 @@
# -*- mode: ruby -*- # -*- mode: ruby -*-
# vi: set ft=ruby : # vi: set ft=ruby :
Vagrant.configure('2') do |config| ENV['VAGRANT_NO_PARALLEL']="true"
config.vm.box = "chaifeng/ubuntu-20.04-docker-19.03.13" Vagrant.configure('2') do |config|
#config.vm.box = "chaifeng/ubuntu-16.04-docker-18.03" ubuntu_version = File.readlines("Dockerfile").filter { |line|
line.start_with?("FROM ")
}.first.match(/\d\d\.\d\d/)[0]
docker_version = File.readlines("Dockerfile").filter { |line|
line.start_with?("ARG docker_version=")
}.first.match(/"([\d\.]+)"/)[1]
config.vm.box = "chaifeng/ubuntu-#{ubuntu_version}-docker-#{docker_version}"
config.vm.provider 'virtualbox' do |vb| config.vm.provider 'virtualbox' do |vb|
vb.memory = '1024' vb.memory = '1024'
vb.default_nic_type = "virtio" vb.default_nic_type = "virtio"
end end
config.vm.provider 'parallels' do |prl|
prl.memory = '1024'
prl.check_guest_tools = false
end
ip_prefix="192.168.56" ip_prefix="192.168.56"
config.vm.provision 'docker-daemon-config', type: 'shell', inline: <<-SHELL config.vm.provision 'docker-daemon-config', type: 'shell', inline: <<-SHELL
@@ -57,15 +70,16 @@ Vagrant.configure('2') do |config|
private_registry="#{ip_prefix}.130:5000" private_registry="#{ip_prefix}.130:5000"
config.vm.define "master" do |master| config.vm.define "master" do |master|
master_ip_address = "#{ip_prefix}.130"
master.vm.hostname = "master" master.vm.hostname = "master"
master.vm.network "private_network", ip: "#{ip_prefix}.130" master.vm.network "private_network", ip: "#{master_ip_address}"
master.vm.provision "unit-testing", type: 'shell', inline: <<-SHELL master.vm.provision "unit-testing", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
/vagrant/test.sh /vagrant/test.sh
SHELL SHELL
master.vm.provision "docker-registry", type: 'docker' do |d| master.vm.provision "docker-registry", preserve_order: true, type: 'docker' do |d|
d.run "registry", d.run "registry",
image: "registry:2", image: "registry:2",
args: "-p 5000:5000", args: "-p 5000:5000",
@@ -73,35 +87,38 @@ Vagrant.configure('2') do |config|
daemonize: true daemonize: true
end end
ufw_docker_agent_image = "#{private_registry}/chaifeng/ufw-docker-agent:test" ufw_docker_agent_image = "#{private_registry}/chaifeng/ufw-docker-agent:test-legacy"
master.vm.provision "docker-build-ufw-docker-agent", type: 'shell', inline: <<-SHELL master.vm.provision "docker-build-ufw-docker-agent", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
docker build -t #{ufw_docker_agent_image} /vagrant suffix="$(iptables --version | grep -o '\\(nf_tables\\|legacy\\)')"
docker push #{ufw_docker_agent_image} 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}" > /etc/profile.d/ufw-docker.sh echo "export UFW_DOCKER_AGENT_IMAGE=#{ufw_docker_agent_image}-${suffix}" > /etc/profile.d/ufw-docker.sh
echo "export DEBUG=true" >> /etc/profile.d/ufw-docker.sh echo "export DEBUG=true" >> /etc/profile.d/ufw-docker.sh
echo "Defaults env_keep += UFW_DOCKER_AGENT_IMAGE" > /etc/sudoers.d/98_ufw-docker echo "Defaults env_keep += UFW_DOCKER_AGENT_IMAGE" > /etc/sudoers.d/98_ufw-docker
echo "Defaults env_keep += DEBUG" >> /etc/sudoers.d/98_ufw-docker echo "Defaults env_keep += DEBUG" >> /etc/sudoers.d/98_ufw-docker
SHELL SHELL
master.vm.provision "swarm-init", type: 'shell', inline: <<-SHELL master.vm.provision "swarm-init", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
docker info | fgrep 'Swarm: active' && exit 0 docker info | fgrep 'Swarm: active' && exit 0
docker swarm init --advertise-addr eth1 docker swarm init --advertise-addr "#{master_ip_address}"
docker swarm join-token worker --quiet > /vagrant/.vagrant/docker-join-token docker swarm join-token worker --quiet > /vagrant/.vagrant/docker-join-token
SHELL SHELL
master.vm.provision "build-webapp", type: 'shell', inline: <<-SHELL master.vm.provision "build-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
docker build -t #{private_registry}/chaifeng/hostname-webapp - <<\\DOCKERFILE docker build -t #{private_registry}/chaifeng/hostname-webapp - <<\\DOCKERFILE
FROM httpd:alpine FROM httpd:alpine
RUN { echo '#!/bin/sh'; \\ RUN { echo '#!/bin/sh'; \\
echo 'set -e; (echo -n "${name:-Hi} "; hostname;) > /usr/local/apache2/htdocs/index.html'; \\ echo 'set -e; (echo -n "${name:-Hi} "; hostname;) > /usr/local/apache2/htdocs/index.html'; \\
echo '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 "$@"'; \\ echo 'exec "$@"'; \\
} > /entrypoint.sh; chmod +x /entrypoint.sh } > /entrypoint.sh; chmod +x /entrypoint.sh
@@ -111,7 +128,7 @@ DOCKERFILE
docker push #{private_registry}/chaifeng/hostname-webapp docker push #{private_registry}/chaifeng/hostname-webapp
SHELL SHELL
master.vm.provision "local-webapp", type: 'shell', inline: <<-SHELL master.vm.provision "local-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
for name in public:18080 local:8000; do for name in public:18080 local:8000; do
webapp="${name%:*}_webapp" webapp="${name%:*}_webapp"
@@ -125,7 +142,7 @@ DOCKERFILE
ufw-docker allow public_webapp ufw-docker allow public_webapp
SHELL SHELL
master.vm.provision "multiple-network", type: 'shell', inline: <<-SHELL master.vm.provision "multiple-network", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
if ! docker network ls | grep -F foo-internal; then if ! docker network ls | grep -F foo-internal; then
docker network create --internal foo-internal docker network create --internal foo-internal
@@ -148,7 +165,7 @@ DOCKERFILE
ufw-docker allow internal-multinet-app 80 foo-internal ufw-docker allow internal-multinet-app 80 foo-internal
SHELL SHELL
master.vm.provision "swarm-webapp", type: 'shell', inline: <<-SHELL master.vm.provision "swarm-webapp", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
for name in public:29090 local:9000; do for name in public:29090 local:9000; do
webapp="${name%:*}_service" webapp="${name%:*}_service"
@@ -159,6 +176,14 @@ DOCKERFILE
done done
ufw-docker service allow public_service 80/tcp ufw-docker service allow public_service 80/tcp
docker service inspect "public_multiport" ||
docker service create --name "public_multiport" \
--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 SHELL
end end
@@ -167,7 +192,7 @@ DOCKERFILE
node.vm.hostname = "node#{ip}" node.vm.hostname = "node#{ip}"
node.vm.network "private_network", ip: "#{ip_prefix}.#{ 130 + ip }" node.vm.network "private_network", ip: "#{ip_prefix}.#{ 130 + ip }"
node.vm.provision "swarm-join", type: 'shell', inline: <<-SHELL node.vm.provision "swarm-join", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
docker info | fgrep 'Swarm: active' && exit 0 docker info | fgrep 'Swarm: active' && exit 0
@@ -177,15 +202,25 @@ DOCKERFILE
end end
end end
config.vm.define "node-internal" do |node|
node.vm.hostname = "node-internal"
node.vm.network "private_network", ip: "#{ip_prefix}.142"
end
config.vm.define "external" do |external| config.vm.define "external" do |external|
external.vm.hostname = "external" external.vm.hostname = "external"
external.vm.network "private_network", ip: "#{ip_prefix}.127" external.vm.network "private_network", ip: "#{ip_prefix}.127"
external.vm.provision "testing", type: 'shell', inline: <<-SHELL external.vm.provision "testing", preserve_order: true, type: 'shell', inline: <<-SHELL
set -euo pipefail set -euo pipefail
set -x set -x
server="http://#{ip_prefix}.130" server="http://#{ip_prefix}.130"
function test-webapp() { timeout 3 curl --silent "$@"; } function test-webapp() {
if timeout 3 curl --silent "$@"
then echo "Success: $*"
else echo "Cannot visit: $*"; return 1
fi
}
test-webapp "$server:18080" test-webapp "$server:18080"
! test-webapp "$server:8000" ! test-webapp "$server:8000"
@@ -195,6 +230,10 @@ DOCKERFILE
test-webapp "$server:29090" test-webapp "$server:29090"
! test-webapp "$server:9000" ! test-webapp "$server:9000"
test-webapp "$server:40080"
test-webapp "$server:48080"
! test-webapp "$server:47000"
echo "=====================" echo "====================="
echo " TEST DONE " echo " TEST DONE "
echo "=====================" echo "====================="
+87 -9
View File
@@ -12,12 +12,20 @@ source "$working_dir"/bach/bach.sh
@mocktrue ufw status @mocktrue ufw status
@mocktrue grep -Fq "Status: active" @mocktrue grep -Fq "Status: active"
@ignore remove_blank_lines @mock iptables --version
@mocktrue grep -F '(legacy)'
@mocktrue docker -v
@mock docker -v === @stdout Docker version 0.0.0, build dummy
@mockpipe remove_blank_lines
@ignore echo @ignore echo
@ignore err @ignore err
DEFAULT_PROTO=tcp DEFAULT_PROTO=tcp
GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+" GREP_REGEXP_INSTANCE_NAME="[-_.[:alnum:]]\\+"
UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:090502-legacy
} }
function ufw-docker() { function ufw-docker() {
@@ -30,6 +38,41 @@ function load-ufw-docker-function() {
@load_function "$working_dir/../ufw-docker" "$1" @load_function "$working_dir/../ufw-docker" "$1"
} }
test-ufw-docker-init-legacy() {
@mocktrue grep -F '(legacy)'
@source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help
}
test-ufw-docker-init-legacy-assert() {
iptables --version
test -n chaifeng/ufw-docker-agent:090502-legacy
trap on-exit EXIT INT TERM QUIT ABRT ERR
@dryrun cat
}
test-ufw-docker-init-nf_tables() {
@mockfalse grep -F '(legacy)'
@source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help
}
test-ufw-docker-init-nf_tables-assert() {
iptables --version
test -n chaifeng/ufw-docker-agent:090502-nf_tables
trap on-exit EXIT INT TERM QUIT ABRT ERR
@dryrun cat
}
test-ufw-docker-init() {
UFW_DOCKER_AGENT_IMAGE=chaifeng/ufw-docker-agent:100917
@source <(@sed '/PATH=/d' "$working_dir/../ufw-docker") help
}
test-ufw-docker-init-assert() {
test -n chaifeng/ufw-docker-agent:100917
trap on-exit EXIT INT TERM QUIT ABRT ERR
@dryrun cat
}
test-ufw-docker-help() { test-ufw-docker-help() {
ufw-docker help ufw-docker help
} }
@@ -48,11 +91,23 @@ test-ufw-docker-without-parameters-assert() {
test-ufw-is-disabled() { test-ufw-is-disabled() {
@mockfalse grep -Fq "Status: active" @mockfalse grep -Fq "Status: active"
@mock iptables --version === @stdout 'iptables v1.8.4 (legacy)'
ufw-docker ufw-docker
} }
test-ufw-is-disabled-assert() { test-ufw-is-disabled-assert() {
die "UFW is disabled or you are not root user." 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-docker-is-installed() {
@mockfalse docker -v
ufw-docker
}
test-docker-is-installed-assert() {
die "Docker executable not found."
ufw-docker--help ufw-docker--help
} }
@@ -410,7 +465,7 @@ test-ufw-docker--instance-name-found-a-name() {
} }
test-ufw-docker--instance-name-found-a-name-assert() { test-ufw-docker--instance-name-found-a-name-assert() {
docker inspect --format="{{.Name}}" foo docker inspect --format="{{.Name}}" foo
echo -n foo @dryrun echo -n foo
} }
@@ -433,7 +488,7 @@ test-ufw-docker--list-name() {
ufw-docker--list foo ufw-docker--list foo
} }
test-ufw-docker--list-name-assert() { test-ufw-docker--list-name-assert() {
grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\\( [[:graph:]]*\\)\\?\$" grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$"
} }
test-ufw-docker--list-name-udp() { test-ufw-docker--list-name-udp() {
@@ -442,7 +497,7 @@ test-ufw-docker--list-name-udp() {
ufw-docker--list foo "" udp ufw-docker--list foo "" udp
} }
test-ufw-docker--list-name-udp-assert() { test-ufw-docker--list-name-udp-assert() {
grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\?\\( [[:graph:]]*\\)\\?\$" grep "# allow foo\\( [[:digit:]]\\+\\/\\(tcp\\|udp\\)\\)\\( [[:graph:]]*\\)\$"
} }
@@ -452,7 +507,7 @@ test-ufw-docker--list-name-80() {
ufw-docker--list foo 80 ufw-docker--list foo 80
} }
test-ufw-docker--list-name-80-assert() { test-ufw-docker--list-name-80-assert() {
grep "# allow foo\\( 80\\/tcp\\)\\?\\( [[:graph:]]*\\)\\?\$" grep "# allow foo\\( 80\\/tcp\\)\\( [[:graph:]]*\\)\$"
} }
@@ -462,7 +517,30 @@ test-ufw-docker--list-name-80-udp() {
ufw-docker--list foo 80 udp ufw-docker--list foo 80 udp
} }
test-ufw-docker--list-name-80-udp-assert() { test-ufw-docker--list-name-80-udp-assert() {
grep "# allow foo\\( 80\\/udp\\)\\?\\( [[:graph:]]*\\)\\?\$" grep "# allow foo\\( 80\\/udp\\)\\( [[: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\$"
} }
@@ -479,7 +557,7 @@ test-ufw-docker--list-number-assert() {
test-ufw-docker--delete-empty-result() { test-ufw-docker--delete-empty-result() {
@mock ufw-docker--list-number webapp 80 tcp === @stdout "" @mock ufw-docker--list-number webapp 80 tcp === @stdout ""
@mock sort -rn @mockpipe sort -rn
load-ufw-docker-function ufw-docker--delete load-ufw-docker-function ufw-docker--delete
ufw-docker--delete webapp 80 tcp ufw-docker--delete webapp 80 tcp
@@ -491,7 +569,7 @@ test-ufw-docker--delete-empty-result-assert() {
test-ufw-docker--delete-all() { test-ufw-docker--delete-all() {
@mock ufw-docker--list-number webapp 80 tcp === @stdout 5 8 9 @mock ufw-docker--list-number webapp 80 tcp === @stdout 5 8 9
@mock sort -rn @mockpipe sort -rn
load-ufw-docker-function ufw-docker--delete load-ufw-docker-function ufw-docker--delete
ufw-docker--delete webapp 80 tcp ufw-docker--delete webapp 80 tcp
+56 -41
View File
@@ -5,13 +5,23 @@ set -euo pipefail
LANG=en_US.UTF-8 LANG=en_US.UTF-8
LANGUAGE=en_US: LANGUAGE=en_US:
LC_ALL=en_US.UTF-8 LC_ALL=en_US.UTF-8
PATH="/bin:/usr/bin:/sbin:/usr/sbin" PATH="/bin:/usr/bin:/sbin:/usr/sbin:/snap/bin/"
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}:210925}" 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() { function ufw-docker--status() {
ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME" ufw-docker--list "$GREP_REGEXP_INSTANCE_NAME"
@@ -31,8 +41,10 @@ function ufw-docker--list() {
if [[ -z "$NETWORK" ]]; then if [[ -z "$NETWORK" ]]; then
NETWORK="[[:graph:]]*" NETWORK="[[:graph:]]*"
fi fi
ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\?\\( ${NETWORK}\\)\\?\$" ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\\( ${NETWORK}\\)\$" || \
ufw status numbered | grep "# allow ${INSTANCE_NAME}\\( ${INSTANCE_PORT}\\/${PROTO}\\)\$" || \
ufw status numbered | grep "# allow ${INSTANCE_NAME}\$"
} }
function ufw-docker--list-number() { function ufw-docker--list-number() {
@@ -72,12 +84,11 @@ function ufw-docker--allow() {
if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then
ITER=0 ITER=0
for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do for IP in "${INSTANCE_IP_ADDRESSES[@]}"; do
INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}" INSTANCE_NETWORK="${INSTANCE_NETWORK_NAMES[$ITER]}"
ITER=$((ITER+1)) ITER=$((ITER+1))
if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then
continue continue
fi fi
ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}" ufw-docker--add-rule "$INSTANCE_NAME" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}"
RETVAL="$?" RETVAL="$?"
done done
@@ -281,34 +292,34 @@ function ufw-docker--raw-command() {
after_rules="/etc/ufw/after.rules" after_rules="/etc/ufw/after.rules"
function ufw-docker--check() { function ufw-docker--check() {
err "\\n########## iptables -n -L DOCKER-USER ##########" err "\\n########## iptables -n -L DOCKER-USER ##########"
iptables -n -L DOCKER-USER iptables -n -L DOCKER-USER
err "\\n\\n########## diff $after_rules ##########" err "\\n\\n########## diff $after_rules ##########"
ufw-docker--check-install && err "\\nCheck done." ufw-docker--check-install && err "\\nCheck done."
} }
declare -a files_to_be_deleted declare -a files_to_be_deleted
function rm-on-exit() { function rm-on-exit() {
[[ $# -gt 0 ]] && files_to_be_deleted+=("$@") [[ $# -gt 0 ]] && files_to_be_deleted+=("$@")
} }
function on-exit() { function on-exit() {
for file in "${files_to_be_deleted[@]:-}"; do for file in "${files_to_be_deleted[@]:-}"; do
[[ -f "$file" ]] && rm -r "$file" [[ -f "$file" ]] && rm -r "$file"
done done
files_to_be_deleted=() files_to_be_deleted=()
} }
trap on-exit EXIT INT TERM QUIT ABRT ERR trap on-exit EXIT INT TERM QUIT ABRT ERR
function ufw-docker--check-install() { function ufw-docker--check-install() {
after_rules_tmp="${after_rules_tmp:-$(mktemp)}" after_rules_tmp="${after_rules_tmp:-$(mktemp)}"
rm-on-exit "$after_rules_tmp" rm-on-exit "$after_rules_tmp"
sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp" sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" > "$after_rules_tmp"
>> "${after_rules_tmp}" cat <<-\EOF >> "${after_rules_tmp}" cat <<-\EOF
# BEGIN UFW AND DOCKER # BEGIN UFW AND DOCKER
*filter *filter
:ufw-user-forward - [0:0] :ufw-user-forward - [0:0]
@@ -338,27 +349,27 @@ function ufw-docker--check-install() {
# END UFW AND DOCKER # END UFW AND DOCKER
EOF EOF
diff -u --color=auto "$after_rules" "$after_rules_tmp" diff -u --color=auto "$after_rules" "$after_rules_tmp"
} }
function ufw-docker--install() { function ufw-docker--install() {
if ! ufw-docker--check-install; then if ! ufw-docker--check-install; then
local after_rules_bak local after_rules_bak
after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~" after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~"
err "\\nBacking up $after_rules to $after_rules_bak" err "\\nBacking up $after_rules to $after_rules_bak"
cp "$after_rules" "$after_rules_bak" cp "$after_rules" "$after_rules_bak"
cat "$after_rules_tmp" > "$after_rules" cat "$after_rules_tmp" > "$after_rules"
err "Please restart UFW service manually by using the following command:" err "Please restart UFW service manually by using the following command:"
if type systemctl &>/dev/null; then if type systemctl &>/dev/null; then
err " sudo systemctl restart ufw" err " sudo systemctl restart ufw"
else else
err " sudo service ufw restart" err " sudo service ufw restart"
fi
fi fi
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]] [network]] 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 delete allow [docker-instance-id-or-name [port[/tcp|/udp]] [network]]
@@ -410,7 +421,11 @@ function die() {
# __main__ # __main__
if ! ufw status 2>/dev/null | grep -Fq "Status: active" ; then if ! ufw status 2>/dev/null | grep -Fq "Status: active" ; then
die "UFW is disabled or you are not root user." die "UFW is disabled or you are not root user, or mismatched iptables legacy/nf_tables, current $(iptables --version)"
fi
if ! docker -v &> /dev/null; then
die "Docker executable not found."
fi fi
ufw_action="${1:-help}" ufw_action="${1:-help}"
@@ -438,9 +453,9 @@ case "$ufw_action" in
if [[ "$INSTANCE_PORT" = */udp ]]; then if [[ "$INSTANCE_PORT" = */udp ]]; then
PROTO=udp PROTO=udp
fi fi
shift || true shift || true
NETWORK="${1:-}" NETWORK="${1:-}"
INSTANCE_PORT="${INSTANCE_PORT%/*}" INSTANCE_PORT="${INSTANCE_PORT%/*}"