Statistics
| Branch: | Tag: | Revision:

root / lib / tools / prepare_node_join.py @ fb62843c

History | View | Annotate | Download (6.9 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 OpenSSL
31 d12b9f66 Michael Hanselmann
32 d12b9f66 Michael Hanselmann
from ganeti import cli
33 d12b9f66 Michael Hanselmann
from ganeti import constants
34 d12b9f66 Michael Hanselmann
from ganeti import errors
35 d12b9f66 Michael Hanselmann
from ganeti import pathutils
36 d12b9f66 Michael Hanselmann
from ganeti import utils
37 d12b9f66 Michael Hanselmann
from ganeti import serializer
38 d12b9f66 Michael Hanselmann
from ganeti import ht
39 d12b9f66 Michael Hanselmann
from ganeti import ssh
40 d12b9f66 Michael Hanselmann
from ganeti import ssconf
41 d12b9f66 Michael Hanselmann
42 d12b9f66 Michael Hanselmann
43 1facaf11 Michael Hanselmann
_SSH_KEY_LIST_ITEM = \
44 1facaf11 Michael Hanselmann
  ht.TAnd(ht.TIsLength(3),
45 1facaf11 Michael Hanselmann
          ht.TItems([
46 1facaf11 Michael Hanselmann
            ht.TElemOf(constants.SSHK_ALL),
47 1facaf11 Michael Hanselmann
            ht.Comment("public")(ht.TNonEmptyString),
48 1facaf11 Michael Hanselmann
            ht.Comment("private")(ht.TNonEmptyString),
49 1facaf11 Michael Hanselmann
          ]))
50 1facaf11 Michael Hanselmann
51 1facaf11 Michael Hanselmann
_SSH_KEY_LIST = ht.TListOf(_SSH_KEY_LIST_ITEM)
52 d12b9f66 Michael Hanselmann
53 d12b9f66 Michael Hanselmann
_DATA_CHECK = ht.TStrictDict(False, True, {
54 d12b9f66 Michael Hanselmann
  constants.SSHS_CLUSTER_NAME: ht.TNonEmptyString,
55 d12b9f66 Michael Hanselmann
  constants.SSHS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString,
56 d12b9f66 Michael Hanselmann
  constants.SSHS_SSH_HOST_KEY: _SSH_KEY_LIST,
57 d12b9f66 Michael Hanselmann
  constants.SSHS_SSH_ROOT_KEY: _SSH_KEY_LIST,
58 d12b9f66 Michael Hanselmann
  })
59 d12b9f66 Michael Hanselmann
60 d12b9f66 Michael Hanselmann
61 d12b9f66 Michael Hanselmann
class JoinError(errors.GenericError):
62 d12b9f66 Michael Hanselmann
  """Local class for reporting errors.
63 d12b9f66 Michael Hanselmann

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

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

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

89 d12b9f66 Michael Hanselmann
  """
90 d12b9f66 Michael Hanselmann
  if args:
91 d12b9f66 Michael Hanselmann
    parser.error("No arguments are expected")
92 d12b9f66 Michael Hanselmann
93 d12b9f66 Michael Hanselmann
  return opts
94 d12b9f66 Michael Hanselmann
95 d12b9f66 Michael Hanselmann
96 0602cef3 Michael Hanselmann
def _VerifyCertificate(cert_pem, _check_fn=utils.CheckNodeCertificate):
97 d12b9f66 Michael Hanselmann
  """Verifies a certificate against the local node daemon certificate.
98 d12b9f66 Michael Hanselmann

99 0602cef3 Michael Hanselmann
  @type cert_pem: string
100 0602cef3 Michael Hanselmann
  @param cert_pem: Certificate in PEM format (no key)
101 d12b9f66 Michael Hanselmann

102 d12b9f66 Michael Hanselmann
  """
103 d12b9f66 Michael Hanselmann
  try:
104 0602cef3 Michael Hanselmann
    OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
105 d12b9f66 Michael Hanselmann
  except OpenSSL.crypto.Error, err:
106 d12b9f66 Michael Hanselmann
    pass
107 d12b9f66 Michael Hanselmann
  else:
108 d12b9f66 Michael Hanselmann
    raise JoinError("No private key may be given")
109 d12b9f66 Michael Hanselmann
110 d12b9f66 Michael Hanselmann
  try:
111 0602cef3 Michael Hanselmann
    cert = \
112 0602cef3 Michael Hanselmann
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
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 0602cef3 Michael Hanselmann
  _check_fn(cert)
118 d12b9f66 Michael Hanselmann
119 d12b9f66 Michael Hanselmann
120 d12b9f66 Michael Hanselmann
def VerifyCertificate(data, _verify_fn=_VerifyCertificate):
121 d12b9f66 Michael Hanselmann
  """Verifies cluster certificate.
122 d12b9f66 Michael Hanselmann

123 d12b9f66 Michael Hanselmann
  @type data: dict
124 d12b9f66 Michael Hanselmann

125 d12b9f66 Michael Hanselmann
  """
126 d12b9f66 Michael Hanselmann
  cert = data.get(constants.SSHS_NODE_DAEMON_CERTIFICATE)
127 d12b9f66 Michael Hanselmann
  if cert:
128 d12b9f66 Michael Hanselmann
    _verify_fn(cert)
129 d12b9f66 Michael Hanselmann
130 d12b9f66 Michael Hanselmann
131 dffa96d6 Michael Hanselmann
def VerifyClusterName(data, _verify_fn=ssconf.VerifyClusterName):
132 d12b9f66 Michael Hanselmann
  """Verifies cluster name.
133 d12b9f66 Michael Hanselmann

134 d12b9f66 Michael Hanselmann
  @type data: dict
135 d12b9f66 Michael Hanselmann

136 d12b9f66 Michael Hanselmann
  """
137 d12b9f66 Michael Hanselmann
  name = data.get(constants.SSHS_CLUSTER_NAME)
138 d12b9f66 Michael Hanselmann
  if name:
139 d12b9f66 Michael Hanselmann
    _verify_fn(name)
140 d12b9f66 Michael Hanselmann
  else:
141 d12b9f66 Michael Hanselmann
    raise JoinError("Cluster name must be specified")
142 d12b9f66 Michael Hanselmann
143 d12b9f66 Michael Hanselmann
144 d12b9f66 Michael Hanselmann
def _UpdateKeyFiles(keys, dry_run, keyfiles):
145 d12b9f66 Michael Hanselmann
  """Updates SSH key files.
146 d12b9f66 Michael Hanselmann

147 d12b9f66 Michael Hanselmann
  @type keys: sequence of tuple; (string, string, string)
148 d12b9f66 Michael Hanselmann
  @param keys: Keys to write, tuples consist of key type
149 d12b9f66 Michael Hanselmann
    (L{constants.SSHK_ALL}), public and private key
150 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
151 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
152 d12b9f66 Michael Hanselmann
  @type keyfiles: dict; (string as key, tuple with (string, string) as values)
153 d12b9f66 Michael Hanselmann
  @param keyfiles: Mapping from key types (L{constants.SSHK_ALL}) to file
154 d12b9f66 Michael Hanselmann
    names; value tuples consist of public key filename and private key filename
155 d12b9f66 Michael Hanselmann

156 d12b9f66 Michael Hanselmann
  """
157 d12b9f66 Michael Hanselmann
  assert set(keyfiles) == constants.SSHK_ALL
158 d12b9f66 Michael Hanselmann
159 340ae7da Michael Hanselmann
  for (kind, private_key, public_key) in keys:
160 340ae7da Michael Hanselmann
    (private_file, public_file) = keyfiles[kind]
161 d12b9f66 Michael Hanselmann
162 d12b9f66 Michael Hanselmann
    logging.debug("Writing %s ...", private_file)
163 d12b9f66 Michael Hanselmann
    utils.WriteFile(private_file, data=private_key, mode=0600,
164 d12b9f66 Michael Hanselmann
                    backup=True, dry_run=dry_run)
165 d12b9f66 Michael Hanselmann
166 340ae7da Michael Hanselmann
    logging.debug("Writing %s ...", public_file)
167 340ae7da Michael Hanselmann
    utils.WriteFile(public_file, data=public_key, mode=0644,
168 340ae7da Michael Hanselmann
                    backup=True, dry_run=dry_run)
169 340ae7da Michael Hanselmann
170 d12b9f66 Michael Hanselmann
171 d12b9f66 Michael Hanselmann
def UpdateSshDaemon(data, dry_run, _runcmd_fn=utils.RunCmd,
172 d12b9f66 Michael Hanselmann
                    _keyfiles=None):
173 d12b9f66 Michael Hanselmann
  """Updates SSH daemon's keys.
174 d12b9f66 Michael Hanselmann

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

177 d12b9f66 Michael Hanselmann
  @type data: dict
178 d12b9f66 Michael Hanselmann
  @param data: Input data
179 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
180 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
181 d12b9f66 Michael Hanselmann

182 d12b9f66 Michael Hanselmann
  """
183 d12b9f66 Michael Hanselmann
  keys = data.get(constants.SSHS_SSH_HOST_KEY)
184 d12b9f66 Michael Hanselmann
  if not keys:
185 d12b9f66 Michael Hanselmann
    return
186 d12b9f66 Michael Hanselmann
187 d12b9f66 Michael Hanselmann
  if _keyfiles is None:
188 ebae9e37 Michael Hanselmann
    _keyfiles = constants.SSH_DAEMON_KEYFILES
189 d12b9f66 Michael Hanselmann
190 d12b9f66 Michael Hanselmann
  logging.info("Updating SSH daemon key files")
191 d12b9f66 Michael Hanselmann
  _UpdateKeyFiles(keys, dry_run, _keyfiles)
192 d12b9f66 Michael Hanselmann
193 d12b9f66 Michael Hanselmann
  if dry_run:
194 d12b9f66 Michael Hanselmann
    logging.info("This is a dry run, not restarting SSH daemon")
195 d12b9f66 Michael Hanselmann
  else:
196 d12b9f66 Michael Hanselmann
    result = _runcmd_fn([pathutils.DAEMON_UTIL, "reload-ssh-keys"],
197 d12b9f66 Michael Hanselmann
                        interactive=True)
198 d12b9f66 Michael Hanselmann
    if result.failed:
199 d12b9f66 Michael Hanselmann
      raise JoinError("Could not reload SSH keys, command '%s'"
200 d12b9f66 Michael Hanselmann
                      " had exitcode %s and error %s" %
201 d12b9f66 Michael Hanselmann
                       (result.cmd, result.exit_code, result.output))
202 d12b9f66 Michael Hanselmann
203 d12b9f66 Michael Hanselmann
204 d12b9f66 Michael Hanselmann
def UpdateSshRoot(data, dry_run, _homedir_fn=None):
205 d12b9f66 Michael Hanselmann
  """Updates root's SSH keys.
206 d12b9f66 Michael Hanselmann

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

209 d12b9f66 Michael Hanselmann
  @type data: dict
210 d12b9f66 Michael Hanselmann
  @param data: Input data
211 d12b9f66 Michael Hanselmann
  @type dry_run: boolean
212 d12b9f66 Michael Hanselmann
  @param dry_run: Whether to perform a dry run
213 d12b9f66 Michael Hanselmann

214 d12b9f66 Michael Hanselmann
  """
215 d12b9f66 Michael Hanselmann
  keys = data.get(constants.SSHS_SSH_ROOT_KEY)
216 d12b9f66 Michael Hanselmann
  if not keys:
217 d12b9f66 Michael Hanselmann
    return
218 d12b9f66 Michael Hanselmann
219 f712208d Michael Hanselmann
  (auth_keys_file, keyfiles) = \
220 f712208d Michael Hanselmann
    ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=True,
221 f712208d Michael Hanselmann
                        _homedir_fn=_homedir_fn)
