mirror of
https://github.com/chaifeng/ufw-docker.git
synced 2025-07-09 19:16:12 +02:00
697 lines
23 KiB
Bash
Executable file
697 lines
23 KiB
Bash
Executable file
#!/bin/bash
|
||
set -euo pipefail
|
||
[[ -n "${DEBUG:-}" ]] && set -x
|
||
|
||
# UFW-DOCKER GLOBAL VARIABLES START #
|
||
LC_ALL=C
|
||
PATH="/bin:/usr/bin:/sbin:/usr/sbin:/snap/bin/"
|
||
|
||
GREP_REGEXP_NAME="[-_.[:alnum:]]\\+"
|
||
DEFAULT_PROTO=tcp
|
||
ENV_VALUE_SPLITTER=','
|
||
|
||
ufw_docker_agent=ufw-docker-agent
|
||
ufw_docker_agent_image="${UFW_DOCKER_AGENT_IMAGE:-chaifeng/${ufw_docker_agent}:250708-nf_tables}"
|
||
|
||
after_rules="/etc/ufw/after.rules"
|
||
after6_rules="/etc/ufw/after6.rules"
|
||
# UFW-DOCKER GLOBAL VARIABLES END #
|
||
|
||
if [[ "${ufw_docker_agent_image}" = *-@(legacy|nf_tables) ]]; then
|
||
if 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
|
||
}
|
||
|
||
function ufw-docker--list() {
|
||
local INSTANCE_NAME="${1:-}"
|
||
local INSTANCE_PORT="${2:-}"
|
||
local PROTO="${3:-}"
|
||
local NETWORK="${4:-}"
|
||
local params_count="$#"
|
||
local _grep_regexp
|
||
|
||
[[ -n "${INSTANCE_NAME:-}" ]] || INSTANCE_NAME="$GREP_REGEXP_NAME"
|
||
if [[ -z "${INSTANCE_PORT:-}" && -z "${PROTO:-}" && -z "${NETWORK-}" ]]; then
|
||
INSTANCE_PORT="[[:digit:]]\\+"
|
||
[[ -n "${PROTO:-}" ]] || PROTO="\\(tcp\\|udp\\)"
|
||
_grep_regexp="\\( ${INSTANCE_PORT}/${PROTO}\\( ${GREP_REGEXP_NAME}\\)\\?\\)\\?"
|
||
else
|
||
[[ -n "${INSTANCE_PORT:-}" ]] || INSTANCE_PORT="[[:digit:]]\\+"
|
||
[[ -n "${PROTO:-}" ]] || PROTO="${DEFAULT_PROTO}"
|
||
if [[ -n "${NETWORK:-}" ]]; then
|
||
NETWORK=" ${NETWORK}"
|
||
else
|
||
NETWORK="\\( ${GREP_REGEXP_NAME}\\)\\?"
|
||
fi
|
||
_grep_regexp=" ${INSTANCE_PORT}/${PROTO}${NETWORK}"
|
||
fi
|
||
|
||
local ufw_output
|
||
ufw_output="$(ufw status numbered)"
|
||
|
||
grep "# allow ${INSTANCE_NAME}\\(/v6\\)\\?${_grep_regexp}\$" <<< "$ufw_output"
|
||
}
|
||
|
||
function ufw-docker--list-number() {
|
||
ufw-docker--list "$@" | sed -e 's/^\[[[:blank:]]*\([[:digit:]]\+\)\].*/\1/'
|
||
}
|
||
|
||
function ufw-docker--delete() {
|
||
for UFW_NUMBER in $(ufw-docker--list-number "$@" | sort -rn); do
|
||
echo "delete \"$UFW_NUMBER\""
|
||
echo y | ufw delete "$UFW_NUMBER" || true
|
||
done
|
||
}
|
||
|
||
function ufw-docker--allow() {
|
||
local INSTANCE_NAME="$1"
|
||
local INSTANCE_PORT="$2"
|
||
local PROTO="$3"
|
||
local NETWORK="${4:-}"
|
||
local NETWORK_ADDRESSES PORT_PROTO_LIST PROT_PROTO IP SUFFIX
|
||
|
||
docker inspect "$INSTANCE_NAME" &>/dev/null ||
|
||
die "Docker instance \"$INSTANCE_NAME\" doesn't exist."
|
||
|
||
mapfile -t NETWORK_ADDRESSES < <(docker inspect --format '{{range $name, $net := .NetworkSettings.Networks}}{{if $net.IPAddress}}{{$name}} {{$net.IPAddress}}{{"\n"}}{{end}}{{if $net.GlobalIPv6Address}}{{$name}} {{$net.GlobalIPv6Address}}{{"\n"}}{{end}}{{end}}' "$INSTANCE_NAME" 2>/dev/null | remove_blank_lines)
|
||
|
||
[[ -z "${NETWORK_ADDRESSES:-}" ]] && die "Could not find a running instance \"$INSTANCE_NAME\"."
|
||
|
||
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
|
||
err "\"$INSTANCE_NAME\" doesn't have any published ports."
|
||
return 1
|
||
fi
|
||
|
||
local count=0
|
||
for PORT_PROTO in "${PORT_PROTO_LIST[@]}"; do
|
||
if [[ -z "$INSTANCE_PORT" || "$PORT_PROTO" = "${INSTANCE_PORT}/${PROTO}" ]]; then
|
||
for item in "${NETWORK_ADDRESSES[@]}"; do
|
||
INSTANCE_NETWORK="${item% *}"
|
||
IP="${item#* }"
|
||
if [[ -n "$NETWORK" ]] && [[ "$NETWORK" != "$INSTANCE_NETWORK" ]]; then
|
||
continue
|
||
fi
|
||
if [[ "$IP" = *:* ]]; then SUFFIX="/v6"; else SUFFIX=""; fi
|
||
ufw-docker--add-rule "${INSTANCE_NAME}${SUFFIX}" "$IP" "${PORT_PROTO%/*}" "${PORT_PROTO#*/}" "${INSTANCE_NETWORK}"
|
||
(( ++count ))
|
||
done
|
||
fi
|
||
done
|
||
if [[ "$count" -eq 0 ]]; then
|
||
err "Fail to add rule(s), cannot find the published port ${INSTANCE_PORT}/${PROTO} of instance \"${INSTANCE_NAME}\" or cannot update outdated rule(s)."
|
||
return 1
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
function ufw-docker--add-service-rule() {
|
||
declare service_id="$1"
|
||
declare port="${2%/*}"
|
||
declare proto="${2#*/}"
|
||
|
||
declare target_ip_port
|
||
target_ip_port="$(iptables -t nat -L DOCKER-INGRESS | grep -E "^DNAT\\s+${proto}\\s+.+\\sto:[.0-9]+:${port}\$" | grep -Eo "[.0-9]+:${port}\$")"
|
||
|
||
[[ -z "$target_ip_port" ]] && die "Could not find VIP of service ${service_id}."
|
||
|
||
ufw-docker--add-rule "$service_id" "${target_ip_port%:*}" "$port" "$proto"
|
||
}
|
||
|
||
function ufw-docker--add-rule() {
|
||
local INSTANCE_NAME="$1"
|
||
local INSTANCE_IP_ADDRESS="$2"
|
||
local PORT="$3"
|
||
local PROTO="$4"
|
||
local NETWORK="${5:-}"
|
||
|
||
declare comment
|
||
|
||
echo "allow ${INSTANCE_NAME} ${PORT}/${PROTO} ${NETWORK}"
|
||
typeset -a UFW_OPTS
|
||
UFW_OPTS=(route allow proto "${PROTO}"
|
||
from any to "$INSTANCE_IP_ADDRESS")
|
||
comment="allow ${INSTANCE_NAME}"
|
||
[[ -n "$PORT" ]] && {
|
||
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" "$NETWORK" &>/dev/null; then
|
||
ufw --dry-run "${UFW_OPTS[@]}" | grep "^Skipping" && return 0
|
||
err "Remove outdated rule."
|
||
ufw-docker--delete "$INSTANCE_NAME" "$PORT" "$PROTO" "$NETWORK"
|
||
fi
|
||
echo ufw "${UFW_OPTS[@]}"
|
||
ufw "${UFW_OPTS[@]}"
|
||
}
|
||
|
||
function ufw-docker--instance-name() {
|
||
local INSTANCE_ID="$1"
|
||
{
|
||
{
|
||
docker inspect --format='{{.Name}}' "$INSTANCE_ID" 2>/dev/null | sed -e 's,^/,,' |
|
||
grep "^${GREP_REGEXP_NAME}\$" 2>/dev/null
|
||
} || echo -n "$INSTANCE_ID";
|
||
} | remove_blank_lines
|
||
}
|
||
|
||
function ufw-docker--service() {
|
||
declare service_action="${1:-help}"
|
||
case "$service_action" in
|
||
delete)
|
||
shift || true
|
||
if [[ "${1:?Invalid 'delete' command syntax.}" != "allow" ]]; then
|
||
die "\"delete\" command only support removing allowed rules"
|
||
fi
|
||
shift || true
|
||
declare service_id_or_name="${1:?Missing swarm service name or service ID}"
|
||
|
||
"ufw-docker--service-${service_action}" "$@"
|
||
;;
|
||
allow)
|
||
shift || true
|
||
declare service_id_or_name="${1:?Missing swarm service name or service ID}"
|
||
declare service_port="${2:?Missing the port number, such as '80/tcp'.}"
|
||
|
||
"ufw-docker--service-${service_action}" "${service_id_or_name}" "${service_port}"
|
||
;;
|
||
*)
|
||
ufw-docker--help
|
||
;;
|
||
esac
|
||
}
|
||
|
||
function ufw-docker--get-service-id() {
|
||
declare service_name="$1"
|
||
docker service inspect "${service_name}" --format "{{.ID}}"
|
||
}
|
||
|
||
function ufw-docker--get-service-name() {
|
||
declare service_name="$1"
|
||
docker service inspect "${service_name}" --format "{{.Spec.Name}}"
|
||
}
|
||
|
||
function ufw-docker--list-service-ports() {
|
||
declare service_name="$1"
|
||
docker service inspect "$service_name" \
|
||
--format '{{range .Endpoint.Spec.Ports}}{{.PublishedPort}} {{.TargetPort}}/{{.Protocol}}{{"\n"}}{{end}}'
|
||
}
|
||
|
||
function ufw-docker--service-allow() {
|
||
declare service_name="$1"
|
||
declare service_port="$2"
|
||
declare service_proto=tcp
|
||
|
||
if [[ -n "$service_port" ]] &&
|
||
! grep -E '^[0-9]+(/(tcp|udp))?$' <<< "$service_port" &>/dev/null; then
|
||
die "Invalid port syntax: $service_port"
|
||
return 1
|
||
fi
|
||
|
||
if [[ "$service_port" = */* ]]; then
|
||
service_proto="${service_port#*/}"
|
||
service_port="${service_port%/*}"
|
||
fi
|
||
|
||
declare service_id
|
||
service_id="$(ufw-docker--get-service-id "${service_name}")"
|
||
[[ -z "${service_id:-}" ]] && die "Could not find service \"$service_name\""
|
||
service_name="$(ufw-docker--get-service-name "${service_name}")"
|
||
|
||
declare env_value= published_port= target_port=
|
||
exec 9< <(ufw-docker--list-service-ports "$service_name")
|
||
while read -u 9 -r published_port target_port; do
|
||
if [[ "$target_port" = "${service_port}/${service_proto}" ]]; then
|
||
env_value="${service_name}/${published_port}/${service_proto}"
|
||
break;
|
||
fi
|
||
done
|
||
exec 9<&-
|
||
|
||
[[ -z "${env_value:-}" ]] && die "Service $service_name does not publish port $service_port."
|
||
|
||
declare service_env
|
||
if ! docker service inspect "$ufw_docker_agent" &>/dev/null; then
|
||
err "Not found ufw-docker-agent service, creating ..."
|
||
service_env="ufw_public_${service_id}=${env_value}"
|
||
docker service create --name "$ufw_docker_agent" --mode global \
|
||
--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="${ufw_docker_agent_image}" \
|
||
--env DEBUG="${DEBUG:-}" \
|
||
--env "${service_env}" \
|
||
"${ufw_docker_agent_image}"
|
||
else
|
||
declare -a value_list=() service_env_list=()
|
||
exec 8< <(ufw-docker--get-env-list)
|
||
while read -u 8 -r id value; do
|
||
[[ "$id" != "$service_id" && "$value" = "${service_name}"/* ]] && service_env_list+=(--env-rm "ufw_public_${id}")
|
||
[[ "$id" = "$service_id" ]] || continue
|
||
[[ "${value}" = "${env_value}" || "${value}" = "${env_value}"/* ]] ||
|
||
value_list+=("${value}")
|
||
done
|
||
exec 8<&-
|
||
value_list=("${env_value}" "${value_list[@]}")
|
||
|
||
service_env="ufw_public_${service_id}=$(IFS="${ENV_VALUE_SPLITTER}"; printf '%s' "${value_list[*]}")"
|
||
|
||
service_env_list+=(--env-add "${service_env}")
|
||
|
||
docker service update --update-parallelism=0 \
|
||
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
|
||
--env-add DEBUG="${DEBUG:-}" \
|
||
"${service_env_list[@]}" \
|
||
--image "${ufw_docker_agent_image}" \
|
||
"${ufw_docker_agent}"
|
||
fi
|
||
}
|
||
|
||
function ufw-docker--get-env-list() {
|
||
docker service inspect "${ufw_docker_agent}" \
|
||
--format '{{range $k,$v := .Spec.TaskTemplate.ContainerSpec.Env}}{{ $v }}{{"\n"}}{{end}}' |
|
||
sed -e '/^ufw_public_/!d' \
|
||
-e 's/^ufw_public_//' \
|
||
-e 's/=/ /' |
|
||
while read -r id value; do
|
||
tr ',' '\n' <<< "$value" | sed "s/^/${id} /g"
|
||
done
|
||
}
|
||
|
||
function ufw-docker--service-delete() {
|
||
declare service_name="$1"
|
||
declare service_port="${2:-}"
|
||
declare port proto
|
||
if [[ "${service_port:-}" = */* ]]; then
|
||
port="${service_port%/*}"
|
||
proto="${service_port#*/}"
|
||
elif [[ -n "${service_port:-}" ]]; then
|
||
port="$service_port"
|
||
proto="$DEFAULT_PROTO"
|
||
fi
|
||
|
||
declare service_id
|
||
service_id="$(ufw-docker--get-service-id "${service_name}")"
|
||
[[ -z "${service_id:-}" ]] && die "Could not find service \"$service_name\""
|
||
service_name="$(ufw-docker--get-service-name "${service_name}")"
|
||
|
||
declare env_value= published_port= target_port=
|
||
if [[ -n "${port:-}" ]]; then
|
||
exec 9< <(ufw-docker--list-service-ports "$service_name")
|
||
while read -u 9 -r published_port target_port; do
|
||
if [[ "$target_port" = "${port}/${proto}" ]]; then
|
||
env_value="${service_name}/${published_port}/${proto}"
|
||
break;
|
||
fi
|
||
done
|
||
exec 9<&-
|
||
[[ -n "${env_value:-}" ]] || die "Service $service_name does not publish port $service_port."
|
||
else
|
||
declare env_value="${service_name}"
|
||
fi
|
||
|
||
declare id value
|
||
declare -a value_list=()
|
||
exec 8< <(ufw-docker--get-env-list)
|
||
while read -u 8 -r id value; do
|
||
[[ "$id" = "$service_id" ]] || continue
|
||
[[ "${value}" = "${env_value}" || "${value}" = "${env_value}"/* ]] ||
|
||
value_list+=("${value}")
|
||
done
|
||
exec 8<&-
|
||
value_list=("${env_value}/deny" "${value_list[@]}")
|
||
|
||
declare service_env
|
||
service_env="ufw_public_${service_id}=$(IFS="${ENV_VALUE_SPLITTER}"; printf '%s' "${value_list[*]}")"
|
||
|
||
docker service update --update-parallelism=0 \
|
||
--env-add ufw_docker_agent_image="${ufw_docker_agent_image}" \
|
||
--env-add "${service_env}" \
|
||
--env-add DEBUG="${DEBUG-}" \
|
||
--image "${ufw_docker_agent_image}" \
|
||
"${ufw_docker_agent}"
|
||
}
|
||
|
||
function ufw-docker--raw-command() {
|
||
ufw "$@"
|
||
}
|
||
|
||
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 IPv4 firewall rules done."
|
||
|
||
if command -v ip6tables >/dev/null 2>&1; then
|
||
err "\\n########## ip6tables -n -L DOCKER-USER ##########"
|
||
ip6tables -n -L DOCKER-USER
|
||
|
||
err "\\n\\n########## diff $after6_rules ##########"
|
||
ufw-docker--check-install_ipv6 "$@" && err "\\nCheck IPv6 firewall rules done."
|
||
fi
|
||
}
|
||
|
||
declare -a files_to_be_deleted
|
||
|
||
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--list-docker-subnets() {
|
||
local ipversion="$1"
|
||
shift || true
|
||
if [[ -z "${1-}" ]]; then
|
||
docker network ls --format '{{.ID}}' |
|
||
while read -r net; do
|
||
docker network inspect "$net" --format '{{range .IPAM.Config}}{{.Subnet}}{{"\n"}}{{end}}'
|
||
done
|
||
else
|
||
printf "%s\n" "$@"
|
||
fi |
|
||
while read -r cidr; do
|
||
if [[ "${ipversion}" = "IPv4" && "$cidr" = *.* ]] || [[ "${ipversion}" = "IPv6" && "$cidr" = *:* ]]
|
||
then echo "$cidr"
|
||
fi
|
||
done |
|
||
sort
|
||
}
|
||
|
||
function ufw-docker--check-install() {
|
||
declare -a cidr_list
|
||
declare cidr
|
||
if [[ -z "${1-}" ]]; then
|
||
cidr_list=(10.0.0.0/8 172.16.0.0/12 192.168.0.0/16)
|
||
elif [[ "${1-}" = '--docker-subnets' ]]; then
|
||
shift || true
|
||
mapfile -t cidr_list < <(ufw-docker--list-docker-subnets IPv4 "$@")
|
||
fi
|
||
if [[ -z "${cidr_list:-}" ]]; then
|
||
err "ERROR: Could not find any IPv4 subnets used by docker engine\n"
|
||
exit 1
|
||
fi
|
||
|
||
after_rules_tmp="${after_rules_tmp:-$(mktemp)}"
|
||
rm-on-exit "$after_rules_tmp"
|
||
|
||
sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after_rules" | tee "$after_rules_tmp" >/dev/null
|
||
{
|
||
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
|
||
|
||
EOF
|
||
|
||
for cidr in "${cidr_list[@]}"; do
|
||
echo "-A DOCKER-USER -j RETURN -s ${cidr}"
|
||
done
|
||
|
||
cat <<-\EOF
|
||
|
||
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
|
||
|
||
EOF
|
||
|
||
for cidr in "${cidr_list[@]}"; do
|
||
echo "-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d ${cidr}"
|
||
done
|
||
for cidr in "${cidr_list[@]}"; do
|
||
echo "-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d ${cidr}"
|
||
done
|
||
|
||
cat <<-\EOF
|
||
|
||
-A DOCKER-USER -j RETURN
|
||
|
||
-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
|
||
} | tee -a "${after_rules_tmp}" >/dev/null
|
||
diff -u --color=auto "$after_rules" "$after_rules_tmp"
|
||
}
|
||
|
||
function ufw-docker--check-install_ipv6() {
|
||
declare -a cidr6_list
|
||
declare cidr
|
||
if [[ -z "${1-}" ]]; then
|
||
cidr6_list=(fd00::/8)
|
||
elif [[ "${1-}" = '--docker-subnets' ]]; then
|
||
shift || true
|
||
mapfile -t cidr6_list < <(ufw-docker--list-docker-subnets IPv6 "$@")
|
||
fi
|
||
if [[ -z "${cidr6_list:-}" ]]; then
|
||
err "INFO: Could not find any IPv6 subnets used by docker engine, will disable IPv6 support.\n"
|
||
return 0
|
||
fi
|
||
|
||
after6_rules_tmp="${after6_rules_tmp:-$(mktemp)}"
|
||
rm-on-exit "$after6_rules_tmp"
|
||
|
||
sed "/^# BEGIN UFW AND DOCKER/,/^# END UFW AND DOCKER/d" "$after6_rules" | tee "$after6_rules_tmp" >/dev/null
|
||
{
|
||
cat <<-\EOF
|
||
# BEGIN UFW AND DOCKER
|
||
*filter
|
||
:ufw6-user-forward - [0:0]
|
||
:ufw6-docker-logging-deny - [0:0]
|
||
:DOCKER-USER - [0:0]
|
||
-A DOCKER-USER -j ufw6-user-forward
|
||
|
||
EOF
|
||
|
||
for cidr in "${cidr6_list[@]}"; do
|
||
echo "-A DOCKER-USER -j RETURN -s ${cidr}"
|
||
done
|
||
|
||
cat <<-\EOF
|
||
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
|
||
EOF
|
||
|
||
for cidr in "${cidr6_list[@]}"; do
|
||
echo "-A DOCKER-USER -j ufw6-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d ${cidr}"
|
||
echo "-A DOCKER-USER -j ufw6-docker-logging-deny -p udp -m udp --dport 0:32767 -d ${cidr}"
|
||
done
|
||
|
||
cat <<-\EOF
|
||
|
||
-A DOCKER-USER -j RETURN
|
||
|
||
-A ufw6-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
|
||
-A ufw6-docker-logging-deny -j DROP
|
||
|
||
COMMIT
|
||
# END UFW AND DOCKER
|
||
EOF
|
||
} | tee -a "${after6_rules_tmp}"
|
||
diff -u --color=auto "$after6_rules" "$after6_rules_tmp"
|
||
}
|
||
|
||
function ufw-docker--install() {
|
||
local changed=false
|
||
if ! ufw-docker--check-install "$@"; then
|
||
changed=true
|
||
local after_rules_bak
|
||
after_rules_bak="${after_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~"
|
||
err "\\nBacking up $after_rules to $after_rules_bak"
|
||
cp "$after_rules" "$after_rules_bak"
|
||
cat "$after_rules_tmp" > "$after_rules"
|
||
fi
|
||
|
||
if ! ufw-docker--check-install_ipv6 "$@"; then
|
||
changed=true
|
||
local after6_rules_bak
|
||
after6_rules_bak="${after6_rules}-ufw-docker~$(date '+%Y-%m-%d-%H%M%S')~"
|
||
err "\\nBacking up $after6_rules to $after6_rules_bak"
|
||
cp "$after6_rules" "$after6_rules_bak"
|
||
cat "$after6_rules_tmp" > "$after6_rules"
|
||
fi
|
||
|
||
if "$changed"; then
|
||
err "Please restart UFW service manually by using the following command:"
|
||
if type systemctl &>/dev/null; then
|
||
err " sudo systemctl restart ufw"
|
||
else
|
||
err " sudo service ufw restart"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
function ufw-docker--install--help() {
|
||
cat <<HELP
|
||
ufw-docker $1 --docker-subnets [SUBNET1 SUBNET2 …]
|
||
|
||
Specify which subnets should be used when configuring firewall rules for Docker containers and any allowed networks that communicate with containers.
|
||
|
||
- If this option is not provided, only standard private LAN subnets are used (RFC1918 for IPv4 and fd00::/8 for IPv6).
|
||
- If --docker-subnets is given without any arguments, all Docker network subnets will be automatically detected and used.
|
||
- If one or more subnets are specified, these subnets will be used for firewall rules, and they can include any networks that need to communicate with containers—not just the subnets configured by the Docker engine.
|
||
You can specify multiple subnets separated by spaces (each in CIDR format).
|
||
|
||
Examples:
|
||
- ufw-docker $1
|
||
Use only standard private subnets (default behavior).
|
||
- IPv4 subnets: 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
|
||
- IPv6 subnet: fd00::/8
|
||
|
||
- ufw-docker $1 --docker-subnets
|
||
Auto-detect and use all Docker network subnets.
|
||
|
||
- ufw-docker $1 --docker-subnets 10.207.0.0/16 192.168.207.0/24 fd00:cf::/64
|
||
Use only the specified subnets, including those outside of Docker’s own configuration, for all networks that should be allowed to communicate with containers.
|
||
HELP
|
||
}
|
||
|
||
function ufw-docker--help() {
|
||
cat <<-EOF >&2
|
||
Usage:
|
||
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 <install|check> [--docker-subnets [SUBNET0 SUBNET1 ...]]
|
||
|
||
ufw-docker <status|install|check|help>
|
||
|
||
Examples:
|
||
ufw-docker help
|
||
ufw-docker check --help
|
||
ufw-docker install --help
|
||
|
||
ufw-docker check # Check the installation of firewall rules
|
||
ufw-docker check --docker-subnets # Auto-detect and use all Docker network subnets
|
||
ufw-docker check --docker-subnets 192.168.207.0/24 10.207.0.0/16 fd00:cf::/64
|
||
|
||
ufw-docker install # Install firewall rules
|
||
ufw-docker install --docker-subnets # Auto-detect and use all Docker network subnets
|
||
ufw-docker install --docker-subnets 192.168.207.0/24 10.207.0.0/16 fd00:cf::/64
|
||
|
||
ufw-docker status
|
||
|
||
ufw-docker list httpd
|
||
|
||
ufw-docker allow httpd
|
||
ufw-docker allow httpd 80
|
||
ufw-docker allow httpd 80/tcp
|
||
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
|
||
|
||
ufw-docker service delete allow httpd
|
||
EOF
|
||
}
|
||
|
||
function remove_blank_lines() {
|
||
sed '/^[[:blank:]]*$/d'
|
||
}
|
||
|
||
function err() {
|
||
echo -e "$@" >&2
|
||
}
|
||
|
||
function die() {
|
||
err "ERROR:" "$@"
|
||
exit 1
|
||
}
|
||
|
||
# __main__
|
||
|
||
if ! ufw status 2>/dev/null | grep -Fq "Status: active" ; then
|
||
die "UFW is disabled or you are not root user, or mismatched iptables legacy/nf_tables, current $(iptables --version)"
|
||
fi
|
||
|
||
if ! docker -v &> /dev/null; then
|
||
die "Docker executable not found."
|
||
fi
|
||
|
||
ufw_action="${1:-help}"
|
||
|
||
case "$ufw_action" in
|
||
delete)
|
||
shift || true
|
||
if [[ "${1:?Invalid 'delete' command syntax.}" != "allow" ]]; then
|
||
die "\"delete\" command only support removing allowed rules"
|
||
fi
|
||
;&
|
||
list|allow)
|
||
shift || true
|
||
|
||
INSTANCE_ID="${1:?Docker instance name/ID cannot be empty.}"
|
||
INSTANCE_NAME="$(ufw-docker--instance-name "$INSTANCE_ID")"
|
||
shift || true
|
||
|
||
INSTANCE_PORT="${1:-}"
|
||
if [[ -n "$INSTANCE_PORT" && ! "$INSTANCE_PORT" =~ [0-9]+(/(tcp|udp))? ]]; then
|
||
die "invalid port syntax: \"$INSTANCE_PORT\"."
|
||
fi
|
||
|
||
PROTO="$DEFAULT_PROTO"
|
||
if [[ "$INSTANCE_PORT" = */udp ]]; then
|
||
PROTO=udp
|
||
fi
|
||
shift || true
|
||
|
||
NETWORK="${1:-}"
|
||
|
||
INSTANCE_PORT="${INSTANCE_PORT%/*}"
|
||
;;&
|
||
delete|list)
|
||
"ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO" "$NETWORK"
|
||
;;
|
||
allow)
|
||
"ufw-docker--$ufw_action" "$INSTANCE_NAME" "$INSTANCE_PORT" "$PROTO" "$NETWORK"
|
||
;;
|
||
service|raw-command|add-service-rule)
|
||
shift || true
|
||
"ufw-docker--$ufw_action" "$@"
|
||
;;
|
||
install|check)
|
||
shift || true
|
||
if [[ "${1-}" = @(help|-h|--help) ]]; then
|
||
ufw-docker--install--help "$ufw_action"
|
||
exit
|
||
fi
|
||
"ufw-docker--$ufw_action" "$@"
|
||
;;
|
||
status)
|
||
ufw-docker--"$ufw_action"
|
||
;;
|
||
*)
|
||
ufw-docker--help
|
||
;;
|
||
esac
|