Statistics
| Branch: | Tag: | Revision:

root / lib / tools / prepare_node_join.py @ 796b5152

History | View | Annotate | Download (8.4 kB)

1 d12b9f66 Michael Hanselmann
#
2 d12b9f66 Michael Hanselmann
#
3 d12b9f66 Michael Hanselmann
4 d12b9f66 Michael Hanselmann
# Copyright (C) 2012 Google Inc.
5 d12b9f66 Michael Hanselmann
#
6 d12b9f66 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 d12b9f66 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 d12b9f66 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 d12b9f66 Michael Hanselmann
# (at your option) any later version.
10 d12b9f66 Michael Hanselmann
#
11 d12b9f66 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 d12b9f66 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 d12b9f66 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 d12b9f66 Michael Hanselmann
# General Public License for more details.
15 d12b9f66 Michael Hanselmann
#
16 d12b9f66 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 d12b9f66 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 d12b9f66 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 d12b9f66 Michael Hanselmann
# 02110-1301, USA.
20 d12b9f66 Michael Hanselmann
21 d12b9f66 Michael Hanselmann
"""Script to prepare a node for joining a cluster.
22 d12b9f66 Michael Hanselmann

23 d12b9f66 Michael Hanselmann
"""
24 d12b9f66 Michael Hanselmann
25 d12b9f66 Michael Hanselmann
import os
26 d12b9f66 Michael Hanselmann
import os.path
27 d12b9f66 Michael Hanselmann
import optparse
28 d12b9f66 Michael Hanselmann
import sys
29 d12b9f66 Michael Hanselmann
import logging
30 d12b9f66 Michael Hanselmann
import errno
31 d12b9f66 Michael Hanselmann
import OpenSSL
32 d12b9f66 Michael Hanselmann
33 d12b9f66 Michael Hanselmann
from ganeti import cli
34 d12b9f66 Michael Hanselmann
from ganeti import constants
35 d12b9f66 Michael Hanselmann
from ganeti import errors
36 d12b9f66 Michael Hanselmann
from ganeti import pathutils
37 d12b9f66 Michael Hanselmann
from ganeti import utils
38 d12b9f66 Michael Hanselmann
from ganeti import serializer
39 d12b9f66 Michael Hanselmann
from ganeti import ht
40 d12b9f66 Michael Hanselmann
from ganeti import ssh
41 d12b9f66 Michael Hanselmann
from ganeti import ssconf
42 d12b9f66 Michael Hanselmann
43 d12b9f66 Michael Hanselmann
44 1facaf11 Michael Hanselmann
_SSH_KEY_LIST_ITEM = \
45 1facaf11 Michael Hanselmann
  ht.TAnd(ht.TIsLength(3),
46 1facaf11 Michael Hanselmann
          ht.TItems([
47 1facaf11 Michael Hanselmann
            ht.TElemOf(constants.SSHK_ALL),
48 1facaf11 Michael Hanselmann
            ht.Comment("public")(ht.TNonEmptyString),
49 1facaf11 Michael Hanselmann
            ht.Comment("private")(ht.TNonEmptyString),
50 1facaf11 Michael Hanselmann
          ]))
51 1facaf11 Michael Hanselmann
52 1facaf11 Michael Hanselmann
_SSH_KEY_LIST = ht.TListOf(_SSH_KEY_LIST_ITEM)
53 d12b9f66 Michael Hanselmann
54 d12b9f66 Michael Hanselmann
_DATA_CHECK = ht.TStrictDict(False, True, {
55 d12b9f66 Michael Hanselmann
  constants.SSHS_CLUSTER_NAME: ht.TNonEmptyString,
56 d12b9f66 Michael Hanselmann
  constants.SSHS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString,
57 d12b9f66 Michael Hanselmann
  constants.SSHS_SSH_HOST_KEY: _SSH_KEY_LIST,
58 d12b9f66 Michael Hanselmann
  constants.SSHS_SSH_ROOT_KEY: _SSH_KEY_LIST,
59 d12b9f66 Michael Hanselmann
  })
