Statistics
| Branch: | Tag: | Revision:

root / doc / examples / hooks / ipsec.in @ d1e95dde

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