Last active
March 13, 2020 00:45
-
-
Save eiginn/9498189 to your computer and use it in GitHub Desktop.
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 | |
#GistID: 9498189 | |
# wrapper to take a file with the output of "ipset save" and restore with conflicting set names | |
# begrudgingly uses mktemp | |
# ignore: ^(realm_|ipjail_|cali(4|6)-) | |
# "realm_" from gatesetter | |
# "ip(6?)jail_" from ipjaild (cflare.co/spec-ipjail) | |
# "cali(4|6)-" from calico (k8s) | |
set -euo pipefail | |
export NETNS_NAME=ipset_test | |
export TESTRUN="" | |
export LOCKFILE=/var/lock/ipset_restore.lock | |
FILTERED_PREFIX="^(realm_|ip(6?)jail_|cali(4|6)-)" | |
# Short prefix because ipset name is limited to 31 bytes | |
temp_prefix='_' | |
die() { | |
echo "$@" >&2 | |
exit 1 | |
} | |
lock() { | |
exec 200>$LOCKFILE | |
flock -n 200 | |
} | |
cleanup() { | |
ip netns del "$NETNS_NAME" | |
rm -f /tmp/ipset.* | |
} | |
test_run() { | |
local file | |
local changed_text | |
file="$1" | |
if [[ -z "$TESTRUN" ]]; then | |
changed_text="loaded ipsets have been updated" | |
else | |
changed_text="loaded ipsets are outdated" | |
fi | |
# make netns to test against | |
ip netns add "$NETNS_NAME" &> /dev/null || (ip netns del "$NETNS_NAME" && ip netns add "$NETNS_NAME") | |
# load ipsets into namespace | |
ip netns exec "$NETNS_NAME" ipset restore < "$file" | |
# check for diff and echo back the string salt expects to see on last line | |
# https://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html#using-the-stateful-argument | |
diff -u <(ipset save | awk '$2 !~ /'$FILTERED_PREFIX'/ {print}' | sort) \ | |
<(ip netns exec "$NETNS_NAME" ipset save | awk '$2 !~ /'$FILTERED_PREFIX'/ {print}' | sort) && \ | |
{ echo "changed=no comment='loaded ipsets are up to date'"; return 0; } || \ | |
{ echo "changed=yes comment='${changed_text}'"; return 1; } | |
} | |
while getopts ":t" opt; do | |
case "$opt" in | |
t) | |
export TESTRUN=true | |
;; | |
\?) | |
die "Invalid option: -$OPTARG" | |
;; | |
esac | |
done | |
shift $((OPTIND -1)) | |
# we should have exacly 1 arg left | |
(("${#}" == 1)) || die "wrong number of args: ${#}" | |
file="${1}" | |
[[ -r "${file}" ]] || die "Can't read file: ${file}" | |
if ! [[ -z "$TESTRUN" ]]; then | |
# just do a test run and exit emitting changes pending | |
(test_run "$file") || true | |
cleanup || true | |
exit 0 | |
fi | |
# make sure only one of these is running | |
lock || die "could not obtain lock: ${LOCKFILE}" | |
# use test run to see if we have pending changes to loaded ipsets | |
changes_file="$(mktemp /tmp/ipset.XXXXXXXXXX.changes)" | |
# see if we have pending changes | |
if ! (test_run "$file") > "$changes_file"; then | |
# restore any sets from file | |
# this finds set names and loops through them | |
for ip_set in $(awk '{if (0 < NF){print $2}}' "${file}" | sort --uniq); do | |
tempfile="$(mktemp /tmp/ipset.XXXXXXXXXX)" || die "Cannot create temp file" | |
# grabs all rules for this one set | |
grep -v '^\s*$\|^\s*\#' "${file}" | awk '$2 == "'"${ip_set}"'"{print}' > "${tempfile}" | |
# tests if the set we want to restore already exists | |
if ipset list "${ip_set}" &>/dev/null; then | |
# rename conflicting set to ${temp_prefix} | |
sed -i "s%${ip_set}%${temp_prefix}${ip_set}%" "${tempfile}" | |
# destroy temp ipset if it exists | |
if ipset list "${temp_prefix}${ip_set}" &>/dev/null; then | |
ipset destroy "${temp_prefix}${ip_set}" || \ | |
die "Failed to remove stale temporary ipset ${temp_prefix}${ip_set}" | |
fi | |
# restore with temp name | |
ipset restore < "${tempfile}" | |
# swap conflicting sets so old one is now named ${temp_prefix} | |
ipset swap "${ip_set}" "${temp_prefix}${ip_set}" | |
# destroy old set | |
ipset destroy "${temp_prefix}${ip_set}" || \ | |
die "Failed to remove old ipset after swap ${temp_prefix}${ip_set}" | |
else | |
# else just restore | |
ipset restore < "${tempfile}" | |
fi | |
rm "${tempfile}" | |
done | |
# remove sets that weren't in restore file | |
for ip_set in $(ipset save | awk '$2 !~ /'$FILTERED_PREFIX'/ {print $2}' | sort --uniq); do | |
if awk '$2 == "'"$ip_set"'" {exit 1}' ${file}; then | |
if (iptables-save | grep -- "--match-set ${ip_set} " || \ | |
ip6tables-save | grep -- "--match-set ${ip_set} "); then | |
echo "Tried to remove ${ip_set}, but it is still in use" | |
else | |
ipset destroy "${ip_set}" || echo "Failed to remove unknown ipset: ${ip_set}" | |
fi | |
fi | |
done | |
fi | |
cat "$changes_file" | |
cleanup || true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment