| #!/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 |
| |
| |