Statistics
| Branch: | Tag: | Revision:

root / lib / ssh.py @ aa4260ca

History | View | Annotate | Download (5.9 kB)

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

24 a8083063 Iustin Pop
"""
25 a8083063 Iustin Pop
26 a8083063 Iustin Pop
27 a8083063 Iustin Pop
import os
28 a8083063 Iustin Pop
29 a8083063 Iustin Pop
from ganeti import logger
30 a8083063 Iustin Pop
from ganeti import utils
31 a8083063 Iustin Pop
from ganeti import errors
32 82122173 Iustin Pop
from ganeti import constants
33 82122173 Iustin Pop
34 82122173 Iustin Pop
35 82122173 Iustin Pop
__all__ = ["SSHCall", "CopyFileToNode", "VerifyNodeHostname",
36 82122173 Iustin Pop
           "KNOWN_HOSTS_OPTS", "BATCH_MODE_OPTS", "ASK_KEY_OPTS"]
37 82122173 Iustin Pop
38 82122173 Iustin Pop
39 82122173 Iustin Pop
KNOWN_HOSTS_OPTS = [
40 82122173 Iustin Pop
  "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE,
41 82122173 Iustin Pop
  "-oUserKnownHostsFile=/dev/null",
42 82122173 Iustin Pop
  ]
43 82122173 Iustin Pop
44 82122173 Iustin Pop
# Note: BATCH_MODE conflicts with ASK_KEY
45 82122173 Iustin Pop
BATCH_MODE_OPTS = [
46 82122173 Iustin Pop
  "-oEscapeChar=none",
47 82122173 Iustin Pop
  "-oBatchMode=yes",
48 82122173 Iustin Pop
  "-oStrictHostKeyChecking=yes",
49 82122173 Iustin Pop
  ]
50 82122173 Iustin Pop
51 82122173 Iustin Pop
ASK_KEY_OPTS = [
52 82122173 Iustin Pop
  "-oStrictHostKeyChecking=ask",
53 82122173 Iustin Pop
  "-oEscapeChar=none",
54 82122173 Iustin Pop
  "-oHashKnownHosts=no",
55 82122173 Iustin Pop
  ]
56 82122173 Iustin Pop
57 72f0f7fd Iustin Pop
58 70d9e3d8 Iustin Pop
def GetUserFiles(user, mkdir=False):
59 70d9e3d8 Iustin Pop
  """Return the paths of a user's ssh files.
60 70d9e3d8 Iustin Pop

61 70d9e3d8 Iustin Pop
  The function will return a triplet (priv_key_path, pub_key_path,
62 70d9e3d8 Iustin Pop
  auth_key_path) that are used for ssh authentication. Currently, the
63 70d9e3d8 Iustin Pop
  keys used are DSA keys, so this function will return:
64 70d9e3d8 Iustin Pop
  (~user/.ssh/id_dsa, ~user/.ssh/id_dsa.pub,
65 70d9e3d8 Iustin Pop
  ~user/.ssh/authorized_keys).
66 70d9e3d8 Iustin Pop

67 70d9e3d8 Iustin Pop
  If the optional parameter mkdir is True, the ssh directory will be
68 70d9e3d8 Iustin Pop
  created if it doesn't exist.
69 70d9e3d8 Iustin Pop

70 70d9e3d8 Iustin Pop
  Regardless of the mkdir parameters, the script will raise an error
71 70d9e3d8 Iustin Pop
  if ~user/.ssh is not a directory.
72 70d9e3d8 Iustin Pop

