diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2014-06-25 05:22:58 +0200 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2015-06-07 02:51:51 +0200 |
commit | a4d0e4a7f8cd829de8346fb6edd9866cc855134f (patch) | |
tree | 2b66a0fb217b9fc200dcaaa51ca426283318ff58 /roles/common | |
parent | 01abd3dbf8e357fd71ebfa41519dc4d1f4bc0bd8 (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/common')
-rwxr-xr-x | roles/common/files/usr/local/bin/genkeypair.sh | 128 | ||||
-rw-r--r-- | roles/common/handlers/main.yml | 3 | ||||
-rw-r--r-- | roles/common/tasks/ipsec.yml | 44 | ||||
-rw-r--r-- | roles/common/tasks/main.yml | 5 | ||||
-rw-r--r-- | roles/common/templates/etc/ipsec.conf.j2 | 17 |
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 %} |