summaryrefslogtreecommitdiffstats
path: root/roles
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2013-11-03 05:54:11 +0100
committerGuilhem Moulin <guilhem@fripost.org>2015-06-07 02:50:35 +0200
commit2bcaaf01a5fcc2d2ce618da6af30a43a70d03d80 (patch)
tree020bd450fbc622e49c7284f70785749c31aa4429 /roles
parent6c30a3f5a131b6e628b588c0723d5e5374e115e1 (diff)
Use a dedicated, non-routable, IPv4 for IPSec.
At the each IPSec end-point the traffic is DNAT'ed to / MASQUERADE'd from our dedicated IP after ESP decapsulation. Also, some IP tables ensure that alien (not coming from / going to the tunnel end-point) is dropped.
Diffstat (limited to 'roles')
-rwxr-xr-xroles/common/files/etc/network/if-post-down.d/iptables27
-rwxr-xr-xroles/common/files/etc/network/if-up.d/ipsec44
-rwxr-xr-xroles/common/files/usr/local/sbin/update-firewall.sh71
-rw-r--r--roles/common/handlers/main.yml6
-rw-r--r--roles/common/tasks/firewall.yml8
-rw-r--r--roles/common/tasks/ipsec.yml16
6 files changed, 156 insertions, 16 deletions
diff --git a/roles/common/files/etc/network/if-post-down.d/iptables b/roles/common/files/etc/network/if-post-down.d/iptables
new file mode 100755
index 0000000..944ff3a
--- /dev/null
+++ b/roles/common/files/etc/network/if-post-down.d/iptables
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# A post-down hook to flush ip tables and delete custom chains in the
+# loaded v4 and v6 rulesets.
+#
+# Copyright 2013 Guilhem Moulin <guilhem@fripost.org>
+#
+# Licensed under the GNU GPL version 3 or higher.
+#
+
+set -ue
+PATH=/usr/sbin:/usr/bin:/sbin:/bin
+
+# Ignore the loopback interface; run the script for ifdown only.
+[ "$IFACE" != lo -a "$MODE" = stop ] || exit 0
+
+case "$ADDRFAM" in
+ inet) ipts=/sbin/iptables-save; ipt=/sbin/iptables;;
+ inet6) ipts=/sbin/ip6tables-save; ipt=/sbin/ip6tables;;
+ *) exit 0
+esac
+
+$ipts | sed -nr 's/^\*//p' | \
+while read table; do
+ $ipt -t "$table" -F
+ $ipt -t "$table" -X
+done
diff --git a/roles/common/files/etc/network/if-up.d/ipsec b/roles/common/files/etc/network/if-up.d/ipsec
new file mode 100755
index 0000000..e21d6ea
--- /dev/null
+++ b/roles/common/files/etc/network/if-up.d/ipsec
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# A post-up/down hook to automatically create/delete a 'sec' VLAN
+# device, and a dedicated, host-scoped, IP for IPSec (v4 only).
+#
+# Copyright 2013 Guilhem Moulin <guilhem@fripost.org>
+#
+# Licensed under the GNU GPL version 3 or higher.
+#
+
+set -ue
+PATH=/usr/sbin:/usr/bin:/sbin:/bin
+
+if=sec0
+ip=172.16.0.1/32
+
+# Ignore the loopback interface and non inet4 families.
+[ "$IFACE" != lo -a "$ADDRFAM" = inet ] || exit 0
+
+# Only the device with the default, globally-scoped route, is of
+# interest here.
+[ "$( /bin/ip -4 route show to default scope global \
+ | sed -nr '/^default via \S+ dev (\S+).*/ {s//\1/p;q}' )" \
+ = \
+ "$IFACE" ] || exit 0
+
+case "$MODE" in
+ start) # Don't create $if if it's already there
+ /bin/ip -o link show | grep -qE "^[0-9]+:\s+$if" && exit 0
+
+ # Create a new VLAN $IFACE on physical device $if. This is
+ # required otherwise charon thinks the left peer is that
+ # host-scoped, non-routable IP.
+ /bin/ip link add link "$IFACE" name "$if" type vlan id 2713
+ /bin/ip address add "$ip" dev "$if" scope host
+ /bin/ip link set dev "$if" up
+ ;;
+ stop) # Don't create $if if it's no there
+ /bin/ip -o link show | grep -qE "^[0-9]+:\s+$if" || exit 0
+
+ # Deactivate the VLAN
+ /bin/ip link set dev "$if" down
+ ;;
+esac
diff --git a/roles/common/files/usr/local/sbin/update-firewall.sh b/roles/common/files/usr/local/sbin/update-firewall.sh
index 8530277..54a66e8 100755
--- a/roles/common/files/usr/local/sbin/update-firewall.sh
+++ b/roles/common/files/usr/local/sbin/update-firewall.sh
@@ -61,12 +61,26 @@ iptables() {
# iptables-restore(8) instead.
echo "$@" >> "$new";
}
+commit() {
+ # End a table
+ echo COMMIT >> "$new"
+}
inet46() {
case "$1" in
4) echo "$2";;
6) echo "$3";;
esac
}
+ipt-chains() {
+ # Define new (tables and) chains.
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ ?*:*) echo ":${1%:*} ${1##*:} [0:0]";;
+ ?*) echo "*$1";;
+ esac
+ shift
+ done >> "$new"
+}
ipt-trim() {
# Remove dynamic chain/rules from the input stream, as they are
@@ -134,15 +148,17 @@ run() {
| sed -nr "/^[0-9]+:\s+(sec[0-9]+)@$if:\s.*/ {s//\1/p;q}" )
# The (host-scoped) IP reserved for IPSec.
- local ipsec=
+ local ipsec= secmark
if [ -n "$ifsec" -a $f = 4 ]; then
tables+=( [$f]=' mangle nat' )
ipsec=$( /bin/ip -$f address show dev "$ifsec" scope host \
| sed -nr '/^\s+inet\s(\S+).*/ {s//\1/p;q}' )
+ secmark=0x1
fi
# Store the old (current) ruleset
- local old=$(mktemp -t current-rules.v$f.XXXXXX)
+ local old=$(mktemp -t current-rules.v$f.XXXXXX) \
+ new=$(mktemp -t new-rules.v$f.XXXXXX)
for table in ${tables[$f]}; do
$ipt-save -ct $table
done > "$old"
@@ -156,14 +172,35 @@ run() {
fail2ban=1
fi
+ if [ -n "$ipsec" ]; then
+ # We DNAT the IPSec paquets to $ipsec after decapsulation, and
+ # SNAT them before encapsulation. We need to do the NAT'ing
+ # before packets enter the IPSec stack because they are signed
+ # afterwards, and NAT'ing would mess up the signature.
+ ipt-chains mangle PREROUTING:ACCEPT INPUT:ACCEPT \
+ FORWARD:DROP \
+ OUTPUT:ACCEPT POSTROUTING:ACCEPT
+ # Mark all IPSec packets to keep track of them and NAT them
+ # after decapsulation. Unmarked packets that are to be sent to
+ # $ipsec are dropped.
+ iptables -A PREROUTING -p esp -j MARK --set-mark $secmark
+ iptables -A INPUT -d "$ipsec" -m mark \! --mark $secmark -j DROP
+ commit
+
+ ipt-chains nat PREROUTING:ACCEPT INPUT:ACCEPT \
+ OUTPUT:ACCEPT POSTROUTING:ACCEPT
+ # DNAT all marked packets that have been decapsulated. Packets
+ # originating from our IPSec are SNAT'ed (MASQUERADE). We cannot
+ # mark them here (it won't survivethe NAT'ing), but any reply
+ # not going through IPSec would be dropped (since unmarked).
+ iptables -A PREROUTING \! -p esp -m mark --mark "$secmark" \
+ -j DNAT --to "${ipsec%/*}"
+ iptables -A POSTROUTING -s "$ipsec" -j MASQUERADE
+ commit
+ fi
+
# The usual chains in filter, along with the desired default policies.
- local new=$(mktemp -t new-rules.v$f.XXXXXX)
- cat > "$new" <<- EOF
- *filter
- :INPUT DROP [0:0]
- :FORWARD DROP [0:0]
- :OUTPUT DROP [0:0]
- EOF
+ ipt-chains filter INPUT:DROP FORWARD:DROP OUTPUT:DROP
if [ -z "$if" ]; then
# If the interface is not configured, we stop here and DROP all
@@ -238,6 +275,14 @@ run() {
iptables -A INPUT -i lo -s "$localhost" -d "$localhost" -j ACCEPT
iptables -A OUTPUT -o lo -s "$localhost" -d "$localhost" -j ACCEPT
+ if [ -n "$ipsec" ]; then
+ # ACCEPT any, *marked* traffic destinating to the non-routable
+ # $ipsec. Also ACCEPT all traffic originating from $ipsec, as it
+ # is MASQUERADE'd.
+ iptables -A INPUT -i "$if" -d "$ipsec" -m mark --mark "$secmark" -j ACCEPT
+ iptables -A OUTPUT -s "$ipsec" -o "$if" -j ACCEPT
+ fi
+
# Prepare fail2ban. We make fail2ban insert its rules in a dedicated
# chain, so that it doesn't mess up the existing rules.
[ $fail2ban -eq 1 ] && iptables -A INPUT -i $if -j fail2ban
@@ -296,11 +341,9 @@ run() {
done
########################################################################
+ commit
- # Commit the table 'filter'.
- echo COMMIT >> "$new"
-
local rv1=0 rv2=0 persistent=/etc/iptables/rules.v$f
local oldz=$(mktemp -t current-rules.v$f.XXXXXX)
@@ -311,7 +354,7 @@ run() {
-e 's/^\[[0-9]+:[0-9]+\]\s+//' \
"$old" > "$oldz"
- /usr/bin/uniq "$new" | /bin/ip netns exec $netns $ipt-restore
+ /usr/bin/uniq "$new" | /bin/ip netns exec $netns $ipt-restore || ipt-revert
for table in ${tables[$f]}; do
/bin/ip netns exec $netns $ipt-save -t $table
@@ -327,7 +370,7 @@ run() {
local update="Please run '${0##*/}'."
if [ $check -eq 0 ]; then
- /usr/bin/uniq "$new" | $ipt-restore
+ /usr/bin/uniq "$new" | $ipt-restore || ipt-revert
else
if [ $rv1 -ne 0 ]; then
log "WARN: The IPv$f firewall is not up to date! $update"
diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml
index a64a236..9cae8bf 100644
--- a/roles/common/handlers/main.yml
+++ b/roles/common/handlers/main.yml
@@ -19,3 +19,9 @@
- name: Restart IPSec
service: name=ipsec state=restarted
+
+- name: Reload networking
+ # /etc/init.d/networking doesn't answer the status command; but since
+ # it should be "up" whenever ansible has access to the machine, we use
+ # pattern=init as a dummy assumption.
+ service: name=networking pattern=init state=reloaded
diff --git a/roles/common/tasks/firewall.yml b/roles/common/tasks/firewall.yml
index b1cd9b1..5029932 100644
--- a/roles/common/tasks/firewall.yml
+++ b/roles/common/tasks/firewall.yml
@@ -24,10 +24,14 @@
mode=0755
- name: Make the iptable ruleset persistent
- copy: src=etc/network/if-pre-up.d/iptables
- dest=/etc/network/if-pre-up.d/iptables
+ copy: src=etc/network/{{ item }}
+ dest=/etc/network/{{ item }}
owner=root group=root
mode=0755
+ with_items:
+ - if-pre-up.d/iptables
+ - if-post-down.d/iptables
+
- name: Ensure the firewall is up to date
command: /usr/local/sbin/update-firewall.sh -c
diff --git a/roles/common/tasks/ipsec.yml b/roles/common/tasks/ipsec.yml
index d4270d7..3d7a1dd 100644
--- a/roles/common/tasks/ipsec.yml
+++ b/roles/common/tasks/ipsec.yml
@@ -37,3 +37,19 @@
mode=0644
notify:
- Restart IPSec
+
+- name: Auto-create a dedicated interface for IPSec
+ copy: src=etc/network/if-up.d/ipsec
+ dest=/etc/network/if-up.d/ipsec
+ owner=root group=root
+ mode=0755
+
+# XXX: As of 1.3.1 ansible doesn't accept relative src.
+# See https://github.com/ansible/ansible/issues/4459
+- name: Auto-deactivate the dedicated interface for IPSec
+ file: #src=../if-up.d/ipsec
+ src=/etc/network/if-up.d/ipsec
+ dest=/etc/network/if-down.d/ipsec
+ owner=root group=root state=link
+ notify:
+ - Reload networking