73 70d9e3d8 Iustin Pop
  """
74 70d9e3d8 Iustin Pop
  user_dir = utils.GetHomeDir(user)
75 70d9e3d8 Iustin Pop
  if not user_dir:
76 70d9e3d8 Iustin Pop
    raise errors.OpExecError("Cannot resolve home of user %s" % user)
77 70d9e3d8 Iustin Pop
78 70d9e3d8 Iustin Pop
  ssh_dir = os.path.join(user_dir, ".ssh")
79 70d9e3d8 Iustin Pop
  if not os.path.lexists(ssh_dir):
80 70d9e3d8 Iustin Pop
    if mkdir:
81 70d9e3d8 Iustin Pop
      try:
82 70d9e3d8 Iustin Pop
        os.mkdir(ssh_dir, 0700)
83 70d9e3d8 Iustin Pop
      except EnvironmentError, err:
84 70d9e3d8 Iustin Pop
        raise errors.OpExecError("Can't create .ssh dir for user %s: %s" %
85 70d9e3d8 Iustin Pop
                                 (user, str(err)))
86 70d9e3d8 Iustin Pop
  elif not os.path.isdir(ssh_dir):
87 70d9e3d8 Iustin Pop
    raise errors.OpExecError("path ~%s/.ssh is not a directory" % user)
88 70d9e3d8 Iustin Pop
89 70d9e3d8 Iustin Pop
  return [os.path.join(ssh_dir, base)
90 70d9e3d8 Iustin Pop
          for base in ["id_dsa", "id_dsa.pub", "authorized_keys"]]
91 70d9e3d8 Iustin Pop
92 70d9e3d8 Iustin Pop
93 00003458 Guido Trotter
def BuildSSHCmd(hostname, user, command, batch=True, ask_key=False):
94 00003458 Guido Trotter
  """Build an ssh string to execute a command on a remote node.
95 a8083063 Iustin Pop

96 a8083063 Iustin Pop
  Args:
97 a8083063 Iustin Pop
    hostname: the target host, string
98 a8083063 Iustin Pop
    user: user to auth as
99 a8083063 Iustin Pop
    command: the command
100 82122173 Iustin Pop
    batch: if true, ssh will run in batch mode with no prompting
101 82122173 Iustin Pop
    ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
102 82122173 Iustin Pop
             we can connect to an unknown host (not valid in batch mode)
103 a8083063 Iustin Pop

104 a8083063 Iustin Pop
  Returns:
105 00003458 Guido Trotter
    The ssh call to run 'command' on the remote host.
106 a8083063 Iustin Pop

107 a8083063 Iustin Pop
  """
108 82122173 Iustin Pop
  argv = ["ssh", "-q"]
109 82122173 Iustin Pop
  argv.extend(KNOWN_HOSTS_OPTS)
110 a8083063 Iustin Pop
  if batch:
111 a8083063 Iustin Pop
    # if we are in batch mode, we can't ask the key
112 a8083063 Iustin Pop
    if ask_key:
113 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("SSH call requested conflicting options")
114 82122173 Iustin Pop
    argv.extend(BATCH_MODE_OPTS)
115 82122173 Iustin Pop
  elif ask_key:
116 82122173 Iustin Pop
    argv.extend(ASK_KEY_OPTS)
117 72f0f7fd Iustin Pop
  argv.extend(["%s@%s" % (user, hostname), command])
118 00003458 Guido Trotter
  return argv
119 00003458 Guido Trotter
120 00003458 Guido Trotter
121 00003458 Guido Trotter
def SSHCall(hostname, user, command, batch=True, ask_key=False):
122 00003458 Guido Trotter
  """Execute a command on a remote node.
123 00003458 Guido Trotter

124 00003458 Guido Trotter
  This method has the same return value as `utils.RunCmd()`, which it
125 00003458 Guido Trotter
  uses to launch ssh.
126 00003458 Guido Trotter

127 00003458 Guido Trotter
  Args:
128 00003458 Guido Trotter
    hostname: the target host, string
129 00003458 Guido Trotter
    user: user to auth as
130 00003458 Guido Trotter
    command: the command
131 00003458 Guido Trotter
    batch: if true, ssh will run in batch mode with no prompting
132 00003458 Guido Trotter
    ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
133 00003458 Guido Trotter
             we can connect to an unknown host (not valid in batch mode)
134 00003458 Guido Trotter

135 00003458 Guido Trotter
  Returns:
136 00003458 Guido Trotter
    `utils.RunResult` as for `utils.RunCmd()`
137 00003458 Guido Trotter

138 00003458 Guido Trotter
  """
139 aa4260ca Iustin Pop
  return utils.RunCmd(BuildSSHCmd(hostname, user, command,
140 aa4260ca Iustin Pop
                                  batch=batch, ask_key=ask_key))
141 a8083063 Iustin Pop
142 a8083063 Iustin Pop
143 a8083063 Iustin Pop
def CopyFileToNode(node, filename):
144 a8083063 Iustin Pop
  """Copy a file to another node with scp.
