Merge branch 'devel-2.5'
[ganeti-local] / doc / examples / hooks / ipsec.in
1 #!/bin/bash
2
3 # Copyright (C) 2009 Google Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19
20
21 # This is an example ganeti hook that sets up an IPsec ESP link between all the
22 # nodes of a cluster for a given list of protocols.
23
24 # When run on cluster initialization it will create the shared key to be used
25 # for all the links. When run on node add/removal it will reconfigure IPsec
26 # on each node of the cluster.
27
28 set -e
29
30 LOCALSTATEDIR=@LOCALSTATEDIR@
31 SYSCONFDIR=@SYSCONFDIR@
32
33 GNTDATA=${LOCALSTATEDIR}/lib/ganeti
34
35 LOCKFILE=${LOCALSTATEDIR}/lock/ganeti_ipsec
36 CRYPTALGO=rijndael-cbc
37 KEYPATH=${GNTDATA}/ipsec.key
38 KEYSIZE=24
39 PROTOSTOSEC="icmp tcp"
40 TCPTOIGNORE="22 1811"
41 # On debian/ubuntu this file is automatically reloaded on boot
42 SETKEYCONF=${SYSCONFDIR}/ipsec-tools.conf
43 SETKEYCUSTOMCONF=${SYSCONFDIR}/ipsec-tools-custom.conf
44 AUTOMATIC_MARKER="# Automatically generated rules"
45 REGEN_KEY_WAIT=2
46
47 NODES=${GNTDATA}/ssconf_node_secondary_ips
48 MASTERNAME_FILE=${GNTDATA}/ssconf_master_node
49 MASTERIP_FILE=${GNTDATA}/ssconf_master_ip
50
51 SSHOPTS="-q -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no \
52          -oGlobalKnownHostsFile=${GNTDATA}/known_hosts"
53 SCPOPTS="-p $SSHOPTS"
54
55 CLEANUP=( )
56
57 cleanup() {
58   # Perform all registered cleanup operation
59   local i
60   for (( i=${#CLEANUP[@]}; i >= 0 ; --i )); do
61     ${CLEANUP[$i]}
62   done
63 }
64
65 acquire_lockfile() {
66   # Acquire the lockfile associated with system ipsec configuration.
67   lockfile-create "$LOCKFILE" || exit 1
68   CLEANUP+=("lockfile-remove $LOCKFILE")
69 }
70
71 update_system_ipsec() {
72   # Update system ipsec configuration.
73   # $1 : temporary location of a working configuration
74   local TMPCONF="$1"
75   acquire_lockfile
76   mv "$TMPCONF" "$SETKEYCONF"
77   setkey -f "$SETKEYCONF"
78 }
79
80 update_keyfile() {
81   # Obtain the IPsec keyfile from the master.
82   local MASTERIP=$(< "$MASTERIP_FILE")
83   scp $SCPOPTS "$MASTERIP":"$KEYPATH" "$KEYPATH"
84 }
85
86 gather_key() {
87   # Output IPsec key, if no key is present on the node
88   # obtain it from master.
89   if [[ ! -f "$KEYPATH" ]]; then
90     update_keyfile
91   fi
92   cut -d ' ' -f2 "$KEYPATH"
93 }
94
95 gather_key_seqno() {
96   # Output IPsec key sequence number, if no key is present
97   # on the node exit with error.
98   if [[ ! -f "$KEYPATH" ]]; then
99     echo 'Cannot obtain key timestamp, no key file.' >&2
100     exit 1
101   fi
102   cut -d ' ' -f1 "$KEYPATH"
103 }
104
105 update_ipsec_conf() {
106   # Generate a new IPsec configuration and update the system.
107   local TMPCONF=$(mktemp)
108   CLEANUP+=("rm -f $TMPCONF")
109   ESCAPED_HOSTNAME=$(sed 's/\./\\./g' <<< "$HOSTNAME")
110   local MYADDR=$(grep -E "^$ESCAPED_HOSTNAME\\>" "$NODES" | cut -d ' ' -f2)
111   local KEY=$(gather_key)
112   local SETKEYPATH=$(which setkey)
113
114   {
115   echo "#!$SETKEYPATH -f"
116   echo
117   echo "# Configuration for $MYADDR"
118   echo
119   echo '# This file has been automatically generated. Do not modify by hand,'
120   echo "# add your own rules to $SETKEYCUSTOMCONF instead."
121   echo
122   echo '# Flush SAD and SPD'
123   echo 'flush;'
124   echo 'spdflush;'
125   echo
126   if [[ -f "$SETKEYCUSTOMCONF" ]]; then
127     echo "# Begin custom rules from $SETKEYCUSTOMCONF"
128     cat "$SETKEYCUSTOMCONF"
129     echo "# End custom rules from $SETKEYCUSTOMCONF"
130     echo
131   fi
132   echo "$AUTOMATIC_MARKER"
133   for node in $(cut -d ' ' -f2 "$NODES") ; do
134     if [[ "$node" != "$MYADDR" ]]; then
135       # Traffic to ignore
136       for port in $TCPTOIGNORE ; do
137         echo "spdadd $MYADDR[$port] $node tcp -P out none;"
138         echo "spdadd $node $MYADDR[$port] tcp -P in none;"
139         echo "spdadd $MYADDR $node[$port] tcp -P out none;"
140         echo "spdadd $node[$port] $MYADDR tcp -P in none;"
141       done
142       # IPsec ESP rules
143       echo "add $MYADDR $node esp 0x201 -E $CRYPTALGO $KEY;"
144       echo "add $node $MYADDR esp 0x201 -E $CRYPTALGO $KEY;"
145       for proto in $PROTOSTOSEC ; do
146         echo "spdadd $MYADDR $node $proto -P out ipsec esp/transport//require;"
147         echo "spdadd $node $MYADDR $proto -P in ipsec esp/transport//require;"
148       done
149       echo
150     fi
151   done
152   } > "$TMPCONF"
153
154   chmod 400 "$TMPCONF"
155   update_system_ipsec "$TMPCONF"
156 }
157
158 regen_ipsec_conf() {
159   # Reconfigure IPsec on the system when a new key is generated
160   # on the master (assuming the current configuration is working
161   # and a new key is about to be generated on the master).
162   if [[ ! -f "$KEYPATH" ]]; then
163     echo 'Asking to regenerate with new key, but no old key.' >&2
164     exit 1
165   fi
166   local CURSEQNO=$(gather_key_seqno)
167   update_keyfile
168   local NEWSEQNO=$(gather_key_seqno)
169   while [[ $NEWSEQNO -le $CURSEQNO ]]; do
170     # Master did not update yet, wait..
171     sleep $REGEN_KEY_WAIT
172     update_keyfile
173     NEWSEQNO=$(gather_key_seqno)
174   done
175   update_ipsec_conf
176 }
177
178 clean_ipsec_conf() {
179   # Unconfigure IPsec on the system, removing the key and
180   # the rules previously generated.
181   rm -f "$KEYPATH"
182
183   local TMPCONF=$(mktemp)
184   CLEANUP+=("rm -f $TMPCONF")
185   # Remove all auto-generated rules
186   sed "/$AUTOMATIC_MARKER/q" "$SETKEYCONF" > "$TMPCONF"
187   chmod 400 "$TMPCONF"
188   update_system_ipsec "$TMPCONF"
189 }
190
191 generate_secret() {
192   # Generate a random HEX string (length specified by global variable KEYSIZE)
193   python -c "from ganeti import utils; print utils.GenerateSecret($KEYSIZE)"
194 }
195
196 gen_key() {
197   # Generate a new random key to be used for IPsec, the key is associated with
198   # a sequence number.
199   local KEY=$(generate_secret)
200   if [[ ! -f "$KEYPATH" ]]; then
201     # New environment/cluster, let's start from scratch
202     local SEQNO="0"
203   else
204     local SEQNO=$(( $(gather_key_seqno) + 1 ))
205   fi
206   local TMPKEYPATH=$(mktemp)
207   CLEANUP+=("rm -f $TMPKEYPATH")
208   echo -n "$SEQNO 0x$KEY" > "$TMPKEYPATH"
209   chmod 400 "$TMPKEYPATH"
210   mv "$TMPKEYPATH" "$KEYPATH"
211 }
212
213 trap cleanup EXIT
214
215 hooks_path="$GANETI_HOOKS_PATH"
216 if [[ ! -n "$hooks_path" ]]; then
217   echo '\$GANETI_HOOKS_PATH not specified.' >&2
218   exit 1
219 fi
220 hooks_phase="$GANETI_HOOKS_PHASE"
221 if [[ ! -n "$hooks_phase" ]]; then
222   echo '\$GANETI_HOOKS_PHASE not specified.' >&2
223   exit 1
224 fi
225
226 if [[ "$hooks_phase" = post ]]; then
227   case "$hooks_path" in
228     cluster-init)
229         gen_key
230         ;;
231     cluster-destroy)
232         clean_ipsec_conf
233         ;;
234     cluster-regenkey)
235         # This hook path is not yet implemented in Ganeti, here we suppose it
236         # runs on all the nodes.
237         MASTERNAME=$(< "$MASTERNAME_FILE")
238         if [[ "$MASTERNAME" = "$HOSTNAME" ]]; then
239           gen_key
240           update_ipsec_conf
241         else
242           regen_ipsec_conf
243         fi
244         ;;
245     node-add)
246         update_ipsec_conf
247         ;;
248     node-remove)
249         node_name="$GANETI_NODE_NAME"
250         if [[ ! -n "$node_name" ]]; then
251           echo '\$GANETI_NODE_NAME not specified.' >&2
252           exit 1
253         fi
254         if [[ "$node_name" = "$HOSTNAME" ]]; then
255           clean_ipsec_conf
256         else
257           update_ipsec_conf
258         fi
259         ;;
260     *)
261         echo "Hooks path $hooks_path is not for us." >&2
262         ;;
263   esac
264 else
265   echo "Hooks phase $hooks_phase is not for us." >&2
266 fi
267
268