60 d12b9f66 Michael Hanselmann
61 d12b9f66 Michael Hanselmann
62 d12b9f66 Michael Hanselmann
class JoinError(errors.GenericError):
63 d12b9f66 Michael Hanselmann
  """Local class for reporting errors.
64 d12b9f66 Michael Hanselmann

65 d12b9f66 Michael Hanselmann
  """
66 d12b9f66 Michael Hanselmann
67 d12b9f66 Michael Hanselmann
68 d12b9f66 Michael Hanselmann
def ParseOptions():
69 d12b9f66 Michael Hanselmann
  """Parses the options passed to the program.
70 d12b9f66 Michael Hanselmann

71 d12b9f66 Michael Hanselmann
  @return: Options and arguments
72 d12b9f66 Michael Hanselmann

73 d12b9f66 Michael Hanselmann
  """
74 d12b9f66 Michael Hanselmann
  program = os.path.basename(sys.argv[0])
75 d12b9f66 Michael Hanselmann
76 d12b9f66 Michael Hanselmann
  parser = optparse.OptionParser(usage="%prog [--dry-run]",
77 d12b9f66 Michael Hanselmann
                                 prog=program)
78 d12b9f66 Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
79 d12b9f66 Michael Hanselmann
  parser.add_option(cli.VERBOSE_OPT)
80 d12b9f66 Michael Hanselmann
  parser.add_option(cli.DRY_RUN_OPT)
81 d12b9f66 Michael Hanselmann
82 d12b9f66 Michael Hanselmann
  (opts, args) = parser.parse_args()
83 d12b9f66 Michael Hanselmann
84 d12b9f66 Michael Hanselmann
  return VerifyOptions(parser, opts, args)
85 d12b9f66 Michael Hanselmann
86 d12b9f66 Michael Hanselmann
87 d12b9f66 Michael Hanselmann
def VerifyOptions(parser, opts, args):
88 d12b9f66 Michael Hanselmann
  """Verifies options and arguments for correctness.
89 d12b9f66 Michael Hanselmann

90 d12b9f66 Michael Hanselmann
  """
91 d12b9f66 Michael Hanselmann
  if args:
92 d12b9f66 Michael Hanselmann
    parser.error("No arguments are expected")
93 d12b9f66 Michael Hanselmann
94 d12b9f66 Michael Hanselmann
  return opts
95 d12b9f66 Michael Hanselmann
96 d12b9f66 Michael Hanselmann
97 d12b9f66 Michael Hanselmann
def _VerifyCertificate(cert, _noded_cert_file=pathutils.NODED_CERT_FILE):
98 d12b9f66 Michael Hanselmann
  """Verifies a certificate against the local node daemon certificate.
99 d12b9f66 Michael Hanselmann

100 d12b9f66 Michael Hanselmann
  @type cert: string
101 d12b9f66 Michael Hanselmann
  @param cert: Certificate in PEM format (no key)
102 d12b9f66 Michael Hanselmann

103 d12b9f66 Michael Hanselmann
  """
104 d12b9f66 Michael Hanselmann
  try:
105 d12b9f66 Michael Hanselmann
    OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert)
106 d12b9f66 Michael Hanselmann
  except OpenSSL.crypto.Error, err:
107 d12b9f66 Michael Hanselmann
    pass
108 d12b9f66 Michael Hanselmann
  else:
109 d12b9f66 Michael Hanselmann
    raise JoinError("No private key may be given")
110 d12b9f66 Michael Hanselmann
111 d12b9f66 Michael Hanselmann
  try:
112 d12b9f66 Michael Hanselmann
    cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
113 d12b9f66 Michael Hanselmann
  except Exception, err:
