Package ganeti :: Package tools :: Module prepare_node_join
[hide private]
[frames] | no frames]

Source Code for Module ganeti.tools.prepare_node_join

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2012 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30  """Script to prepare a node for joining a cluster. 
 31   
 32  """ 
 33   
 34  import os 
 35  import os.path 
 36  import optparse 
 37  import sys 
 38  import logging 
 39  import OpenSSL 
 40   
 41  from ganeti import cli 
 42  from ganeti import constants 
 43  from ganeti import errors 
 44  from ganeti import pathutils 
 45  from ganeti import utils 
 46  from ganeti import ht 
 47  from ganeti import ssh 
 48  from ganeti.tools import common 
 49   
 50   
 51  _SSH_KEY_LIST_ITEM = \ 
 52    ht.TAnd(ht.TIsLength(3), 
 53            ht.TItems([ 
 54              ht.TElemOf(constants.SSHK_ALL), 
 55              ht.Comment("public")(ht.TNonEmptyString), 
 56              ht.Comment("private")(ht.TNonEmptyString), 
 57            ])) 
 58   
 59  _SSH_KEY_LIST = ht.TListOf(_SSH_KEY_LIST_ITEM) 
 60   
 61  _DATA_CHECK = ht.TStrictDict(False, True, { 
 62    constants.SSHS_CLUSTER_NAME: ht.TNonEmptyString, 
 63    constants.SSHS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString, 
 64    constants.SSHS_SSH_HOST_KEY: _SSH_KEY_LIST, 
 65    constants.SSHS_SSH_ROOT_KEY: _SSH_KEY_LIST, 
 66    }) 
 67   
 68   
69 -class JoinError(errors.GenericError):
70 """Local class for reporting errors. 71 72 """
73 74
75 -def ParseOptions():
76 """Parses the options passed to the program. 77 78 @return: Options and arguments 79 80 """ 81 program = os.path.basename(sys.argv[0]) 82 83 parser = optparse.OptionParser(usage="%prog [--dry-run]", 84 prog=program) 85 parser.add_option(cli.DEBUG_OPT) 86 parser.add_option(cli.VERBOSE_OPT) 87 parser.add_option(cli.DRY_RUN_OPT) 88 89 (opts, args) = parser.parse_args() 90 91 return common.VerifyOptions(parser, opts, args)
92 93
94 -def _VerifyCertificate(cert_pem, _check_fn=utils.CheckNodeCertificate):
95 """Verifies a certificate against the local node daemon certificate. 96 97 @type cert_pem: string 98 @param cert_pem: Certificate in PEM format (no key) 99 100 """ 101 try: 102 OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem) 103 except OpenSSL.crypto.Error, err: 104 pass 105 else: 106 raise JoinError("No private key may be given") 107 108 try: 109 cert = \ 110 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem) 111 except Exception, err: 112 raise errors.X509CertError("(stdin)", 113 "Unable to load certificate: %s" % err) 114 115 _check_fn(cert)
116 117
118 -def VerifyCertificate(data, _verify_fn=_VerifyCertificate):
119 """Verifies cluster certificate. 120 121 @type data: dict 122 123 """ 124 cert = data.get(constants.SSHS_NODE_DAEMON_CERTIFICATE) 125 if cert: 126 _verify_fn(cert)
127 128
129 -def _UpdateKeyFiles(keys, dry_run, keyfiles):
130 """Updates SSH key files. 131 132 @type keys: sequence of tuple; (string, string, string) 133 @param keys: Keys to write, tuples consist of key type 134 (L{constants.SSHK_ALL}), public and private key 135 @type dry_run: boolean 136 @param dry_run: Whether to perform a dry run 137 @type keyfiles: dict; (string as key, tuple with (string, string) as values) 138 @param keyfiles: Mapping from key types (L{constants.SSHK_ALL}) to file 139 names; value tuples consist of public key filename and private key filename 140 141 """ 142 assert set(keyfiles) == constants.SSHK_ALL 143 144 for (kind, private_key, public_key) in keys: 145 (private_file, public_file) = keyfiles[kind] 146 147 logging.debug("Writing %s ...", private_file) 148 utils.WriteFile(private_file, data=private_key, mode=0600, 149 backup=True, dry_run=dry_run) 150 151 logging.debug("Writing %s ...", public_file) 152 utils.WriteFile(public_file, data=public_key, mode=0644, 153 backup=True, dry_run=dry_run)
154 155
156 -def UpdateSshDaemon(data, dry_run, _runcmd_fn=utils.RunCmd, 157 _keyfiles=None):
158 """Updates SSH daemon's keys. 159 160 Unless C{dry_run} is set, the daemon is restarted at the end. 161 162 @type data: dict 163 @param data: Input data 164 @type dry_run: boolean 165 @param dry_run: Whether to perform a dry run 166 167 """ 168 keys = data.get(constants.SSHS_SSH_HOST_KEY) 169 if not keys: 170 return 171 172 if _keyfiles is None: 173 _keyfiles = constants.SSH_DAEMON_KEYFILES 174 175 logging.info("Updating SSH daemon key files") 176 _UpdateKeyFiles(keys, dry_run, _keyfiles) 177 178 if dry_run: 179 logging.info("This is a dry run, not restarting SSH daemon") 180 else: 181 result = _runcmd_fn([pathutils.DAEMON_UTIL, "reload-ssh-keys"], 182 interactive=True) 183 if result.failed: 184 raise JoinError("Could not reload SSH keys, command '%s'" 185 " had exitcode %s and error %s" % 186 (result.cmd, result.exit_code, result.output))
187 188
189 -def UpdateSshRoot(data, dry_run, _homedir_fn=None):
190 """Updates root's SSH keys. 191 192 Root's C{authorized_keys} file is also updated with new public keys. 193 194 @type data: dict 195 @param data: Input data 196 @type dry_run: boolean 197 @param dry_run: Whether to perform a dry run 198 199 """ 200 keys = data.get(constants.SSHS_SSH_ROOT_KEY) 201 if not keys: 202 return 203 204 (auth_keys_file, keyfiles) = \ 205 ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=True, 206 _homedir_fn=_homedir_fn) 207 208 _UpdateKeyFiles(keys, dry_run, keyfiles) 209 210 if dry_run: 211 logging.info("This is a dry run, not modifying %s", auth_keys_file) 212 else: 213 for (_, _, public_key) in keys: 214 utils.AddAuthorizedKey(auth_keys_file, public_key)
215 216
217 -def Main():
218 """Main routine. 219 220 """ 221 opts = ParseOptions() 222 223 utils.SetupToolLogging(opts.debug, opts.verbose) 224 225 try: 226 data = common.LoadData(sys.stdin.read(), _DATA_CHECK) 227 228 # Check if input data is correct 229 common.VerifyClusterName(data, JoinError) 230 VerifyCertificate(data) 231 232 # Update SSH files 233 UpdateSshDaemon(data, opts.dry_run) 234 UpdateSshRoot(data, opts.dry_run) 235 236 logging.info("Setup finished successfully") 237 except Exception, err: # pylint: disable=W0703 238 logging.debug("Caught unhandled exception", exc_info=True) 239 240 (retcode, message) = cli.FormatError(err) 241 logging.error(message) 242 243 return retcode 244 else: 245 return constants.EXIT_SUCCESS
246