222 f712208d Michael Hanselmann
223 f712208d Michael Hanselmann
  _UpdateKeyFiles(keys, dry_run, keyfiles)
224 d12b9f66 Michael Hanselmann
225 d12b9f66 Michael Hanselmann
  if dry_run:
226 d12b9f66 Michael Hanselmann
    logging.info("This is a dry run, not modifying %s", auth_keys_file)
227 d12b9f66 Michael Hanselmann
  else:
228 910ef222 Michael Hanselmann
    for (_, _, public_key) in keys:
229 910ef222 Michael Hanselmann
      utils.AddAuthorizedKey(auth_keys_file, public_key)
230 d12b9f66 Michael Hanselmann
231 d12b9f66 Michael Hanselmann
232 d12b9f66 Michael Hanselmann
def LoadData(raw):
233 d12b9f66 Michael Hanselmann
  """Parses and verifies input data.
234 d12b9f66 Michael Hanselmann

235 d12b9f66 Michael Hanselmann
  @rtype: dict
236 d12b9f66 Michael Hanselmann

237 d12b9f66 Michael Hanselmann
  """
238 5d630c22 Michael Hanselmann
  return serializer.LoadAndVerifyJson(raw, _DATA_CHECK)
239 d12b9f66 Michael Hanselmann
240 d12b9f66 Michael Hanselmann
241 d12b9f66 Michael Hanselmann
def Main():
242 d12b9f66 Michael Hanselmann
  """Main routine.
243 d12b9f66 Michael Hanselmann

244 d12b9f66 Michael Hanselmann
  """