114 d12b9f66 Michael Hanselmann
    raise errors.X509CertError("(stdin)",
115 d12b9f66 Michael Hanselmann
                               "Unable to load certificate: %s" % err)
116 d12b9f66 Michael Hanselmann
117 d12b9f66 Michael Hanselmann
  try:
118 d12b9f66 Michael Hanselmann
    noded_pem = utils.ReadFile(_noded_cert_file)
119 d12b9f66 Michael Hanselmann
  except EnvironmentError, err:
120 d12b9f66 Michael Hanselmann
    if err.errno != errno.ENOENT:
121 d12b9f66 Michael Hanselmann
      raise
122 d12b9f66 Michael Hanselmann
123 d12b9f66 Michael Hanselmann
    logging.debug("Local node certificate was not found (file %s)",
124 d12b9f66 Michael Hanselmann
                  _noded_cert_file)
125 d12b9f66 Michael Hanselmann
    return
126 d12b9f66 Michael Hanselmann
127 d12b9f66 Michael Hanselmann
  try:
128 d12b9f66 Michael Hanselmann
    key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, noded_pem)
129 d12b9f66 Michael Hanselmann
  except Exception, err:
130 d12b9f66 Michael Hanselmann
    raise errors.X509CertError(_noded_cert_file,
131 d12b9f66 Michael Hanselmann
                               "Unable to load private key: %s" % err)
132 d12b9f66 Michael Hanselmann
133 d12b9f66 Michael Hanselmann
  ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
134 d12b9f66 Michael Hanselmann
  ctx.use_privatekey(key)
135 d12b9f66 Michael Hanselmann
  ctx.use_certificate(cert)
136 d12b9f66 Michael Hanselmann
  try:
137 d12b9f66 Michael Hanselmann
    ctx.check_privatekey()
138 d12b9f66 Michael Hanselmann
  except OpenSSL.SSL.Error:
139 d12b9f66 Michael Hanselmann
    raise JoinError("Given cluster certificate does not match local key")
140 d12b9f66 Michael Hanselmann
141 d12b9f66 Michael Hanselmann
142 d12b9f66 Michael Hanselmann
def VerifyCertificate(data, _verify_fn=_VerifyCertificate):
143 d12b9f66 Michael Hanselmann
  """Verifies cluster certificate.
144 d12b9f66 Michael Hanselmann

145 d12b9f66 Michael Hanselmann
  @type data: dict
146 d12b9f66 Michael Hanselmann

147 d12b9f66 Michael Hanselmann
  """
148 d12b9f66 Michael Hanselmann
  cert = data.get(constants.SSHS_NODE_DAEMON_CERTIFICATE)
149 d12b9f66 Michael Hanselmann
  if cert:
150 d12b9f66 Michael Hanselmann
    _verify_fn(cert)
151 d12b9f66 Michael Hanselmann
152 d12b9f66 Michael Hanselmann
153 d12b9f66 Michael Hanselmann
def _VerifyClusterName(name, _ss_cluster_name_file=None):
154 d12b9f66 Michael Hanselmann
  """Verifies cluster name against a local cluster name.
155 d12b9f66 Michael Hanselmann

156 d12b9f66 Michael Hanselmann
  @type name: string
157 d12b9f66 Michael Hanselmann
  @param name: Cluster name
158 d12b9f66 Michael Hanselmann

159 d12b9f66 Michael Hanselmann
  """
160 d12b9f66 Michael Hanselmann
  if _ss_cluster_name_file is None:
161 d12b9f66 Michael Hanselmann
    _ss_cluster_name_file = \
162 d12b9f66 Michael Hanselmann
      ssconf.SimpleStore().KeyToFilename(constants.SS_CLUSTER_NAME)
163 d12b9f66 Michael Hanselmann
164 d12b9f66 Michael Hanselmann
  try:
165 d12b9f66 Michael Hanselmann
    local_name = utils.ReadOneLineFile(_ss_cluster_name_file)
