summaryrefslogtreecommitdiffstats
path: root/roles
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2014-06-25 05:22:58 +0200
committerGuilhem Moulin <guilhem@fripost.org>2015-06-07 02:51:51 +0200
commita4d0e4a7f8cd829de8346fb6edd9866cc855134f (patch)
tree2b66a0fb217b9fc200dcaaa51ca426283318ff58 /roles
parent01abd3dbf8e357fd71ebfa41519dc4d1f4bc0bd8 (diff)
Don't require a PKI for IPSec.
Instead, generate a server certificate for each host (on the machine itself). Then fetch all these certs locally, and copy them over to each IPSec peer. That requires more certs to be stored on each machines (n vs 2), but it can be done automatically, and is easier to deploy. Note: When adding a new machine to the inventory, one needs to run the playbook on that machine (to generate the cert and fetch it locally) first, then on all other machines.
Diffstat (limited to 'roles')
-rwxr-xr-xroles/common/files/usr/local/bin/genkeypair.sh128
-rw-r--r--roles/common/handlers/main.yml3
-rw-r--r--roles/common/tasks/ipsec.yml44
-rw-r--r--roles/common/tasks/main.yml5
-rw-r--r--roles/common/templates/etc/ipsec.conf.j217
5 files changed, 164 insertions, 33 deletions
diff --git a/roles/common/files/usr/local/bin/genkeypair.sh b/roles/common/files/usr/local/bin/genkeypair.sh
new file mode 100755
index 0000000..2af24cf
--- /dev/null
+++ b/roles/common/files/usr/local/bin/genkeypair.sh
@@ -0,0 +1,128 @@
+#!/bin/sh
+
+# Generate self-signed server certificates. Inspired from
+# make-ssl-cert(8).
+# XXX: add support for DKIM and OpenSSH
+#
+# Copyright © 2014 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/bin:/bin
+
+# Default values
+type=rsa
+bits=
+hash=sha1
+
+force=
+pubkey=pubkey.pem
+privkey=privkey.pem
+
+usage() {
+ cat >&2 <<- EOF
+ Usage: $0 [OPTIONS]
+ Generate self-signed server certificates
+
+ Options:
+ -t type: key type (default: rsa)
+ -b bits: key length or EC curve (default: 2048 for RSA, 1024 for DSA, secp224r1 for ECDSA)
+ -h digest: digest algorithm (default: sha1)
+ -n CN: common name (default: \$(hostname --fqdn)
+ -f force: overwrite key files if they exist
+ --pubkey: public key file (default: pubkey.pem)
+ --privkey: private key file (default: privkey.pem; created with og-rwx)
+
+ Return values:
+ 0 The key pair was successfully generated
+ 1 The public or private key file exists, and -f is not set
+ 2 The key generation failed
+ EOF
+}
+
+name=$(hostname --fqdn)
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -t) shift; type="$1";;
+ -t*) type="${1#-t}";;
+
+ -b) shift; bits="$1";;
+ -b*) bits="${1#-b}";;
+
+ -h) shift; hash="$1";;
+ -h*) hash="${1#-h}";;
+
+ -n) shift; name="$1";;
+ -n*) name="${1#-n}";;
+
+ -f) force=1;;
+ --pubkey=*) pubkey="${1#--pubkey=}";;
+ --privkey=*) privkey="${1#--privkey=}";;
+
+ --help) usage; exit;;
+ *) echo "Unrecognized argument: $1" >&2; exit 2
+ esac
+ shift;
+done
+
+rand=/dev/urandom
+case "$type" in
+ rsa) genkey=genrsa; genkeyargs="-f4 ${bits:-2048}";;
+ dsa) genkey=dsaparam; genkeyargs="-noout -genkey ${bits:-1024}";;
+ # See 'openssl ecparam -list_curves' for the list of supported
+ # curves. StrongSwan doesn't support explicit curve parameters
+ # (however explicit parameters might be required to make exotic
+ # curves work with some clients.)
+ ecdsa) genkey=ecparam
+ genkeyargs="-noout -name ${bits:-secp224r1} -param_enc named_curve -genkey";;
+ *) echo "Unrecognized key type: $type" >&2; exit 2
+esac
+
+case "$hash" in
+ md5|rmd160|sha1|sha224|sha256|sha384|sha512) ;;
+ *) echo "Invalid digest algorithm: $hash" >&2; exit 2;
+esac
+
+[ ${#name} -le 64 ] || { echo "Hostname too long: $name" >&2; exit 2; }
+for file in "$pubkey" "$privkey"; do
+ [ -z "$force" -a -s "$file" ] || continue
+ echo "Error: File exists: $file" >&2
+ exit 1
+done
+
+config=$(mktemp) || exit 2
+trap 'rm -f "$config"' EXIT
+# see /usr/share/ssl-cert/ssleay.cnf
+cat >"$config" <<- EOF
+ [ req ]
+ distinguished_name = req_distinguished_name
+ prompt = no
+ policy = policy_anything
+ req_extensions = v3_req
+ x509_extensions = v3_req
+
+ [ req_distinguished_name ]
+ commonName = $name
+
+ [ v3_req ]
+ basicConstraints = critical, CA:FALSE
+EOF
+
+# Ensure "$privkey" is created with umask 0077
+mv "$(mktemp)" "$privkey" || exit 2
+chmod og-rwx "$privkey" || exit 2
+
+openssl $genkey -rand /dev/urandom $genkeyargs >"$privkey" || exit 2
+openssl req -config "$config" -new -x509 -days 3650 -"$hash" -key "$privkey" >"$pubkey" || exit 2
diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml
index 2f4f900..89e4b6b 100644
--- a/roles/common/handlers/main.yml
+++ b/roles/common/handlers/main.yml
@@ -17,9 +17,6 @@
- name: Restart fail2ban
service: name=fail2ban state=restarted
-- name: Missing IPSec certificate
- fail: msg="strongswan IPsec is lacking public or private keys on '{{ ansible_fqdn }}'."
-
- name: Restart IPSec
service: name=ipsec state=restarted
diff --git a/roles/common/tasks/ipsec.yml b/roles/common/tasks/ipsec.yml
index 7870626..6b97ddb 100644
--- a/roles/common/tasks/ipsec.yml
+++ b/roles/common/tasks/ipsec.yml
@@ -1,33 +1,43 @@
- name: Install strongSwan
apt: pkg=strongswan-ikev2
-- name: Ensure we have our private key
- file: path=/etc/ipsec.d/private/{{ inventory_hostname }}.key
- owner=root group=root
- mode=0600
+- name: Generate a key pair for IPSec
+ command: genkeypair.sh --pubkey=/etc/ipsec.d/certs/{{ inventory_hostname }}.pem
+ --privkey=/etc/ipsec.d/private/{{ inventory_hostname }}.key
+ -n {{ inventory_hostname }}
+ -t ecdsa -b secp521r1 -h sha512
+ register: r1
+ failed_when: r1.rc > 1
+ changed_when: r1.rc == 0
notify:
- - Missing IPSec certificate
+ - Restart IPSec
-- name: Ensure we have our public key
- file: path=/etc/ipsec.d/certs/{{ inventory_hostname }}.pem
- owner=root group=root
- mode=0644
- notify:
- - Missing IPSec certificate
+- name: Fetch the public part of IPSec's host key
+ sudo: False
+ # Ensure we don't fetch private data
+ fetch: src=/etc/ipsec.d/certs/{{ inventory_hostname }}.pem
+ dest=certs/ipsec/
+ fail_on_missing=yes
+ flat=yes
-- name: Ensure we have the CA's public key
- file: path=/etc/ipsec.d/cacerts/cacert.pem
+# Don't copy our pubkey due to a possible race condition. Only the
+# remote machine has authority regarding its key.
+- name: Copy IPSec host pubkeys (except ours)
+ copy: src=certs/ipsec/{{ item }}.pem
+ dest=/etc/ipsec.d/certs/{{ item }}.pem
owner=root group=root
mode=0644
+ with_items: groups.all | difference([inventory_hostname])
+ register: r2
notify:
- - Missing IPSec certificate
+ - Restart IPSec
- name: Configure IPSec's secrets
template: src=etc/ipsec.secrets.j2
dest=/etc/ipsec.secrets
owner=root group=root
mode=0600
- register: r1
+ register: r3
notify:
- Restart IPSec
@@ -36,13 +46,13 @@
dest=/etc/ipsec.conf
owner=root group=root
mode=0644
- register: r2
+ register: r4
notify:
- Restart IPSec
- name: Start IPSec
service: name=ipsec state=started
- when: not (r1.changed or r2.changed)
+ when: not (r1.changed or r2.changed or r3.changed or r4.changed)
- name: Auto-create a dedicated interface for IPSec
copy: src=etc/network/if-up.d/ipsec
diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml
index 55feff8..f24a2c9 100644
--- a/roles/common/tasks/main.yml
+++ b/roles/common/tasks/main.yml
@@ -9,6 +9,11 @@
- include: fail2ban.yml tags=fail2ban
- include: smart.yml tags=smartmontools,smart
- include: haveged.yml tags=haveged,entropy
+- name: Copy genkeypair.sh
+ copy: src=usr/local/bin/genkeypair.sh
+ dest=/usr/local/bin/genkeypair.sh
+ owner=root group=root
+ mode=0755
- include: ipsec.yml tags=strongswan,ipsec
- include: logging.yml tags=logging
- include: ntp.yml tags=ntp
diff --git a/roles/common/templates/etc/ipsec.conf.j2 b/roles/common/templates/etc/ipsec.conf.j2
index 5ac2dd1..1dbcdbd 100644
--- a/roles/common/templates/etc/ipsec.conf.j2
+++ b/roles/common/templates/etc/ipsec.conf.j2
@@ -2,10 +2,7 @@
# Do NOT edit this file directly!
config setup
- # crlcheckinterval = 600
- strictcrlpolicy = no
- # cachecrls = yes
- plutostart = no
+ plutostart = no
# Add connections here.
@@ -22,18 +19,12 @@ conn %default
leftauth = pubkey
left = %defaultroute
leftcert = {{ inventory_hostname }}.pem
- leftid = "C=SE, O=Fripost, OU=IPsec, CN={{ inventory_hostname }}"
- leftca = "C=SE, O=Fripost, OU=root CA, CN=IPsec (internal network)"
leftfirewall = yes
rightauth = pubkey
- rightca = %same
auto = start
-
-{% for host in groups.all|sort %}
-{% if host != inventory_hostname %}
+{% for host in groups.all | difference([inventory_hostname]) | sort %}
conn {{ host }}
- right = {{ hostvars[host]['inventory_hostname'] }}
- rightid = "C=SE, O=Fripost, OU=IPsec, CN={{ hostvars[host]['inventory_hostname'] }}"
-{% endif -%}
+ right = {{ hostvars[host]['inventory_hostname'] }}
+ rightcert = {{ hostvars[host]['inventory_hostname'] }}.pem
{%- endfor %}