diff --git a/doc/examples/hooks/ipsec.in b/doc/examples/hooks/ipsec.in new file mode 100755 index 0000000000000000000000000000000000000000..5c9db2e0e8915a7ad823e1732f32a688c52550c8 --- /dev/null +++ b/doc/examples/hooks/ipsec.in @@ -0,0 +1,268 @@ +#!/bin/bash + +# Copyright (C) 2009 Google Inc. +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + + +# This is an example ganeti hook that sets up an IPsec ESP link between all the +# nodes of a cluster for a given list of protocols. + +# When run on cluster initialization it will create the shared key to be used +# for all the links. When run on node add/removal it will reconfigure IPsec +# on each node of the cluster. + +set -e + +LOCALSTATEDIR=@LOCALSTATEDIR@ +SYSCONFDIR=@SYSCONFDIR@ + +GNTDATA=${LOCALSTATEDIR}/lib/ganeti + +LOCKFILE=${LOCALSTATEDIR}/lock/ganeti_ipsec +CRYPTALGO=rijndael-cbc +KEYPATH=${GNTDATA}/ipsec.key +KEYSIZE=24 +PROTOSTOSEC="icmp tcp" +TCPTOIGNORE="22 1811" +# On debian/ubuntu this file is automatically reloaded on boot +SETKEYCONF=${SYSCONFDIR}/ipsec-tools.conf +SETKEYCUSTOMCONF=${SYSCONFDIR}/ipsec-tools-custom.conf +AUTOMATIC_MARKER="# Automatically generated rules" +REGEN_KEY_WAIT=2 + +NODES=${GNTDATA}/ssconf_node_secondary_ips +MASTERNAME_FILE=${GNTDATA}/ssconf_master_node +MASTERIP_FILE=${GNTDATA}/ssconf_master_ip + +SSHOPTS="-q -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no \ + -oGlobalKnownHostsFile=${GNTDATA}/known_hosts" +SCPOPTS="-p $SSHOPTS" + +CLEANUP=( ) + +cleanup() { + # Perform all registered cleanup operation + local i + for (( i=${#CLEANUP[@]}; i >= 0 ; --i )); do + ${CLEANUP[$i]} + done +} + +acquire_lockfile() { + # Acquire the lockfile associated with system ipsec configuration. + lockfile-create "$LOCKFILE" || exit 1 + CLEANUP+=("lockfile-remove $LOCKFILE") +} + +update_system_ipsec() { + # Update system ipsec configuration. + # $1 : temporary location of a working configuration + local TMPCONF="$1" + acquire_lockfile + mv "$TMPCONF" "$SETKEYCONF" + setkey -f "$SETKEYCONF" +} + +update_keyfile() { + # Obtain the IPsec keyfile from the master. + local MASTERIP=$(< "$MASTERIP_FILE") + scp $SCPOPTS "$MASTERIP":"$KEYPATH" "$KEYPATH" +} + +gather_key() { + # Output IPsec key, if no key is present on the node + # obtain it from master. + if [[ ! -f "$KEYPATH" ]]; then + update_keyfile + fi + cut -d ' ' -f2 "$KEYPATH" +} + +gather_key_seqno() { + # Output IPsec key sequence number, if no key is present + # on the node exit with error. + if [[ ! -f "$KEYPATH" ]]; then + echo 'Cannot obtain key timestamp, no key file.' >&2 + exit 1 + fi + cut -d ' ' -f1 "$KEYPATH" +} + +update_ipsec_conf() { + # Generate a new IPsec configuration and update the system. + local TMPCONF=$(mktemp) + CLEANUP+=("rm -f $TMPCONF") + ESCAPED_HOSTNAME=$(sed 's/\./\\./g' <<< "$HOSTNAME") + local MYADDR=$(grep -E "^$ESCAPED_HOSTNAME\\>" "$NODES" | cut -d ' ' -f2) + local KEY=$(gather_key) + local SETKEYPATH=$(which setkey) + + { + echo "#!$SETKEYPATH -f" + echo + echo "# Configuration for $MYADDR" + echo + echo '# This file has been automatically generated. Do not modify by hand,' + echo "# add your own rules to $SETKEYCUSTOMCONF instead." + echo + echo '# Flush SAD and SPD' + echo 'flush;' + echo 'spdflush;' + echo + if [[ -f "$SETKEYCUSTOMCONF" ]]; then + echo "# Begin custom rules from $SETKEYCUSTOMCONF" + cat "$SETKEYCUSTOMCONF" + echo "# End custom rules from $SETKEYCUSTOMCONF" + echo + fi + echo "$AUTOMATIC_MARKER" + for node in $(cut -d ' ' -f2 "$NODES") ; do + if [[ "$node" != "$MYADDR" ]]; then + # Traffic to ignore + for port in $TCPTOIGNORE ; do + echo "spdadd $MYADDR[$port] $node tcp -P out none;" + echo "spdadd $node $MYADDR[$port] tcp -P in none;" + echo "spdadd $MYADDR $node[$port] tcp -P out none;" + echo "spdadd $node[$port] $MYADDR tcp -P in none;" + done + # IPsec ESP rules + echo "add $MYADDR $node esp 0x201 -E $CRYPTALGO $KEY;" + echo "add $node $MYADDR esp 0x201 -E $CRYPTALGO $KEY;" + for proto in $PROTOSTOSEC ; do + echo "spdadd $MYADDR $node $proto -P out ipsec esp/transport//require;" + echo "spdadd $node $MYADDR $proto -P in ipsec esp/transport//require;" + done + echo + fi + done + } > "$TMPCONF" + + chmod 400 "$TMPCONF" + update_system_ipsec "$TMPCONF" +} + +regen_ipsec_conf() { + # Reconfigure IPsec on the system when a new key is generated + # on the master (assuming the current configuration is working + # and a new key is about to be generated on the master). + if [[ ! -f "$KEYPATH" ]]; then + echo 'Asking to regenerate with new key, but no old key.' >&2 + exit 1 + fi + local CURSEQNO=$(gather_key_seqno) + update_keyfile + local NEWSEQNO=$(gather_key_seqno) + while [[ $NEWSEQNO -le $CURSEQNO ]]; do + # Master did not update yet, wait.. + sleep $REGEN_KEY_WAIT + update_keyfile + NEWSEQNO=$(gather_key_seqno) + done + update_ipsec_conf +} + +clean_ipsec_conf() { + # Unconfigure IPsec on the system, removing the key and + # the rules previously generated. + rm -f "$KEYPATH" + + local TMPCONF=$(mktemp) + CLEANUP+=("rm -f $TMPCONF") + # Remove all auto-generated rules + sed "/$AUTOMATIC_MARKER/q" "$SETKEYCONF" > "$TMPCONF" + chmod 400 "$TMPCONF" + update_system_ipsec "$TMPCONF" +} + +generate_secret() { + # Generate a random HEX string (length specified by global variable KEYSIZE) + python -c "from ganeti import utils; print utils.GenerateSecret($KEYSIZE)" +} + +gen_key() { + # Generate a new random key to be used for IPsec, the key is associated with + # a sequence number. + local KEY=$(generate_secret) + if [[ ! -f "$KEYPATH" ]]; then + # New environment/cluster, let's start from scratch + local SEQNO="0" + else + local SEQNO=$(( $(gather_key_seqno) + 1 )) + fi + local TMPKEYPATH=$(mktemp) + CLEANUP+=("rm -f $TMPKEYPATH") + echo -n "$SEQNO 0x$KEY" > "$TMPKEYPATH" + chmod 400 "$TMPKEYPATH" + mv "$TMPKEYPATH" "$KEYPATH" +} + +trap cleanup EXIT + +hooks_path="$GANETI_HOOKS_PATH" +if [[ ! -n "$hooks_path" ]]; then + echo '\$GANETI_HOOKS_PATH not specified.' >&2 + exit 1 +fi +hooks_phase="$GANETI_HOOKS_PHASE" +if [[ ! -n "$hooks_phase" ]]; then + echo '\$GANETI_HOOKS_PHASE not specified.' >&2 + exit 1 +fi + +if [[ "$hooks_phase" = post ]]; then + case "$hooks_path" in + cluster-init) + gen_key + ;; + cluster-destroy) + clean_ipsec_conf + ;; + cluster-regenkey) + # This hook path is not yet implemented in Ganeti, here we suppose it + # runs on all the nodes. + MASTERNAME=$(< "$MASTERNAME_FILE") + if [[ "$MASTERNAME" = "$HOSTNAME" ]]; then + gen_key + update_ipsec_conf + else + regen_ipsec_conf + fi + ;; + node-add) + update_ipsec_conf + ;; + node-remove) + node_name="$GANETI_NODE_NAME" + if [[ ! -n "$node_name" ]]; then + echo '\$GANETI_NODE_NAME not specified.' >&2 + exit 1 + fi + if [[ "$node_name" = "$HOSTNAME" ]]; then + clean_ipsec_conf + else + update_ipsec_conf + fi + ;; + *) + echo "Hooks path $hooks_path is not for us." >&2 + ;; + esac +else + echo "Hooks phase $hooks_phase is not for us." >&2 +fi + +