145 a8083063 Iustin Pop

146 a8083063 Iustin Pop
  Args:
147 a8083063 Iustin Pop
    node: node in the cluster
148 a8083063 Iustin Pop
    filename: absolute pathname of a local file
149 a8083063 Iustin Pop

150 a8083063 Iustin Pop
  Returns:
151 a8083063 Iustin Pop
    success: True/False
152 a8083063 Iustin Pop

153 a8083063 Iustin Pop
  """
154 a8083063 Iustin Pop
  if not os.path.isfile(filename):
155 a8083063 Iustin Pop
    logger.Error("file %s does not exist" % (filename))
156 a8083063 Iustin Pop
    return False
157 a8083063 Iustin Pop
158 a8083063 Iustin Pop
  if not os.path.isabs(filename):
159 a8083063 Iustin Pop
    logger.Error("file %s must be an absolute path" % (filename))
160 a8083063 Iustin Pop
    return False
161 a8083063 Iustin Pop
162 82122173 Iustin Pop
  command = ["scp", "-q", "-p"]
163 82122173 Iustin Pop
  command.extend(KNOWN_HOSTS_OPTS)
164 82122173 Iustin Pop
  command.extend(BATCH_MODE_OPTS)
165 82122173 Iustin Pop
  command.append(filename)
166 82122173 Iustin Pop
  command.append("%s:%s" % (node, filename))
167 a8083063 Iustin Pop
168 a8083063 Iustin Pop
  result = utils.RunCmd(command)
169 a8083063 Iustin Pop
170 a8083063 Iustin Pop
  if result.failed:
171 a8083063 Iustin Pop
    logger.Error("copy to node %s failed (%s) error %s,"
172 a8083063 Iustin Pop
                 " command was %s" %
173 a8083063 Iustin Pop
                 (node, result.fail_reason, result.output, result.cmd))
174 a8083063 Iustin Pop
175 a8083063 Iustin Pop
  return not result.failed
176 a8083063 Iustin Pop
177 a8083063 Iustin Pop
178 a8083063 Iustin Pop
def VerifyNodeHostname(node):
179 a8083063 Iustin Pop
  """Verify hostname consistency via SSH.
180 a8083063 Iustin Pop

181 a8083063 Iustin Pop

182 a8083063 Iustin Pop
  This functions connects via ssh to a node and compares the hostname
183 a8083063 Iustin Pop
  reported by the node to the name with have (the one that we
184 a8083063 Iustin Pop
  connected to).
185 a8083063 Iustin Pop

186 a8083063 Iustin Pop
  This is used to detect problems in ssh known_hosts files
187 a8083063 Iustin Pop
  (conflicting known hosts) and incosistencies between dns/hosts
188 a8083063 Iustin Pop
  entries and local machine names
189 a8083063 Iustin Pop

190 a8083063 Iustin Pop
  Args:
191 a8083063 Iustin Pop
    node: nodename of a host to check. can be short or full qualified hostname
192 a8083063 Iustin Pop

193 a8083063 Iustin Pop
  Returns:
194 a8083063 Iustin Pop
    (success, detail)
195 a8083063 Iustin Pop
    where
196 a8083063 Iustin Pop
      success: True/False
197 a8083063 Iustin Pop
      detail: String with details
198 a8083063 Iustin Pop

199 a8083063 Iustin Pop
  """
200 a8083063 Iustin Pop
  retval = SSHCall(node, 'root', 'hostname')
201 a8083063 Iustin Pop
202 a8083063 Iustin Pop
  if retval.failed:
203 a8083063 Iustin Pop
    msg = "ssh problem"
204 a8083063 Iustin Pop
    output = retval.output
205 a8083063 Iustin Pop
    if output:
206 a8083063 Iustin Pop
      msg += ": %s" % output
207 a8083063 Iustin Pop
    return False, msg
208 a8083063 Iustin Pop
209 a8083063 Iustin Pop
  remotehostname = retval.stdout.strip()
210 a8083063 Iustin Pop
211 a8083063 Iustin Pop
  if not remotehostname or remotehostname != node:
212 a8083063 Iustin Pop
    return False, "hostname mismatch, got %s" % remotehostname
213 a8083063 Iustin Pop
214 a8083063 Iustin Pop
  return True, "host matches"