blob: 978cedf08c6ee937de712218729ef2b762f26f5a [file] [log] [blame]
#!/bin/bash
# Copyright (C) 2009 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# 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