Last active
August 30, 2024 16:41
-
-
Save deric/b52628663e7758deec0a4ab5ab4e5908 to your computer and use it in GitHub Desktop.
Generate iptables port forwarding for running containers (assumes default Docker chains already exists)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Copyright 2020-2022 Tomas Barton | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
# | |
set -o nounset -o pipefail | |
# | |
function -h { | |
cat <<USAGE | |
Generate iptables rules for running docker containers. Use | |
$(basename $0) -v -n | |
to inspect iptables rules without applying changes. | |
USAGE: | |
-b / --binary iptables binary | |
-i / --interface Docker virtual interface, default: docker0 | |
-d / --debug Debugging output | |
-n / --noop Dry run / no iptables rule is applied | |
-v / --verbose Detailed output | |
USAGE | |
}; function --help { -h ;} | |
function msg { out "$*" >&1 ;} | |
function out { printf '%s\n' "$*" ;} | |
function iptables_apply { | |
local binary="$1" | |
local table="$2" | |
local action="$3" | |
local rule="$4" | |
local noop=$5 | |
local verbose=$6 | |
# check if the rule is already defined | |
eval "${binary} -t ${table} --check ${rule} 2>/dev/null" | |
if [[ $? -ne 0 ]]; then | |
if [[ $noop == true ]]; then | |
msg $rule; | |
else | |
if [[ $verbose == true ]]; then | |
msg "${rule}" | |
fi | |
eval "${binary} -t ${table} ${action} ${rule}"; | |
fi | |
fi | |
} | |
function main { | |
local verbose=false | |
local debug=false | |
local noop=false | |
local interface="docker0" | |
local binary="iptables" | |
while [[ $# -gt 0 ]] | |
do | |
case "$1" in # Munging globals, beware | |
-i|--interface) interface="$2"; shift 2 ;; | |
-b|--binary) binary="$2"; shift 2 ;; | |
-n|--noop) noop=true; shift 1 ;; | |
-v|--verbose) verbose=true; shift 1 ;; | |
-d|--debug) debug=true; shift 1 ;; | |
*) err 'Argument error. Please see help: -h' ;; | |
esac | |
done | |
if [[ $debug == true ]]; then | |
set -x | |
fi | |
if [[ $noop == true ]]; then | |
msg "NOOP: Only printing iptables rules to be eventually applied" | |
fi | |
# list currently running container IDs | |
local containers=$(docker ps --format '{{.ID}}') | |
if [[ ! -z "$containers" ]]; then | |
while read -r cont; do | |
# old docker API response | |
local ip=$(docker inspect -f '{{.NetworkSettings.IPAddress}}' ${cont}) | |
if [[ -z "${ip}" ]]; then | |
# newer docker API, probably > 23.01 | |
ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${cont}) | |
fi | |
if [[ $verbose == true ]]; then | |
msg "Container ${cont}" | |
fi | |
# extract port forwarding | |
local ports=$(docker inspect -f '{{json .NetworkSettings.Ports}}' ${cont}) | |
if [[ "${ports}" != "{}" ]]; then | |
local fwd=$(echo "${ports}" | jq -r '. as $a| keys[] | select($a[.]!=null) as $f | "\($f)/\($a[$f][].HostPort)"') | |
if [[ ! -z "$fwd" ]]; then | |
# pass tripples likes `3000/tcp/29956` | |
while read -r pfwd; do | |
local dport protocol hport | |
local IFS="/" | |
read dport protocol hport <<< "${pfwd}" | |
if [[ -z "${ip}" ]]; then | |
err "ERROR: Empty IP for container: ${cont}" | |
fi | |
local rule="DOCKER -d ${ip}\/32 ! -i ${interface} -o ${interface} -p ${protocol} -m ${protocol} --dport ${dport} -j ACCEPT" | |
iptables_apply "${binary}" "filter" "-A" "${rule}" ${noop} ${verbose} | |
rule="POSTROUTING -s ${ip}\/32 -d ${ip}\/32 -p ${protocol} -m ${protocol} --dport ${dport} -j MASQUERADE" | |
iptables_apply "${binary}" "nat" "-A" "${rule}" ${noop} ${verbose} | |
rule="DOCKER ! -i ${interface} -p ${protocol} -m ${protocol} --dport ${hport} -j DNAT --to-destination ${ip}:${dport}" | |
iptables_apply "${binary}" "nat" "-A" "${rule}" ${noop} ${verbose} | |
done <<< "$fwd" | |
fi | |
fi | |
done <<< "$containers" | |
fi | |
} | |
if [[ ${1:-} ]] && declare -F | cut -d' ' -f3 | fgrep -qx -- "${1:-}" | |
then | |
case "$1" in | |
-h|--help) : ;; | |
*) ;; | |
esac | |
"$@" | |
else | |
main "$@" | |
fi |
@panomitrius Right, that's fairly recent release. I've tested the script only on Docker versions up to 19.03.x releases. Probably Docker changed the format of the response in the latest release.
Alright, thanks for your time @deric. I found this script that works for my purposes :)
@deric can i ask you what licence do you have for this code and can i use it in a opensource project ?
@fdurand Sure, you can use it. I usually don't include license header on a single script. Apache 2 license would be ok?
@deric Yes Apache 2 License is perfect.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
docker --version
gives:Docker version 20.10.1, build 831ebea
docker ps --format '{{json .}}'
gives a really long output. I'll share the output of one of the containers here:{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2020-12-19 03:41:44 +0100 CET","ID":"13f4ddebf7a0","Image":"rocketchat/rocket.chat:latest","Labels":"com.docker.compose.oneoff=False,com.docker.compose.project.config_files=docker-compose.yml,com.docker.compose.version=1.27.4,[email protected],traefik.backend=rocketchat,traefik.frontend.rule=Host: your.domain.tld,com.docker.compose.config-hash=5671c0526c55cb391d4db681e58eb2297eedbf58ef77bd10b5728a75ec764ccc,com.docker.compose.container-number=1,com.docker.compose.project=rocketchat-ld,com.docker.compose.project.working_dir=/var/docker/rocketchat-ld,com.docker.compose.service=rocketchatLD","LocalVolumes":"0","Mounts":"/var/docker/ro…","Names":"rocketchatLD","Networks":"rocketchat-ld_default","Ports":"3000/tcp, 0.0.0.0:3100-\u003e3100/tcp","RunningFor":"2 days ago","Size":"0B (virtual 1.47GB)","State":"running","Status":"Up 28 hours"}