Skip to content

Instantly share code, notes, and snippets.

@eiginn
Last active March 13, 2020 00:45
Show Gist options
  • Save eiginn/9498189 to your computer and use it in GitHub Desktop.
Save eiginn/9498189 to your computer and use it in GitHub Desktop.
#!/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