-
-
Save deric/b52628663e7758deec0a4ab5ab4e5908 to your computer and use it in GitHub Desktop.
#!/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 |
Thank's for a quick reply! It still doesn't run with correct host/network, for example the full output of the last error message (ran with -v) is:
Container 5f9b5f48da05
DOCKER -d \/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9000 -j ACCEPT
iptables v1.8.4 (legacy): host/network
' not found
Try iptables -h' or 'iptables --help' for more information.
POSTROUTING -s \/32 -d \/32 -p tcp -m tcp --dport 9000 -j MASQUERADE
iptables v1.8.4 (legacy): host/network
' not found`
@panomitrius And what's the name of your docker interface? have you tied using -i
flag?
@panomitrius And what's the name of your docker interface? have you tied using
-i
flag?
It's the default docker0..
Which version of Docker do you use? Could you share the docker ps --format '{{json .}}'
output?
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"}
@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.
@panomitrius let me know if the updated version works for you. I don't know how to reproduce the
host/network
issue, I would need more information about that.