From 3fafa03aeb3640a86d9cd8c639d085df6a8d085d Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Fri, 20 May 2016 01:19:27 +0200 Subject: Set up IPSec tunnels between each pair of hosts. We use a dedicated, non-routable, IPv4 subnet for IPSec. Furthermore the subnet is nullrouted in the absence of xfrm lookup (i.e., when there is no matching IPSec Security Association) to avoid data leaks. Each host is associated with an IP in that subnet (thus only reachble within that subnet, either by the host itself or by its IPSec peers). The peers authenticate each other using RSA public key authentication. Kernel traps are used to ensure that connections are only established when traffic is detected between the peers; after 30m of inactivity (this value needs to be less than the rekeying period) the connection is brought down and a kernel trap is installed. --- roles/common/templates/etc/fail2ban/jail.local.j2 | 2 +- roles/common/templates/etc/ipsec.conf.j2 | 43 ++++++++++++++++++++ roles/common/templates/etc/ipsec.secrets.j2 | 5 +++ roles/common/templates/etc/iptables/services.j2 | 7 ++++ .../common/templates/etc/network/if-up.d/ipsec.j2 | 47 ++++++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 roles/common/templates/etc/ipsec.conf.j2 create mode 100644 roles/common/templates/etc/ipsec.secrets.j2 create mode 100755 roles/common/templates/etc/network/if-up.d/ipsec.j2 (limited to 'roles/common/templates') diff --git a/roles/common/templates/etc/fail2ban/jail.local.j2 b/roles/common/templates/etc/fail2ban/jail.local.j2 index f1c9833..eb6a7fb 100644 --- a/roles/common/templates/etc/fail2ban/jail.local.j2 +++ b/roles/common/templates/etc/fail2ban/jail.local.j2 @@ -14,7 +14,7 @@ chain = fail2ban action = %(action_)s # Don't ban ourselves. -ignoreip = 127.0.0.0/8 {{ groups.all | sort | join(' ') }} +ignoreip = 127.0.0.0/8 {{ ipsec_subnet }} # # JAILS diff --git a/roles/common/templates/etc/ipsec.conf.j2 b/roles/common/templates/etc/ipsec.conf.j2 new file mode 100644 index 0000000..4d6aa68 --- /dev/null +++ b/roles/common/templates/etc/ipsec.conf.j2 @@ -0,0 +1,43 @@ +# {{ ansible_managed }} +# Do NOT edit this file directly! + +config setup + charondebug = "dmn 0, lib 0, cfg 0, ike 0, enc 0, net 0" + +conn %default + keyexchange = ikev2 + keyingtries = %forever + ike = aes128gcm16-prfsha256-ecp256,aes256gcm16-prfsha384-ecp384! + esp = aes128gcm16-ecp256,aes256gcm16-ecp384! +{% if 'NATed' not in group_names %} + mobike = no +{% endif %} +{% if 'DynDNS' in group_names %} + leftallowany = yes +{% endif %} + leftauth = pubkey + left = %defaultroute + leftsubnet = {{ ipsec[inventory_hostname_short] | ipv4 }}/32 + leftcert = {{ inventory_hostname_short }}.pem + leftfirewall = yes + lefthostaccess = yes + rightauth = pubkey + auto = route + dpdaction = hold + inactivity = 30m + modeconfig = push + +{% for host in groups.all | difference([inventory_hostname]) | sort %} + +conn {{ hostvars[host].inventory_hostname_short }} + right = {{ hostvars[host].inventory_hostname }} +{% if 'DynDNS' in hostvars[host].group_names %} + rightallowany = yes +{% endif %} + rightcert = {{ hostvars[host].inventory_hostname_short }}.pem + rightsubnet = {{ ipsec[ hostvars[host].inventory_hostname_short ] | ipv4 }}/32 +{% if 'NATed' not in group_names and 'NATed' in hostvars[host].group_names %} + mobike = yes +{% endif %} + +{%- endfor %} diff --git a/roles/common/templates/etc/ipsec.secrets.j2 b/roles/common/templates/etc/ipsec.secrets.j2 new file mode 100644 index 0000000..6ed670d --- /dev/null +++ b/roles/common/templates/etc/ipsec.secrets.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} +# Do NOT edit this file directly! + +# Our VPN uses RSA only. +: RSA {{ inventory_hostname_short }}.key diff --git a/roles/common/templates/etc/iptables/services.j2 b/roles/common/templates/etc/iptables/services.j2 index 303fa06..6bd2533 100644 --- a/roles/common/templates/etc/iptables/services.j2 +++ b/roles/common/templates/etc/iptables/services.j2 @@ -4,6 +4,13 @@ # direction protocol destination port source port # (in|out|inout)[46]? (tcp|udp|..) (port|port:port|port,port) (port|port:port|port,port) +{% if groups.all | length > 1 %} +inout4 udp 500 500 # ISAKMP +{% if groups.NATed | length > 0 %} +inout4 udp 4500 4500 # IPSec NAT Traversal +{% endif %} +{% endif %} + out tcp 80,443 # HTTP/HTTPS out tcp 9418 # GIT out udp 53 # DNS diff --git a/roles/common/templates/etc/network/if-up.d/ipsec.j2 b/roles/common/templates/etc/network/if-up.d/ipsec.j2 new file mode 100755 index 0000000..7dd41d4 --- /dev/null +++ b/roles/common/templates/etc/network/if-up.d/ipsec.j2 @@ -0,0 +1,47 @@ +#!/bin/sh + +# A post-up/down hook to automatically create/delete a virtual subnet +# for IPSec (inet4 only). +# Copyright © 2016 Guilhem Moulin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set -ue +PATH=/usr/sbin:/usr/bin:/sbin:/bin + +# 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. +ip="$( ip -4 -o route show to default scope global \ + | sed -nr '/^default via (\S+) dev (\S+).*/ {s//\2 \1/p;q}' )" +[ "${ip% *}" = "$IFACE" ] || exit 0 +ip="${ip##* }" + +vip="{{ ipsec[inventory_hostname_short] }}" +vsubnet="{{ ipsec_subnet }}" + +case "$MODE" in + start) ip address add "$vip/32" dev "$IFACE" scope global || true + # Nullroute the subnet used for IPSec to avoid data leaks + # in the absence of xfrm lookup (i.e., when there is no + # matching IPSec Security Association). + ip route replace prohibit "$vsubnet" proto static || true + ip route replace table 220 to "$vsubnet" via "$ip" dev "$IFACE" proto static src "$vip" || true + ;; + stop) ip route del table 220 to "$vsubnet" via "$ip" dev "$IFACE" proto static src "$vip" || true + ip route del prohibit "$vsubnet" proto static || true + ip address del "$vip/32" dev "$IFACE" scope global || true +esac -- cgit v1.2.3