166 d12b9f66 Michael Hanselmann
  except EnvironmentError, err:
167 d12b9f66 Michael Hanselmann
    if err.errno != errno.ENOENT:
168 d12b9f66 Michael Hanselmann
      raise
169 d12b9f66 Michael Hanselmann
170 d12b9f66 Michael Hanselmann
    logging.debug("Local cluster name was not found (file %s)",
171 d12b9f66 Michael Hanselmann
                  _ss_cluster_name_file)
172 d12b9f66 Michael Hanselmann
  else:
173 d12b9f66 Michael Hanselmann
    if name != local_name:
174 d12b9f66 Michael Hanselmann
      raise JoinError("Current cluster name is '%s'" % local_name)
175 d12b9f66 Michael Hanselmann
176 d12b9f66 Michael Hanselmann
177 d12b9f66 Michael Hanselmann
def VerifyClusterName(data, _verify_fn=_VerifyClusterName):
178 d12b9f66 Michael Hanselmann
  """Verifies cluster name.
179 d12b9f66 Michael Hanselmann

180 d12b9f66 Michael Hanselmann
  @type data: dict
181 d12b9f66 Michael Hanselmann

182 d12b9f66 Michael Hanselmann
  """
183 d12b9f66 Michael Hanselmann
  name = data.get(constants.SSHS_CLUSTER_NAME)
184 d12b9f66 Michael Hanselmann
  if name:
185 d12b9f66 Michael Hanselmann
    _verify_fn(name)
186 d12b9f66 Michael Hanselmann
  else:
187 d12b9f66 Michael Hanselmann
    raise JoinError("Cluster name must be specified")
188 d12b9f66 Michael Hanselmann
189 d12b9f66 Michael Hanselmann
190 d12b9f66 Michael Hanselmann
def _UpdateKeyFiles(keys, dry_run, keyfiles):
191 d12b9f66 Michael Hanselmann
  """Updates SSH key files.
192 d12b9f66 Michael Hanselmann

193 d12b9f66 Michael Hanselmann
  @type keys: sequence of tuple; (string, string, string)
194 d12b9f66 Michael Hanselmann
  @param keys: Keys to write, tuples consist of key type
195 d12b9f66 Michael Hanselmann
    (L{constants.SSHK_ALL}), public and private key
196 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
197 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
198 d12b9f66 Michael Hanselmann
  @type keyfiles: dict; (string as key, tuple with (string, string) as values)
199 d12b9f66 Michael Hanselmann
  @param keyfiles: Mapping from key types (L{constants.SSHK_ALL}) to file
200 d12b9f66 Michael Hanselmann
    names; value tuples consist of public key filename and private key filename
201 d12b9f66 Michael Hanselmann

202 d12b9f66 Michael Hanselmann
  """
203 d12b9f66 Michael Hanselmann
  assert set(keyfiles) == constants.SSHK_ALL
204 d12b9f66 Michael Hanselmann
205 340ae7da Michael Hanselmann
  for (kind, private_key, public_key) in keys:
206 340ae7da Michael Hanselmann
    (private_file, public_file) = keyfiles[kind]
207 d12b9f66 Michael Hanselmann
208 d12b9f66 Michael Hanselmann
    logging.debug("Writing %s ...", private_file)
209 d12b9f66 Michael Hanselmann
    utils.WriteFile(private_file, data=private_key, mode=0600,
210 d12b9f66 Michael Hanselmann
                    backup=True, dry_run=dry_run)
211 d12b9f66 Michael Hanselmann
212 340ae7da Michael Hanselmann
    logging.debug("Writing %s ...", public_file)
213 340ae7da Michael Hanselmann
    utils.WriteFile(public_file, data=public_key, mode=0644,
214 340ae7da Michael Hanselmann
                    backup=True, dry_run=dry_run)
