root / doc / examples / hooks / ipsec.in @ ab6536ba
History | View | Annotate | Download (7.4 kB)
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 |
|