Package ganeti :: Module ssh
[hide private]
[frames] | no frames]

Source Code for Module ganeti.ssh

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007 Google Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, but 
 12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 14  # General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
 19  # 02110-1301, USA. 
 20   
 21   
 22  """Module encapsulating ssh functionality. 
 23   
 24  """ 
 25   
 26   
 27  import os 
 28  import logging 
 29  import re 
 30   
 31  from ganeti import utils 
 32  from ganeti import errors 
 33  from ganeti import constants 
 34   
 35   
36 -def FormatParamikoFingerprint(fingerprint):
37 """Format paramiko PKey fingerprint. 38 39 @type fingerprint: str 40 @param fingerprint: PKey fingerprint 41 @return: The string hex representation of the fingerprint 42 43 """ 44 assert len(fingerprint) % 2 == 0 45 return ":".join(re.findall(r"..", fingerprint.lower()))
46 47
48 -def GetUserFiles(user, mkdir=False):
49 """Return the paths of a user's ssh files. 50 51 The function will return a triplet (priv_key_path, pub_key_path, 52 auth_key_path) that are used for ssh authentication. Currently, the 53 keys used are DSA keys, so this function will return: 54 (~user/.ssh/id_dsa, ~user/.ssh/id_dsa.pub, 55 ~user/.ssh/authorized_keys). 56 57 If the optional parameter mkdir is True, the ssh directory will be 58 created if it doesn't exist. 59 60 Regardless of the mkdir parameters, the script will raise an error 61 if ~user/.ssh is not a directory. 62 63 """ 64 user_dir = utils.GetHomeDir(user) 65 if not user_dir: 66 raise errors.OpExecError("Cannot resolve home of user %s" % user) 67 68 ssh_dir = utils.PathJoin(user_dir, ".ssh") 69 if mkdir: 70 utils.EnsureDirs([(ssh_dir, constants.SECURE_DIR_MODE)]) 71 elif not os.path.isdir(ssh_dir): 72 raise errors.OpExecError("Path %s is not a directory" % ssh_dir) 73 74 return [utils.PathJoin(ssh_dir, base) 75 for base in ["id_dsa", "id_dsa.pub", "authorized_keys"]]
76 77
78 -class SshRunner:
79 """Wrapper for SSH commands. 80 81 """
82 - def __init__(self, cluster_name):
83 self.cluster_name = cluster_name
84
85 - def _BuildSshOptions(self, batch, ask_key, use_cluster_key, 86 strict_host_check, private_key=None, quiet=True):
87 """Builds a list with needed SSH options. 88 89 @param batch: same as ssh's batch option 90 @param ask_key: allows ssh to ask for key confirmation; this 91 parameter conflicts with the batch one 92 @param use_cluster_key: if True, use the cluster name as the 93 HostKeyAlias name 94 @param strict_host_check: this makes the host key checking strict 95 @param private_key: use this private key instead of the default 96 @param quiet: whether to enable -q to ssh 97 98 @rtype: list 99 @return: the list of options ready to use in L{utils.RunCmd} 100 101 """ 102 options = [ 103 "-oEscapeChar=none", 104 "-oHashKnownHosts=no", 105 "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE, 106 "-oUserKnownHostsFile=/dev/null", 107 "-oCheckHostIp=no", 108 ] 109 110 if use_cluster_key: 111 options.append("-oHostKeyAlias=%s" % self.cluster_name) 112 113 if quiet: 114 options.append("-q") 115 116 if private_key: 117 options.append("-i%s" % private_key) 118 119 # TODO: Too many boolean options, maybe convert them to more descriptive 120 # constants. 121 122 # Note: ask_key conflicts with batch mode 123 if batch: 124 if ask_key: 125 raise errors.ProgrammerError("SSH call requested conflicting options") 126 127 options.append("-oBatchMode=yes") 128 129 if strict_host_check: 130 options.append("-oStrictHostKeyChecking=yes") 131 else: 132 options.append("-oStrictHostKeyChecking=no") 133 134 else: 135 # non-batch mode 136 137 if ask_key: 138 options.append("-oStrictHostKeyChecking=ask") 139 elif strict_host_check: 140 options.append("-oStrictHostKeyChecking=yes") 141 else: 142 options.append("-oStrictHostKeyChecking=no") 143 144 return options
145
146 - def BuildCmd(self, hostname, user, command, batch=True, ask_key=False, 147 tty=False, use_cluster_key=True, strict_host_check=True, 148 private_key=None, quiet=True):
149 """Build an ssh command to execute a command on a remote node. 150 151 @param hostname: the target host, string 152 @param user: user to auth as 153 @param command: the command 154 @param batch: if true, ssh will run in batch mode with no prompting 155 @param ask_key: if true, ssh will run with 156 StrictHostKeyChecking=ask, so that we can connect to an 157 unknown host (not valid in batch mode) 158 @param use_cluster_key: whether to expect and use the 159 cluster-global SSH key 160 @param strict_host_check: whether to check the host's SSH key at all 161 @param private_key: use this private key instead of the default 162 @param quiet: whether to enable -q to ssh 163 164 @return: the ssh call to run 'command' on the remote host. 165 166 """ 167 argv = [constants.SSH] 168 argv.extend(self._BuildSshOptions(batch, ask_key, use_cluster_key, 169 strict_host_check, private_key, 170 quiet=quiet)) 171 if tty: 172 argv.extend(["-t", "-t"]) 173 argv.extend(["%s@%s" % (user, hostname), command]) 174 return argv
175
176 - def Run(self, *args, **kwargs):
177 """Runs a command on a remote node. 178 179 This method has the same return value as `utils.RunCmd()`, which it 180 uses to launch ssh. 181 182 Args: see SshRunner.BuildCmd. 183 184 @rtype: L{utils.RunResult} 185 @return: the result as from L{utils.RunCmd()} 186 187 """ 188 return utils.RunCmd(self.BuildCmd(*args, **kwargs))
189
190 - def CopyFileToNode(self, node, filename):
191 """Copy a file to another node with scp. 192 193 @param node: node in the cluster 194 @param filename: absolute pathname of a local file 195 196 @rtype: boolean 197 @return: the success of the operation 198 199 """ 200 if not os.path.isabs(filename): 201 logging.error("File %s must be an absolute path", filename) 202 return False 203 204 if not os.path.isfile(filename): 205 logging.error("File %s does not exist", filename) 206 return False 207 208 command = [constants.SCP, "-p"] 209 command.extend(self._BuildSshOptions(True, False, True, True)) 210 command.append(filename) 211 command.append("%s:%s" % (node, filename)) 212 213 result = utils.RunCmd(command) 214 215 if result.failed: 216 logging.error("Copy to node %s failed (%s) error %s," 217 " command was %s", 218 node, result.fail_reason, result.output, result.cmd) 219 220 return not result.failed
221
222 - def VerifyNodeHostname(self, node):
223 """Verify hostname consistency via SSH. 224 225 This functions connects via ssh to a node and compares the hostname 226 reported by the node to the name with have (the one that we 227 connected to). 228 229 This is used to detect problems in ssh known_hosts files 230 (conflicting known hosts) and inconsistencies between dns/hosts 231 entries and local machine names 232 233 @param node: nodename of a host to check; can be short or 234 full qualified hostname 235 236 @return: (success, detail), where: 237 - success: True/False 238 - detail: string with details 239 240 """ 241 retval = self.Run(node, 'root', 'hostname --fqdn') 242 243 if retval.failed: 244 msg = "ssh problem" 245 output = retval.output 246 if output: 247 msg += ": %s" % output 248 else: 249 msg += ": %s (no output)" % retval.fail_reason 250 logging.error("Command %s failed: %s", retval.cmd, msg) 251 return False, msg 252 253 remotehostname = retval.stdout.strip() 254 255 if not remotehostname or remotehostname != node: 256 if node.startswith(remotehostname + "."): 257 msg = "hostname not FQDN" 258 else: 259 msg = "hostname mismatch" 260 return False, ("%s: expected %s but got %s" % 261 (msg, node, remotehostname)) 262 263 return True, "host matches"
264 265
266 -def WriteKnownHostsFile(cfg, file_name):
267 """Writes the cluster-wide equally known_hosts file. 268 269 """ 270 utils.WriteFile(file_name, mode=0600, 271 data="%s ssh-rsa %s\n" % (cfg.GetClusterName(), 272 cfg.GetHostKey()))
273