215 340ae7da Michael Hanselmann
216 d12b9f66 Michael Hanselmann
217 d12b9f66 Michael Hanselmann
def UpdateSshDaemon(data, dry_run, _runcmd_fn=utils.RunCmd,
218 d12b9f66 Michael Hanselmann
                    _keyfiles=None):
219 d12b9f66 Michael Hanselmann
  """Updates SSH daemon's keys.
220 d12b9f66 Michael Hanselmann

221 d12b9f66 Michael Hanselmann
  Unless C{dry_run} is set, the daemon is restarted at the end.
222 d12b9f66 Michael Hanselmann

223 d12b9f66 Michael Hanselmann
  @type data: dict
224 d12b9f66 Michael Hanselmann
  @param data: Input data
225 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
226 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
227 d12b9f66 Michael Hanselmann

228 d12b9f66 Michael Hanselmann
  """
229 d12b9f66 Michael Hanselmann
  keys = data.get(constants.SSHS_SSH_HOST_KEY)
230 d12b9f66 Michael Hanselmann
  if not keys:
231 d12b9f66 Michael Hanselmann
    return
232 d12b9f66 Michael Hanselmann
233 d12b9f66 Michael Hanselmann
  if _keyfiles is None:
234 ebae9e37 Michael Hanselmann
    _keyfiles = constants.SSH_DAEMON_KEYFILES
235 d12b9f66 Michael Hanselmann
236 d12b9f66 Michael Hanselmann
  logging.info("Updating SSH daemon key files")
237 d12b9f66 Michael Hanselmann
  _UpdateKeyFiles(keys, dry_run, _keyfiles)
238 d12b9f66 Michael Hanselmann
239 d12b9f66 Michael Hanselmann
  if dry_run:
240 d12b9f66 Michael Hanselmann
    logging.info("This is a dry run, not restarting SSH daemon")
241 d12b9f66 Michael Hanselmann
  else:
242 d12b9f66 Michael Hanselmann
    result = _runcmd_fn([pathutils.DAEMON_UTIL, "reload-ssh-keys"],
243 d12b9f66 Michael Hanselmann
                        interactive=True)
244 d12b9f66 Michael Hanselmann
    if result.failed:
245 d12b9f66 Michael Hanselmann
      raise JoinError("Could not reload SSH keys, command '%s'"
246 d12b9f66 Michael Hanselmann
                      " had exitcode %s and error %s" %
247 d12b9f66 Michael Hanselmann
                       (result.cmd, result.exit_code, result.output))
248 d12b9f66 Michael Hanselmann
249 d12b9f66 Michael Hanselmann
250 d12b9f66 Michael Hanselmann
def UpdateSshRoot(data, dry_run, _homedir_fn=None):
251 d12b9f66 Michael Hanselmann
  """Updates root's SSH keys.
252 d12b9f66 Michael Hanselmann

253 d12b9f66 Michael Hanselmann
  Root's C{authorized_keys} file is also updated with new public keys.
254 d12b9f66 Michael Hanselmann

255 d12b9f66 Michael Hanselmann
  @type data: dict
256 d12b9f66 Michael Hanselmann
  @param data: Input data
257 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
258 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
259 d12b9f66 Michael Hanselmann

260 d12b9f66 Michael Hanselmann
  """
261 d12b9f66 Michael Hanselmann
  keys = data.get(constants.SSHS_SSH_ROOT_KEY)
262 d12b9f66 Michael Hanselmann
  if not keys:
263 d12b9f66 Michael Hanselmann
    return
264 d12b9f66 Michael Hanselmann
265 f712208d Michael Hanselmann
  (auth_keys_file, keyfiles) = \
266 f712208d Michael Hanselmann
    ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=True,
267 f712208d Michael Hanselmann
                        _homedir_fn=_homedir_fn)
268 f712208d Michael Hanselmann
269 f712208d Michael Hanselmann
  _UpdateKeyFiles(keys, dry_run, keyfiles)
