summaryrefslogtreecommitdiffstats
path: root/roles/common/templates
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2016-05-20 01:19:27 +0200
committerGuilhem Moulin <guilhem@fripost.org>2016-05-22 17:53:52 +0200
commit3fafa03aeb3640a86d9cd8c639d085df6a8d085d (patch)
treeba1bc3707aa20e3a80c08b1dd2726524333b3d21 /roles/common/templates
parent1bdc6a1202f9cabea5f907c4213f2a6f902443b6 (diff)
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.
Diffstat (limited to 'roles/common/templates')
-rw-r--r--roles/common/templates/etc/fail2ban/jail.local.j22
-rw-r--r--roles/common/templates/etc/ipsec.conf.j243
-rw-r--r--roles/common/templates/etc/ipsec.secrets.j25
-rw-r--r--roles/common/templates/etc/iptables/services.j27
-rwxr-xr-xroles/common/templates/etc/network/if-up.d/ipsec.j247
5 files changed, 103 insertions, 1 deletions
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 <guilhem@fripost.org>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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