245 d12b9f66 Michael Hanselmann
  opts = ParseOptions()
246 d12b9f66 Michael Hanselmann
247 796b5152 Michael Hanselmann
  utils.SetupToolLogging(opts.debug, opts.verbose)
248 d12b9f66 Michael Hanselmann
249 d12b9f66 Michael Hanselmann
  try:
250 d12b9f66 Michael Hanselmann
    data = LoadData(sys.stdin.read())
251 d12b9f66 Michael Hanselmann
252 d12b9f66 Michael Hanselmann
    # Check if input data is correct
253 d12b9f66 Michael Hanselmann
    VerifyClusterName(data)
254 d12b9f66 Michael Hanselmann
    VerifyCertificate(data)
255 d12b9f66 Michael Hanselmann
256 d12b9f66 Michael Hanselmann
    # Update SSH files
257 d12b9f66 Michael Hanselmann
    UpdateSshDaemon(data, opts.dry_run)
258 d12b9f66 Michael Hanselmann
    UpdateSshRoot(data, opts.dry_run)
259 d12b9f66 Michael Hanselmann
260 d12b9f66 Michael Hanselmann
    logging.info("Setup finished successfully")
261 d12b9f66 Michael Hanselmann
  except Exception, err: # pylint: disable=W0703
262 d12b9f66 Michael Hanselmann
    logging.debug("Caught unhandled exception", exc_info=True)
263 d12b9f66 Michael Hanselmann
264 d12b9f66 Michael Hanselmann
    (retcode, message) = cli.FormatError(err)
265 d12b9f66 Michael Hanselmann
    logging.error(message)
266 d12b9f66 Michael Hanselmann
267 d12b9f66 Michael Hanselmann
    return retcode
268 d12b9f66 Michael Hanselmann
  else:
269 d12b9f66 Michael Hanselmann
    return constants.EXIT_SUCCESS