270 d12b9f66 Michael Hanselmann
271 d12b9f66 Michael Hanselmann
  if dry_run:
272 d12b9f66 Michael Hanselmann
    logging.info("This is a dry run, not modifying %s", auth_keys_file)
273 d12b9f66 Michael Hanselmann
  else:
274 910ef222 Michael Hanselmann
    for (_, _, public_key) in keys:
275 910ef222 Michael Hanselmann
      utils.AddAuthorizedKey(auth_keys_file, public_key)
276 d12b9f66 Michael Hanselmann
277 d12b9f66 Michael Hanselmann
278 d12b9f66 Michael Hanselmann
def LoadData(raw):
279 d12b9f66 Michael Hanselmann
  """Parses and verifies input data.
280 d12b9f66 Michael Hanselmann

281 d12b9f66 Michael Hanselmann
  @rtype: dict
282 d12b9f66 Michael Hanselmann

283 d12b9f66 Michael Hanselmann
  """
284 d12b9f66 Michael Hanselmann
  try:
285 d12b9f66 Michael Hanselmann
    data = serializer.LoadJson(raw)
286 d12b9f66 Michael Hanselmann
  except Exception, err:
287 d12b9f66 Michael Hanselmann
    raise errors.ParseError("Can't parse input data: %s" % err)
288 d12b9f66 Michael Hanselmann
289 d12b9f66 Michael Hanselmann
  if not _DATA_CHECK(data):
290 d12b9f66 Michael Hanselmann
    raise errors.ParseError("Input data does not match expected format: %s" %
291 d12b9f66 Michael Hanselmann
                            _DATA_CHECK)
292 d12b9f66 Michael Hanselmann
293 d12b9f66 Michael Hanselmann
  return data
294 d12b9f66 Michael Hanselmann
295 d12b9f66 Michael Hanselmann
296 d12b9f66 Michael Hanselmann
def Main():
297 d12b9f66 Michael Hanselmann
  """Main routine.
298 d12b9f66 Michael Hanselmann

299 d12b9f66 Michael Hanselmann
  """
300 d12b9f66 Michael Hanselmann
  opts = ParseOptions()
301 d12b9f66 Michael Hanselmann
302 796b5152 Michael Hanselmann
  utils.SetupToolLogging(opts.debug, opts.verbose)
303 d12b9f66 Michael Hanselmann
304 d12b9f66 Michael Hanselmann
  try:
305 d12b9f66 Michael Hanselmann
    data = LoadData(sys.stdin.read())
306 d12b9f66 Michael Hanselmann
307 d12b9f66 Michael Hanselmann
    # Check if input data is correct
308 d12b9f66 Michael Hanselmann
    VerifyClusterName(data)
309 d12b9f66 Michael Hanselmann
    VerifyCertificate(data)
310 d12b9f66 Michael Hanselmann
311 d12b9f66 Michael Hanselmann
    # Update SSH files
312 d12b9f66 Michael Hanselmann
    UpdateSshDaemon(data, opts.dry_run)
313 d12b9f66 Michael Hanselmann
    UpdateSshRoot(data, opts.dry_run)
314 d12b9f66 Michael Hanselmann
315 d12b9f66 Michael Hanselmann
    logging.info("Setup finished successfully")
316 d12b9f66 Michael Hanselmann
  except Exception, err: # pylint: disable=W0703
317 d12b9f66 Michael Hanselmann
    logging.debug("Caught unhandled exception", exc_info=True)
318 d12b9f66 Michael Hanselmann
319 d12b9f66 Michael Hanselmann
    (retcode, message) = cli.FormatError(err)
320 d12b9f66 Michael Hanselmann
    logging.error(message)
321 d12b9f66 Michael Hanselmann
322 d12b9f66 Michael Hanselmann
    return retcode
323 d12b9f66 Michael Hanselmann
  else:
324 d12b9f66 Michael Hanselmann
    return constants.EXIT_SUCCESS